chore: extract more strings (#933)

This commit is contained in:
Mihai C 2021-10-13 12:34:55 +03:00 committed by GitHub
parent 4ce4b141b4
commit bee41b242b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 99 deletions

View file

@ -1,28 +1,32 @@
import { CodeIcon, CreditCardIcon, KeyIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid"; import { CodeIcon, CreditCardIcon, KeyIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
import React from "react"; import React from "react";
import { useLocale } from "@lib/hooks/useLocale";
import NavTabs from "./NavTabs"; import NavTabs from "./NavTabs";
export default function SettingsShell({ children }: { children: React.ReactNode }) { export default function SettingsShell({ children }: { children: React.ReactNode }) {
const { t } = useLocale();
const tabs = [ const tabs = [
{ {
name: "Profile", name: t("profile"),
href: "/settings/profile", href: "/settings/profile",
icon: UserIcon, icon: UserIcon,
}, },
{ {
name: "Security", name: t("security"),
href: "/settings/security", href: "/settings/security",
icon: KeyIcon, icon: KeyIcon,
}, },
{ name: "Embed & Webhooks", href: "/settings/embed", icon: CodeIcon }, { name: t("embed_and_webhooks"), href: "/settings/embed", icon: CodeIcon },
{ {
name: "Teams", name: t("teams"),
href: "/settings/teams", href: "/settings/teams",
icon: UserGroupIcon, icon: UserGroupIcon,
}, },
{ {
name: "Billing", name: t("billing"),
href: "/settings/billing", href: "/settings/billing",
icon: CreditCardIcon, icon: CreditCardIcon,
}, },

View file

@ -19,7 +19,7 @@ const TwoFactorAuthSection = ({ twoFactorEnabled }: { twoFactorEnabled: boolean
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<h2 className="font-cal text-lg leading-6 font-medium text-gray-900">{t("2fa")}</h2> <h2 className="font-cal text-lg leading-6 font-medium text-gray-900">{t("2fa")}</h2>
<Badge className="text-xs ml-2" variant={enabled ? "success" : "gray"}> <Badge className="text-xs ml-2" variant={enabled ? "success" : "gray"}>
{enabled ? "Enabled" : "Disabled"} {enabled ? t("enabled") : t("disabled")}
</Badge> </Badge>
</div> </div>
<p className="mt-1 text-sm text-gray-500">{t("add_an_extra_layer_of_security")}</p> <p className="mt-1 text-sm text-gray-500">{t("add_an_extra_layer_of_security")}</p>
@ -28,7 +28,7 @@ const TwoFactorAuthSection = ({ twoFactorEnabled }: { twoFactorEnabled: boolean
className="mt-6" className="mt-6"
type="submit" type="submit"
onClick={() => (enabled ? setDisableModalOpen(true) : setEnableModalOpen(true))}> onClick={() => (enabled ? setDisableModalOpen(true) : setEnableModalOpen(true))}>
{enabled ? "Disable" : "Enable"} {t("2fa")} {enabled ? t("disable") : t("enable")} {t("2fa")}
</Button> </Button>
{enableModalOpen && ( {enableModalOpen && (

View file

@ -1,7 +1,10 @@
import { ExternalLinkIcon } from "@heroicons/react/solid"; import { ExternalLinkIcon } from "@heroicons/react/solid";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { getSession } from "@lib/auth"; 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 prisma from "@lib/prisma";
import SettingsShell from "@components/SettingsShell"; import SettingsShell from "@components/SettingsShell";
@ -9,24 +12,26 @@ import Shell from "@components/Shell";
import Button from "@components/ui/Button"; import Button from "@components/ui/Button";
export default function Billing() { export default function Billing() {
const { t } = useLocale();
return ( return (
<Shell heading="Billing" subtitle="Manage your billing information and cancel your subscription."> <Shell heading={t("billing")} subtitle={t("manage_your_billing_info")}>
<SettingsShell> <SettingsShell>
<div className="py-6 lg:pb-8 lg:col-span-9"> <div className="py-6 lg:pb-8 lg:col-span-9">
<div className="bg-white border sm:rounded-sm"> <div className="bg-white border sm:rounded-sm">
<div className="px-4 py-5 sm:p-6"> <div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900"> <h3 className="text-lg leading-6 font-medium text-gray-900">
View and manage your billing details {t("view_and_manage_billing_details")}
</h3> </h3>
<div className="mt-2 max-w-xl text-sm text-gray-500"> <div className="mt-2 max-w-xl text-sm text-gray-500">
<p>View and edit your billing details, as well as cancel your subscription.</p> <p>{t("view_and_edit_billing_details")}</p>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<form <form
method="POST" method="POST"
action={`${process.env.NEXT_PUBLIC_BASE_URL}/api/integrations/stripepayment/portal`}> action={`${process.env.NEXT_PUBLIC_BASE_URL}/api/integrations/stripepayment/portal`}>
<Button type="submit"> <Button type="submit">
Go to the billing portal <ExternalLinkIcon className="ml-1 w-4 h-4" /> {t("go_to_billing_portal")} <ExternalLinkIcon className="ml-1 w-4 h-4" />
</Button> </Button>
</form> </form>
</div> </div>
@ -34,13 +39,13 @@ export default function Billing() {
</div> </div>
<div className="mt-4 bg-gray-50 sm:rounded-sm border"> <div className="mt-4 bg-gray-50 sm:rounded-sm border">
<div className="px-4 py-5 sm:p-6"> <div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Need anything else?</h3> <h3 className="text-lg leading-6 font-medium text-gray-900">{t("need_anything_else")}</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500"> <div className="mt-2 max-w-xl text-sm text-gray-500">
<p>If you need any further help with billing, our support team are here to help.</p> <p>{t("further_billing_help")}</p>
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Button href="mailto:help@cal.com" color="secondary" type="submit"> <Button href="mailto:help@cal.com" color="secondary" type="submit">
Contact our support team {t("contact_our_support_team")}
</Button> </Button>
</div> </div>
</div> </div>
@ -53,6 +58,8 @@ export default function Billing() {
export async function getServerSideProps(context: GetServerSidePropsContext) { export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession(context); const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session) { if (!session) {
return { redirect: { permanent: false, destination: "/auth/login" } }; return { redirect: { permanent: false, destination: "/auth/login" } };
} }
@ -74,6 +81,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
}); });
return { return {
props: { session, user }, props: {
session,
user,
...(await serverSideTranslations(locale, ["common"])),
},
}; };
} }

View file

@ -1,9 +1,12 @@
import { PlusIcon } from "@heroicons/react/outline"; import { PlusIcon } from "@heroicons/react/outline";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { useSession } from "next-auth/client"; import { useSession } from "next-auth/client";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import { getSession } from "@lib/auth"; 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 prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
import { Webhook } from "@lib/webhook"; import { Webhook } from "@lib/webhook";
@ -19,6 +22,7 @@ import WebhookList from "@components/webhook/WebhookList";
export default function Embed(props: inferSSRProps<typeof getServerSideProps>) { export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
const [, loading] = useSession(); const [, loading] = useSession();
const { t } = useLocale();
const [isLoading, setLoading] = useState(false); const [isLoading, setLoading] = useState(false);
const [bookingCreated, setBookingCreated] = useState(true); const [bookingCreated, setBookingCreated] = useState(true);
@ -52,7 +56,9 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
} }
const iframeTemplate = `<iframe src="${process.env.NEXT_PUBLIC_APP_URL}/${props.user?.username}" frameborder="0" allowfullscreen></iframe>`; const iframeTemplate = `<iframe src="${process.env.NEXT_PUBLIC_APP_URL}/${props.user?.username}" frameborder="0" allowfullscreen></iframe>`;
const htmlTemplate = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Schedule a meeting</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body>${iframeTemplate}</body></html>`; const htmlTemplate = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${t(
"schedule_a_meeting"
)}</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body>${iframeTemplate}</body></html>`;
const handleErrors = async (resp: Response) => { const handleErrors = async (resp: Response) => {
if (!resp.ok) { if (!resp.ok) {
const err = await resp.json(); const err = await resp.json();
@ -106,26 +112,24 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
}; };
return ( return (
<Shell <Shell heading={t("embed_and_webhooks")} subtitle={t("integrate_using_embed_or_webhooks")}>
heading="Embed &amp; Webhooks"
subtitle="Integrate with your website using our embed options, or get real-time booking information using custom webhooks.">
<SettingsShell> <SettingsShell>
{!editWebhookEnabled && ( {!editWebhookEnabled && (
<div className="py-6 lg:pb-8 lg:col-span-9"> <div className="py-6 lg:pb-8 lg:col-span-9">
<div className="mb-6"> <div className="mb-6">
<h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">iframe Embed</h2> <h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">{t("iframe_embed")}</h2>
<p className="mt-1 text-sm text-gray-500">The easiest way to embed Cal.com on your website.</p> <p className="mt-1 text-sm text-gray-500">{t("embed_calcom")}</p>
</div> </div>
<div className="grid grid-cols-2 space-x-4"> <div className="grid grid-cols-2 space-x-4">
<div> <div>
<label htmlFor="iframe" className="block text-sm font-medium text-gray-700"> <label htmlFor="iframe" className="block text-sm font-medium text-gray-700">
Standard iframe {t("standard_iframe")}
</label> </label>
<div className="mt-1"> <div className="mt-1">
<textarea <textarea
id="iframe" id="iframe"
className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm" className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm"
placeholder="Loading..." placeholder={t("loading")}
defaultValue={iframeTemplate} defaultValue={iframeTemplate}
readOnly readOnly
/> />
@ -133,13 +137,13 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
</div> </div>
<div> <div>
<label htmlFor="fullscreen" className="block text-sm font-medium text-gray-700"> <label htmlFor="fullscreen" className="block text-sm font-medium text-gray-700">
Responsive full screen iframe {t("responsive_fullscreen_iframe")}
</label> </label>
<div className="mt-1"> <div className="mt-1">
<textarea <textarea
id="fullscreen" id="fullscreen"
className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm" className="block w-full h-32 border-gray-300 rounded-sm shadow-sm focus:ring-black focus:border-black sm:text-sm"
placeholder="Loading..." placeholder={t("loading")}
defaultValue={htmlTemplate} defaultValue={htmlTemplate}
readOnly readOnly
/> />
@ -150,26 +154,23 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
<div className="flex justify-between my-6"> <div className="flex justify-between my-6">
<div> <div>
<h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">Webhooks</h2> <h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">Webhooks</h2>
<p className="mt-1 text-sm text-gray-500"> <p className="mt-1 text-sm text-gray-500">{t("receive_cal_meeting_data")} </p>
Receive Cal meeting data at a specified URL, in real-time, when an event is scheduled or
cancelled.{" "}
</p>
</div> </div>
<div> <div>
<Dialog> <Dialog>
<DialogTrigger className="px-4 py-2 my-6 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900"> <DialogTrigger className="px-4 py-2 my-6 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="inline w-5 h-5 mr-1" /> <PlusIcon className="inline w-5 h-5 mr-1" />
New Webhook {t("new_webhook")}
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader <DialogHeader
title="Create a new webhook" title={t("create_new_webhook")}
subtitle="Create a new webhook to your account" subtitle={t("create_new_webhook_to_account")}
/> />
<div className="my-4"> <div className="my-4">
<div className="mb-4"> <div className="mb-4">
<label htmlFor="subUrl" className="block text-sm font-medium text-gray-700"> <label htmlFor="subUrl" className="block text-sm font-medium text-gray-700">
Subscriber Url {t("subscriber_url")}
</label> </label>
<input <input
ref={subUrlRef} ref={subUrlRef}
@ -182,12 +183,12 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
/> />
<legend className="block pt-4 mb-2 text-sm font-medium text-gray-700"> <legend className="block pt-4 mb-2 text-sm font-medium text-gray-700">
{" "} {" "}
Event Triggers{" "} {t("event_triggers")}{" "}
</legend> </legend>
<div className="p-2 border border-gray-300 rounded-sm"> <div className="p-2 border border-gray-300 rounded-sm">
<div className="flex pb-4"> <div className="flex pb-4">
<div className="w-10/12"> <div className="w-10/12">
<h2 className="font-medium text-gray-800">Booking Created</h2> <h2 className="font-medium text-gray-800">{t("booking_created")}</h2>
</div> </div>
<div className="flex items-center justify-center w-2/12 text-right"> <div className="flex items-center justify-center w-2/12 text-right">
<Switch <Switch
@ -202,7 +203,7 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
</div> </div>
<div className="flex py-1"> <div className="flex py-1">
<div className="w-10/12"> <div className="w-10/12">
<h2 className="font-medium text-gray-800">Booking Rescheduled</h2> <h2 className="font-medium text-gray-800">{t("booking_rescheduled")}</h2>
</div> </div>
<div className="flex items-center justify-center w-2/12 text-right"> <div className="flex items-center justify-center w-2/12 text-right">
<Switch <Switch
@ -217,7 +218,7 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
</div> </div>
<div className="flex pt-4"> <div className="flex pt-4">
<div className="w-10/12"> <div className="w-10/12">
<h2 className="font-medium text-gray-800">Booking Cancelled</h2> <h2 className="font-medium text-gray-800">{t("booking_cancelled")}</h2>
</div> </div>
<div className="flex items-center justify-center w-2/12 text-right"> <div className="flex items-center justify-center w-2/12 text-right">
<Switch <Switch
@ -240,11 +241,11 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
onClick={createWebhook} onClick={createWebhook}
color="primary" color="primary"
className="ml-2"> className="ml-2">
Create Webhook {t("create_webhook")}
</Button> </Button>
</DialogClose> </DialogClose>
<DialogClose asChild> <DialogClose asChild>
<Button color="secondary">Cancel</Button> <Button color="secondary">{t("cancel")}</Button>
</DialogClose> </DialogClose>
</div> </div>
</div> </div>
@ -272,12 +273,10 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
<hr className="mt-8" /> <hr className="mt-8" />
<div className="my-6"> <div className="my-6">
<h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">Cal.com API</h2> <h2 className="text-lg font-medium leading-6 text-gray-900 font-cal">Cal.com API</h2>
<p className="mt-1 text-sm text-gray-500"> <p className="mt-1 text-sm text-gray-500">{t("leverage_our_api")}</p>
Leverage our API for full control and customizability.
</p>
</div> </div>
<a href="https://developer.cal.com/api" className="btn btn-primary"> <a href="https://developer.cal.com/api" className="btn btn-primary">
Browse our API documentation {t("browse_api_documentation")}
</a> </a>
</div> </div>
)} )}
@ -289,6 +288,8 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
export async function getServerSideProps(context: GetServerSidePropsContext) { export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession(context); const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session?.user?.email) { if (!session?.user?.email) {
return { redirect: { permanent: false, destination: "/auth/login" } }; return { redirect: { permanent: false, destination: "/auth/login" } };
} }
@ -310,6 +311,10 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
}); });
return { return {
props: { session, user }, props: {
session,
user,
...(await serverSideTranslations(locale, ["common"])),
},
}; };
} }

View file

@ -14,6 +14,7 @@ import {
localeOptions, localeOptions,
OptionType, OptionType,
} from "@lib/core/i18n/i18n.utils"; } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale";
import { isBrandingHidden } from "@lib/isBrandingHidden"; import { isBrandingHidden } from "@lib/isBrandingHidden";
import showToast from "@lib/notification"; import showToast from "@lib/notification";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
@ -30,17 +31,9 @@ import Badge from "@components/ui/Badge";
import Button from "@components/ui/Button"; import Button from "@components/ui/Button";
import { UsernameInput } from "@components/ui/UsernameInput"; import { UsernameInput } from "@components/ui/UsernameInput";
const themeOptions = [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
];
type Props = inferSSRProps<typeof getServerSideProps>; type Props = inferSSRProps<typeof getServerSideProps>;
function HideBrandingInput(props: { function HideBrandingInput(props: { hideBrandingRef: RefObject<HTMLInputElement>; user: Props["user"] }) {
// const { t } = useLocale();
hideBrandingRef: RefObject<HTMLInputElement>;
user: Props["user"];
}) {
const [modelOpen, setModalOpen] = useState(false); const [modelOpen, setModalOpen] = useState(false);
return ( return (
<> <>
@ -72,19 +65,16 @@ function HideBrandingInput(props: {
<div className="sm:flex sm:items-start mb-4"> <div className="sm:flex sm:items-start mb-4">
<div className="mt-3 sm:mt-0 sm:text-left"> <div className="mt-3 sm:mt-0 sm:text-left">
<h3 className="font-cal text-lg leading-6 font-bold text-gray-900" id="modal-title"> <h3 className="font-cal text-lg leading-6 font-bold text-gray-900" id="modal-title">
This feature is only available in Pro plan {t("only_available_on_pro_plan")}
</h3> </h3>
</div> </div>
</div> </div>
<div className="flex flex-col space-y-3"> <div className="flex flex-col space-y-3">
<p> <p>{t("remove_cal_branding_description")}</p>
In order to remove the Cal branding from your booking pages, you need to upgrade to a paid
account.
</p>
<p> <p>
{" "} {" "}
To upgrade go to{" "} {t("to_upgrade_go_to")}{" "}
<a href="https://cal.com/upgrade" className="underline"> <a href="https://cal.com/upgrade" className="underline">
cal.com/upgrade cal.com/upgrade
</a> </a>
@ -96,7 +86,7 @@ function HideBrandingInput(props: {
<Button <Button
className="btn-wide btn-primary text-center table-cell" className="btn-wide btn-primary text-center table-cell"
onClick={() => setModalOpen(false)}> onClick={() => setModalOpen(false)}>
Dismiss {t("dismiss")}
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
@ -107,8 +97,14 @@ function HideBrandingInput(props: {
} }
export default function Settings(props: Props) { export default function Settings(props: Props) {
const { t } = useLocale();
const mutation = trpc.useMutation("viewer.updateProfile"); const mutation = trpc.useMutation("viewer.updateProfile");
const themeOptions = [
{ value: "light", label: t("light") },
{ value: "dark", label: t("dark") },
];
const usernameRef = useRef<HTMLInputElement>(null); const usernameRef = useRef<HTMLInputElement>(null);
const nameRef = useRef<HTMLInputElement>(null); const nameRef = useRef<HTMLInputElement>(null);
const descriptionRef = useRef<HTMLTextAreaElement>(); const descriptionRef = useRef<HTMLTextAreaElement>();
@ -178,7 +174,7 @@ export default function Settings(props: Props) {
locale: enteredLanguage, locale: enteredLanguage,
}) })
.then(() => { .then(() => {
showToast("Your user profile has been updated successfully", "success"); showToast(t("your_user_profile_updated_successfully"), "success");
setHasErrors(false); // dismiss any open errors setHasErrors(false); // dismiss any open errors
}) })
.catch((err) => { .catch((err) => {
@ -189,7 +185,7 @@ export default function Settings(props: Props) {
} }
return ( return (
<Shell heading="Profile" subtitle="Edit your profile information, which shows on your scheduling link."> <Shell heading={t("profile")} subtitle={t("edit_profile_info_description")}>
<SettingsShell> <SettingsShell>
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}> <form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
{hasErrors && <Alert severity="error" title={errorMessage} />} {hasErrors && <Alert severity="error" title={errorMessage} />}
@ -202,7 +198,7 @@ export default function Settings(props: Props) {
</div> </div>
<div className="w-full sm:w-1/2 sm:ml-2"> <div className="w-full sm:w-1/2 sm:ml-2">
<label htmlFor="name" className="block text-sm font-medium text-gray-700"> <label htmlFor="name" className="block text-sm font-medium text-gray-700">
Full name {t("full_name")}
</label> </label>
<input <input
ref={nameRef} ref={nameRef}
@ -210,7 +206,7 @@ export default function Settings(props: Props) {
name="name" name="name"
id="name" id="name"
autoComplete="given-name" autoComplete="given-name"
placeholder="Your name" placeholder={t("your_name")}
required required
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm" className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
defaultValue={props.user.name} defaultValue={props.user.name}
@ -221,19 +217,19 @@ export default function Settings(props: Props) {
<div className="block sm:flex"> <div className="block sm:flex">
<div className="w-full sm:w-1/2 sm:mr-2 mb-6"> <div className="w-full sm:w-1/2 sm:mr-2 mb-6">
<label htmlFor="email" className="block text-sm font-medium text-gray-700"> <label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email {t("email")}
</label> </label>
<input <input
type="text" type="text"
name="email" name="email"
id="email" id="email"
placeholder="Your email" placeholder={t("your_email")}
disabled disabled
className="mt-1 block w-full py-2 px-3 text-gray-500 border border-gray-300 rounded-l-sm bg-gray-50 sm:text-sm" className="mt-1 block w-full py-2 px-3 text-gray-500 border border-gray-300 rounded-l-sm bg-gray-50 sm:text-sm"
defaultValue={props.user.email} defaultValue={props.user.email}
/> />
<p className="mt-2 text-sm text-gray-500" id="email-description"> <p className="mt-2 text-sm text-gray-500" id="email-description">
To change your email, please contact{" "} {t("change_email_contact")}{" "}
<a className="text-blue-500" href="mailto:help@cal.com"> <a className="text-blue-500" href="mailto:help@cal.com">
help@cal.com help@cal.com
</a> </a>
@ -243,14 +239,14 @@ export default function Settings(props: Props) {
<div> <div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700"> <label htmlFor="about" className="block text-sm font-medium text-gray-700">
About {t("about")}
</label> </label>
<div className="mt-1"> <div className="mt-1">
<textarea <textarea
ref={descriptionRef} ref={descriptionRef}
id="about" id="about"
name="about" name="about"
placeholder="A little something about yourself." placeholder={t("little_something_about")}
rows={3} rows={3}
defaultValue={props.user.bio} defaultValue={props.user.bio}
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"></textarea> className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"></textarea>
@ -276,7 +272,7 @@ export default function Settings(props: Props) {
<ImageUploader <ImageUploader
target="avatar" target="avatar"
id="avatar-upload" id="avatar-upload"
buttonMsg="Change avatar" buttonMsg={t("change_avatar")}
handleAvatarChange={handleAvatarChange} handleAvatarChange={handleAvatarChange}
imageSrc={imageSrc} imageSrc={imageSrc}
/> />
@ -285,7 +281,7 @@ export default function Settings(props: Props) {
</div> </div>
<div> <div>
<label htmlFor="language" className="block text-sm font-medium text-gray-700"> <label htmlFor="language" className="block text-sm font-medium text-gray-700">
Language {t("language")}
</label> </label>
<div className="mt-1"> <div className="mt-1">
<Select <Select
@ -300,7 +296,7 @@ export default function Settings(props: Props) {
</div> </div>
<div> <div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700"> <label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
Timezone {t("timezone")}
</label> </label>
<div className="mt-1"> <div className="mt-1">
<TimezoneSelect <TimezoneSelect
@ -314,7 +310,7 @@ export default function Settings(props: Props) {
</div> </div>
<div> <div>
<label htmlFor="weekStart" className="block text-sm font-medium text-gray-700"> <label htmlFor="weekStart" className="block text-sm font-medium text-gray-700">
First Day of Week {t("first_day_of_week")}
</label> </label>
<div className="mt-1"> <div className="mt-1">
<Select <Select
@ -324,15 +320,15 @@ export default function Settings(props: Props) {
classNamePrefix="react-select" classNamePrefix="react-select"
className="react-select-container border border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm" className="react-select-container border border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm"
options={[ options={[
{ value: "Sunday", label: "Sunday" }, { value: "Sunday", label: t("sunday") },
{ value: "Monday", label: "Monday" }, { value: "Monday", label: t("monday") },
]} ]}
/> />
</div> </div>
</div> </div>
<div> <div>
<label htmlFor="theme" className="block text-sm font-medium text-gray-700"> <label htmlFor="theme" className="block text-sm font-medium text-gray-700">
Single Theme {t("single_theme")}
</label> </label>
<div className="my-1"> <div className="my-1">
<Select <Select
@ -358,7 +354,7 @@ export default function Settings(props: Props) {
</div> </div>
<div className="ml-3 text-sm"> <div className="ml-3 text-sm">
<label htmlFor="theme-adjust-os" className="font-medium text-gray-700"> <label htmlFor="theme-adjust-os" className="font-medium text-gray-700">
Automatically adjust theme based on invitee preferences {t("automatically_adjust_theme")}
</label> </label>
</div> </div>
</div> </div>
@ -370,10 +366,10 @@ export default function Settings(props: Props) {
</div> </div>
<div className="ml-3 text-sm"> <div className="ml-3 text-sm">
<label htmlFor="hide-branding" className="font-medium text-gray-700"> <label htmlFor="hide-branding" className="font-medium text-gray-700">
Disable Cal.com branding{" "} {t("disable_cal_branding")}{" "}
{props.user.plan !== "PRO" && <Badge variant="default">PRO</Badge>} {props.user.plan !== "PRO" && <Badge variant="default">PRO</Badge>}
</label> </label>
<p className="text-gray-500">Hide all Cal.com branding from your public pages.</p> <p className="text-gray-500">{t("disable_cal_branding_description")}</p>
</div> </div>
</div> </div>
</div> </div>
@ -418,7 +414,7 @@ export default function Settings(props: Props) {
</div> </div>
<hr className="mt-8" /> <hr className="mt-8" />
<div className="py-4 flex justify-end"> <div className="py-4 flex justify-end">
<Button type="submit">Save</Button> <Button type="submit">{t("save")}</Button>
</div> </div>
</div> </div>
</form> </form>

View file

@ -51,7 +51,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
return { return {
props: { props: {
localeProp: locale,
session, session,
user, user,
...(await serverSideTranslations(locale, ["common"])), ...(await serverSideTranslations(locale, ["common"])),

View file

@ -8,6 +8,7 @@ import { useEffect, useRef, useState } from "react";
import { getSession } from "@lib/auth"; import { getSession } from "@lib/auth";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils"; import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale";
import { Member } from "@lib/member"; import { Member } from "@lib/member";
import { Team } from "@lib/team"; import { Team } from "@lib/team";
@ -20,6 +21,7 @@ import TeamListItem from "@components/team/TeamListItem";
import Button from "@components/ui/Button"; import Button from "@components/ui/Button";
export default function Teams() { export default function Teams() {
const { t } = useLocale();
const noop = () => undefined; const noop = () => undefined;
const [, loading] = useSession(); const [, loading] = useSession();
const [teams, setTeams] = useState([]); const [teams, setTeams] = useState([]);
@ -80,7 +82,7 @@ export default function Teams() {
}; };
return ( return (
<Shell heading="Teams" subtitle="Create and manage teams to use collaborative features."> <Shell heading={t("teams")} subtitle={t("create_manage_teams_collaborative")}>
<SettingsShell> <SettingsShell>
{!editTeamEnabled && ( {!editTeamEnabled && (
<div className="divide-y divide-gray-200 lg:col-span-9"> <div className="divide-y divide-gray-200 lg:col-span-9">
@ -91,10 +93,10 @@ export default function Teams() {
<div className="sm:rounded-sm"> <div className="sm:rounded-sm">
<div className="pb-5 pr-4 sm:pb-6"> <div className="pb-5 pr-4 sm:pb-6">
<h3 className="text-lg font-medium leading-6 text-gray-900"> <h3 className="text-lg font-medium leading-6 text-gray-900">
Create a team to get started {t("create_team_to_get_started")}
</h3> </h3>
<div className="max-w-xl mt-2 text-sm text-gray-500"> <div className="max-w-xl mt-2 text-sm text-gray-500">
<p>Create your first team and invite other users to work together with you.</p> <p>{t("create_first_team_and_invite_others")}</p>
</div> </div>
</div> </div>
</div> </div>
@ -106,7 +108,7 @@ export default function Teams() {
onClick={() => setShowCreateTeamModal(true)} onClick={() => setShowCreateTeamModal(true)}
className="btn btn-white"> className="btn btn-white">
<PlusIcon className="group-hover:text-black text-gray-700 w-3.5 h-3.5 mr-2 inline-block" /> <PlusIcon className="group-hover:text-black text-gray-700 w-3.5 h-3.5 mr-2 inline-block" />
New Team {t("new_team")}
</Button> </Button>
</div> </div>
</div> </div>
@ -156,17 +158,17 @@ export default function Teams() {
</div> </div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title"> <h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
Create a new team {t("create_new_team")}
</h3> </h3>
<div> <div>
<p className="text-sm text-gray-400">Create a new team to collaborate with users.</p> <p className="text-sm text-gray-400">{t("create_new_team_description")}</p>
</div> </div>
</div> </div>
</div> </div>
<form onSubmit={createTeam}> <form onSubmit={createTeam}>
<div className="mb-4"> <div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700"> <label htmlFor="name" className="block text-sm font-medium text-gray-700">
Name {t("name")}
</label> </label>
<input <input
ref={nameRef} ref={nameRef}
@ -180,13 +182,13 @@ export default function Teams() {
</div> </div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> <div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button type="submit" className="btn btn-primary"> <button type="submit" className="btn btn-primary">
Create team {t("create_team")}
</button> </button>
<button <button
onClick={() => setShowCreateTeamModal(false)} onClick={() => setShowCreateTeamModal(false)}
type="button" type="button"
className="mr-2 btn btn-white"> className="mr-2 btn btn-white">
Cancel {t("cancel")}
</button> </button>
</div> </div>
</form> </form>

View file

@ -1,4 +1,34 @@
{ {
"light": "Light",
"dark": "Dark",
"automatically_adjust_theme": "Automatically adjust theme based on invitee preferences",
"email": "Email",
"full_name": "Full name",
"browse_api_documentation": "Browse our API documentation",
"leverage_our_api": "Leverage our API for full control and customizability.",
"create_webhook": "Create Webhook",
"booking_cancelled": "Booking Cancelled",
"booking_rescheduled": "Booking Rescheduled",
"booking_created": "Booking Created",
"event_triggers": "Event triggers",
"subscriber_url": "Subscriber Url",
"create_new_webhook": "Create a new webhook",
"create_new_webhook_to_account": "Create a new webhook to your account",
"new_webhook": "New Webhook",
"receive_cal_meeting_data": "Receive Cal meeting data at a specified URL, in real-time, when an event is scheduled or cancelled.",
"responsive_fullscreen_iframe": "Responsive full screen iframe",
"loading": "Loading...",
"standard_iframe": "Standard iframe",
"iframe_embed": "iframe Embed",
"embed_calcom": "The easiest way to embed Cal.com on your website.",
"integrate_using_embed_or_webhooks": "Integrate with your website using our embed options, or get real-time booking information using custom webhooks.",
"schedule_a_meeting": "Schedule a meeting",
"view_and_manage_billing_details": "View and manage your billing details",
"view_and_edit_billing_details": "View and edit your billing details, as well as cancel your subscription.",
"go_to_billing_portal": "Go to the billing portal",
"need_anything_else": "Need anything else?",
"further_billing_help": "If you need any further help with billing, our support team are here to help.",
"contact_our_support_team": "Contact our support team",
"uh_oh": "Uh oh!", "uh_oh": "Uh oh!",
"no_event_types_have_been_setup": "This user hasn't set up any event types yet.", "no_event_types_have_been_setup": "This user hasn't set up any event types yet.",
"edit_logo": "Edit logo", "edit_logo": "Edit logo",
@ -147,26 +177,25 @@
"new_event_description": "Create a new event type for people to book times with.", "new_event_description": "Create a new event type for people to book times with.",
"event_type_created_successfully": "{{eventTypeTitle}} event type created successfully", "event_type_created_successfully": "{{eventTypeTitle}} event type created successfully",
"hours": "Hours", "hours": "Hours",
"full_name": "Full Name",
"your_email": "Your Email", "your_email": "Your Email",
"change_avatar": "Change Avatar", "change_avatar": "Change Avatar",
"language": "Language", "language": "Language",
"timezone": "Timezone", "timezone": "Timezone",
"first_day_week": "First day week", "first_day_of_week": "First Day of Week",
"single_theme": "Single Theme", "single_theme": "Single Theme",
"file_not_named": "File is not named [idOrSlug]/[user]", "file_not_named": "File is not named [idOrSlug]/[user]",
"create_team": "Create Team", "create_team": "Create Team",
"name_team": "Name", "name": "Name",
"create_new_team_description": "Create a new team to collaborate with users.", "create_new_team_description": "Create a new team to collaborate with users.",
"create_new_team": "Create a new team", "create_new_team": "Create a new team",
"open_invitations": "Open Invitations", "open_invitations": "Open Invitations",
"new_team": "New Team", "new_team": "New Team",
"create_first_invite_other_users": "Create your first team and invite other users to work together with you.", "create_first_team_and_invite_others": "Create your first team and invite other users to work together with you.",
"create_team_get_started": "Create a team to get started", "create_team_to_get_started": "Create a team to get started",
"teams": "Teams", "teams": "Teams",
"create_manage_teams_collaborative": "Create and manage teams to use collaborative features.", "create_manage_teams_collaborative": "Create and manage teams to use collaborative features.",
"this_feature_only_available_paid_plan": "This feature is only available in paid plan", "only_available_on_pro_plan": "This feature is only available in Pro plan",
"order_remove_cal_branding_description": "In order to remove the Cal branding from your booking pages, you need to upgrade to a paid account.", "remove_cal_branding_description": "In order to remove the Cal branding from your booking pages, you need to upgrade to a Pro account.",
"to_upgrade_go_to": "To upgrade go to", "to_upgrade_go_to": "To upgrade go to",
"edit_profile_info_description": "Edit your profile information, which shows on your scheduling link.", "edit_profile_info_description": "Edit your profile information, which shows on your scheduling link.",
"change_email_contact": "To change your email, please contact", "change_email_contact": "To change your email, please contact",
@ -174,9 +203,10 @@
"profile_updated_successfully": "Profile updated successfully", "profile_updated_successfully": "Profile updated successfully",
"your_user_profile_updated_successfully": "Your user profile has been updated successfully.", "your_user_profile_updated_successfully": "Your user profile has been updated successfully.",
"user_cannot_found_db": "User seems logged in but cannot be found in the db", "user_cannot_found_db": "User seems logged in but cannot be found in the db",
"embed_and_webhooks": "Embed &amp; Webhooks", "embed_and_webhooks": "Embed & Webhooks",
"enabled": "Enabled", "enabled": "Enabled",
"disabled": "Disabled", "disabled": "Disabled",
"disable": "Disable",
"billing": "Billing", "billing": "Billing",
"manage_your_billing_info": "Manage your billing information and cancel your subscription.", "manage_your_billing_info": "Manage your billing information and cancel your subscription.",
"availability": "Availability", "availability": "Availability",