From 6787211635388d80a5747f68f6c7c8ca4e8c5dd9 Mon Sep 17 00:00:00 2001 From: Codemod Bot Date: Mon, 20 Jan 2025 19:13:24 +0000 Subject: [PATCH] feat: Apply i18n codemods --- apps/web/app/[domain]/not-found/page.tsx | 11 +- .../(dashboard)/components/user-info.tsx | 18 +- apps/web/app/api/og/analytics/route.tsx | 9 +- .../auth/reset-password/[token]/page.tsx | 15 +- .../app/app.dub.co/(auth)/auth/saml/page.tsx | 7 +- apps/web/app/app.dub.co/(auth)/login/page.tsx | 22 +- .../(auth)/oauth/authorize/authorize-form.tsx | 11 +- .../[programId]/partners/partner-stats.tsx | 13 +- .../settings/billing/invoices/page-client.tsx | 23 +- .../settings/domains/default-domains.tsx | 28 +- .../[integrationSlug]/manage/page.tsx | 7 +- .../settings/library/utm/template-card.tsx | 9 +- .../settings/oauth-apps/new/page-client.tsx | 7 +- .../[slug]/settings/security/page-client.tsx | 45 +- .../account/settings/page-client.tsx | 11 +- .../settings/referrals/page-client.tsx | 9 +- .../account/settings/security/page-client.tsx | 5 +- .../security/request-set-password.tsx | 14 +- .../settings/security/update-password.tsx | 19 +- .../onboarding/(steps)/exit-button.tsx | 5 +- .../onboarding/(steps)/step-page.tsx | 5 +- .../onboarding/(steps)/workspace/page.tsx | 7 +- .../(onboarding)/onboarding/next-button.tsx | 5 +- .../app.dub.co/embed/inline/page-client.tsx | 9 +- .../app/app.dub.co/embed/inline/payouts.tsx | 5 +- apps/web/app/messages | 390 ++++++++++++++++++ .../(apply)/[programSlug]/apply/form.tsx | 40 +- .../apply/success/screenshot.tsx | 53 +-- .../(custom)/[programSlug]/login/page.tsx | 26 +- .../(auth)/(default)/layout.tsx | 14 +- .../settings/payouts/payout-details-sheet.tsx | 11 +- apps/web/emails/api-key-created.tsx | 46 ++- apps/web/emails/failed-payment.tsx | 48 ++- apps/web/emails/feedback-email.tsx | 13 +- apps/web/emails/new-sale-created.tsx | 49 ++- apps/web/emails/webhook-failed.tsx | 23 +- apps/web/ui/account/update-subscription.tsx | 7 +- apps/web/ui/account/user-id.tsx | 7 +- .../web/ui/analytics/events/export-button.tsx | 7 +- .../web/ui/analytics/events/filter-button.tsx | 5 +- apps/web/ui/analytics/export-button.tsx | 5 +- apps/web/ui/analytics/link-preview.tsx | 7 +- apps/web/ui/analytics/locations.tsx | 5 +- apps/web/ui/analytics/top-links.tsx | 5 +- apps/web/ui/auth/login/email-sign-in.tsx | 13 +- apps/web/ui/auth/login/github-button.tsx | 5 +- apps/web/ui/auth/register/signup-oauth.tsx | 7 +- apps/web/ui/domains/domain-card.tsx | 51 ++- apps/web/ui/layout/changelog-popup.tsx | 5 +- apps/web/ui/layout/sidebar/payout-stats.tsx | 11 +- apps/web/ui/links/link-not-found.tsx | 10 +- .../ui/modals/add-edit-utm-template.modal.tsx | 13 +- apps/web/ui/modals/archive-domain-modal.tsx | 11 +- apps/web/ui/modals/delete-link-modal.tsx | 21 +- apps/web/ui/modals/import-bitly-modal.tsx | 33 +- .../ui/modals/link-builder/draft-controls.tsx | 5 +- .../ui/modals/link-builder/password-modal.tsx | 26 +- .../link-builder/utm-templates-button.tsx | 11 +- apps/web/ui/modals/link-qr-modal.tsx | 48 ++- apps/web/ui/modals/remove-teammate-modal.tsx | 40 +- apps/web/ui/modals/token-created-modal.tsx | 17 +- .../oauth-apps/add-edit-integration-form.tsx | 35 +- .../ui/oauth-apps/oauth-app-credentials.tsx | 14 +- apps/web/ui/shared/pro-badge-tooltip.tsx | 5 +- apps/web/ui/webhooks/webhook-events.tsx | 9 +- apps/web/ui/webhooks/webhook-header.tsx | 13 +- .../workspaces/conversion-tracking-toggle.tsx | 12 +- .../ui/workspaces/create-workspace-form.tsx | 17 +- .../ui/workspaces/invite-teammates-form.tsx | 32 +- apps/web/ui/workspaces/upload-logo.tsx | 14 +- packages/stripe-app/src/views/AppSettings.tsx | 116 +++--- packages/stripe-app/src/views/locales/en.json | 6 + 72 files changed, 1228 insertions(+), 452 deletions(-) create mode 100644 apps/web/app/messages create mode 100644 packages/stripe-app/src/views/locales/en.json diff --git a/apps/web/app/[domain]/not-found/page.tsx b/apps/web/app/[domain]/not-found/page.tsx index 1ad903513f..01ce247a45 100644 --- a/apps/web/app/[domain]/not-found/page.tsx +++ b/apps/web/app/[domain]/not-found/page.tsx @@ -1,5 +1,6 @@ import { GlobeSearch } from "@dub/ui"; import { constructMetadata } from "@dub/utils"; +import { getTranslations } from "next-intl/server"; export const runtime = "edge"; @@ -12,20 +13,20 @@ export const metadata = constructMetadata({ }); export default async function NotFoundLinkPage() { + const t = await getTranslations("[domain]/not-found"); + return (
-

Link Not Found

-

- This link does not exist. Please check the URL and try again. -

+

{t("link-not-found")}

+

{t("link-not-exist-message")}

- Create Your Free Branded Link + {t("create-free-branded-link")}
); diff --git a/apps/web/app/admin.dub.co/(dashboard)/components/user-info.tsx b/apps/web/app/admin.dub.co/(dashboard)/components/user-info.tsx index 5c6a65865d..ed7b499422 100644 --- a/apps/web/app/admin.dub.co/(dashboard)/components/user-info.tsx +++ b/apps/web/app/admin.dub.co/(dashboard)/components/user-info.tsx @@ -1,4 +1,6 @@ "use client"; +import { useTranslations } from "next-intl"; + import { Badge, Copy, Globe2, Tick, useCopyToClipboard } from "@dub/ui"; import { capitalize, nFormatter } from "@dub/utils"; import { toast } from "sonner"; @@ -22,15 +24,17 @@ export interface UserInfoProps { } export default function UserInfo({ data }: { data: UserInfoProps }) { + const t = useTranslations("admin.dub.co/(dashboard)/components"); + return (
{Object.keys(data.defaultDomainLinks).length > 0 && ( @@ -57,27 +61,27 @@ export default function UserInfo({ data }: { data: UserInfoProps }) { {workspace.slug}
- ID + {t("id")} {workspace.id}
- Plan + {t("plan")} {capitalize(workspace.plan)}
- Domains + {t("domains")} {workspace.domains}
- Links + {t("links")} {nFormatter(workspace.links, { full: true })}
- Clicks + {t("clicks")} {nFormatter(workspace.clicks, { full: true })} diff --git a/apps/web/app/api/og/analytics/route.tsx b/apps/web/app/api/og/analytics/route.tsx index 6d80ac9a1d..8e01efc464 100644 --- a/apps/web/app/api/og/analytics/route.tsx +++ b/apps/web/app/api/og/analytics/route.tsx @@ -6,12 +6,15 @@ import { linkConstructor, nFormatter, } from "@dub/utils"; +import { getTranslations } from "next-intl/server"; import { ImageResponse } from "next/og"; import { NextRequest } from "next/server"; export const runtime = "edge"; export async function GET(req: NextRequest) { + const t = await getTranslations("api/og/analytics"); + const interMedium = await fetch( new URL("@/styles/Inter-Medium.ttf", import.meta.url), ).then((res) => res.arrayBuffer()); @@ -45,7 +48,7 @@ export async function GET(req: NextRequest) { favicon

{linkConstructor({ domain, key, pretty: true })} @@ -81,7 +84,7 @@ export async function GET(req: NextRequest) { -

Last 24 hours

+

{t("last-24-hours")}

@@ -105,7 +108,7 @@ export async function GET(req: NextRequest) {

- Total Clicks + {t("total-clicks")}

diff --git a/apps/web/app/app.dub.co/(auth)/auth/reset-password/[token]/page.tsx b/apps/web/app/app.dub.co/(auth)/auth/reset-password/[token]/page.tsx index a6e9c32ae1..ce7c616321 100644 --- a/apps/web/app/app.dub.co/(auth)/auth/reset-password/[token]/page.tsx +++ b/apps/web/app/app.dub.co/(auth)/auth/reset-password/[token]/page.tsx @@ -2,6 +2,7 @@ import { ResetPasswordForm } from "@/ui/auth/reset-password-form"; import EmptyState from "@/ui/shared/empty-state"; import { prisma } from "@dub/prisma"; import { InputPassword } from "@dub/ui"; +import { getTranslations } from "next-intl/server"; export const runtime = "nodejs"; @@ -12,14 +13,18 @@ interface Props { } export default async function ResetPasswordPage({ params: { token } }: Props) { + const t = await getTranslations( + "app.dub.co/(auth)/auth/reset-password/[token]", + ); + const validToken = await isValidToken(token); if (!validToken) { return ( ); } @@ -28,10 +33,8 @@ export default async function ResetPasswordPage({ params: { token } }: Props) {
-

Reset your password

-

- Enter new password for your account. -

+

{t("reset-password")}

+

{t("enter-new-password")}

diff --git a/apps/web/app/app.dub.co/(auth)/auth/saml/page.tsx b/apps/web/app/app.dub.co/(auth)/auth/saml/page.tsx index 7eff4b839b..3fad78c5d8 100644 --- a/apps/web/app/app.dub.co/(auth)/auth/saml/page.tsx +++ b/apps/web/app/app.dub.co/(auth)/auth/saml/page.tsx @@ -1,16 +1,19 @@ import EmptyState from "@/ui/shared/empty-state"; import { LoadingSpinner } from "@dub/ui"; import { APP_NAME } from "@dub/utils"; +import { useTranslations } from "next-intl"; import { Suspense } from "react"; import SAMLIDPForm from "./form"; export default function SAMLPage() { + const t = useTranslations("app.dub.co/(auth)/auth/saml"); + return ( <> diff --git a/apps/web/app/app.dub.co/(auth)/login/page.tsx b/apps/web/app/app.dub.co/(auth)/login/page.tsx index dc9eeef84c..76b037d139 100644 --- a/apps/web/app/app.dub.co/(auth)/login/page.tsx +++ b/apps/web/app/app.dub.co/(auth)/login/page.tsx @@ -1,6 +1,7 @@ import LoginForm from "@/ui/auth/login/login-form"; import { AuthLayout } from "@/ui/layout/auth-layout"; import { APP_DOMAIN, constructMetadata } from "@dub/utils"; +import { useTranslations } from "next-intl"; import Link from "next/link"; export const metadata = constructMetadata({ @@ -9,24 +10,29 @@ export const metadata = constructMetadata({ }); export default function LoginPage() { + const t = useTranslations("app.dub.co/(auth)/login"); + return (
-

Sign in to your Dub account

+

{t("sign-in-dub-account")}

- Don't have an account?  - - Sign up - + {t("dont-have-account-sign-up-link", { + component0: ( + + {t("dont-have-account-sign-up-link_component0")} + + ), + })}

); diff --git a/apps/web/app/app.dub.co/(auth)/oauth/authorize/authorize-form.tsx b/apps/web/app/app.dub.co/(auth)/oauth/authorize/authorize-form.tsx index 7a57f6d22e..fdbf10d80e 100644 --- a/apps/web/app/app.dub.co/(auth)/oauth/authorize/authorize-form.tsx +++ b/apps/web/app/app.dub.co/(auth)/oauth/authorize/authorize-form.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import { consolidateScopes, getScopesForRole } from "@/lib/api/tokens/scopes"; import useWorkspaces from "@/lib/swr/use-workspaces"; @@ -25,6 +26,8 @@ export const AuthorizeForm = ({ code_challenge, code_challenge_method, }: AuthorizeFormProps) => { + const t = useTranslations("app.dub.co/(auth)/oauth/authorize"); + const { data: session } = useSession(); const { workspaces } = useWorkspaces(); const { AddWorkspaceModal, setShowAddWorkspaceModal } = @@ -141,7 +144,7 @@ export const AuthorizeForm = ({ /> )}

- Select a workspace to grant API access to + {t("select-workspace-api-access")}

} - text="Create new workspace" + text={t("create-new-workspace")} variant="outline" onClick={() => setShowAddWorkspaceModal(true)} className="justify-start text-gray-700" @@ -163,14 +166,14 @@ export const AuthorizeForm = ({
@@ -74,8 +79,8 @@ export default function WorkspaceInvoicesClient() { )) ) : ( ( <> @@ -98,6 +103,10 @@ export default function WorkspaceInvoicesClient() { } const InvoiceCard = ({ invoice }: { invoice: InvoiceProps }) => { + const t = useTranslations( + "app.dub.co/(dashboard)/[slug]/settings/billing/invoices", + ); + return (
@@ -112,7 +121,7 @@ const InvoiceCard = ({ invoice }: { invoice: InvoiceProps }) => {
-
Total
+
{t("total-label")}
{currencyFormatter(invoice.total / 100, { @@ -145,14 +154,14 @@ const InvoiceCard = ({ invoice }: { invoice: InvoiceProps }) => { "flex size-8 items-center justify-center rounded-md border text-sm sm:size-auto sm:h-9 sm:px-3", )} > -

View invoice

+

{t("view-invoice-button")}

) : ( @@ -126,18 +130,18 @@ const SAMLSection = () => { }} className="rounded-md px-1 py-2 transition-all duration-75 hover:bg-gray-100 active:bg-gray-200" > - Edit + {t("edit-button")} ) : (
@@ -166,6 +170,8 @@ const SAMLSection = () => { }; const SCIMSection = () => { + const t = useTranslations("app.dub.co/(dashboard)/[slug]/settings/security"); + const { plan } = useWorkspace(); const { SCIMModal, setShowSCIMModal } = useSCIMModal(); const { RemoveSCIMModal, setShowRemoveSCIMModal } = useRemoveSCIMModal(); @@ -184,7 +190,7 @@ const SCIMSection = () => { logo: ( p.scim === provider)!.logo} - alt={`${provider} logo`} + alt={t("provider-logo-template", { provider: provider })} className="h-8 w-8" /> ), @@ -212,10 +218,9 @@ const SCIMSection = () => {
-

Directory Sync

+

{t("directory-sync")}

- Automatically provision and deprovision users from your identity - provider. + {t("directory-sync-description")}

@@ -252,7 +257,7 @@ const SCIMSection = () => { className="rounded-md p-2 text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100" > } /> @@ -264,7 +269,7 @@ const SCIMSection = () => { className="rounded-md p-2 text-left text-sm font-medium text-red-600 transition-all duration-75 hover:bg-red-600 hover:text-white" > } /> @@ -282,18 +287,20 @@ const SCIMSection = () => { }} className="rounded-md px-1 py-2 transition-all duration-75 hover:bg-gray-100 active:bg-gray-200" > - Edit + + {t("edit-button-duplicate")} + ) : (
diff --git a/apps/web/app/app.dub.co/(dashboard)/account/settings/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/account/settings/page-client.tsx index f444f61eca..1832bcbd79 100644 --- a/apps/web/app/app.dub.co/(dashboard)/account/settings/page-client.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/account/settings/page-client.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import DeleteAccountSection from "@/ui/account/delete-account"; import UpdateDefaultWorkspace from "@/ui/account/update-default-workspace"; @@ -11,13 +12,15 @@ import { useSession } from "next-auth/react"; import { toast } from "sonner"; export default function SettingsPageClient() { + const t = useTranslations("app.dub.co/(dashboard)/account/settings"); + const { data: session, update, status } = useSession(); return ( <>
diff --git a/apps/web/app/app.dub.co/(dashboard)/account/settings/security/page-client.tsx b/apps/web/app/app.dub.co/(dashboard)/account/settings/security/page-client.tsx index 2aed73ad83..4438013ba1 100644 --- a/apps/web/app/app.dub.co/(dashboard)/account/settings/security/page-client.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/account/settings/security/page-client.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import useUser from "@/lib/swr/use-user"; import { RequestSetPassword } from "./request-set-password"; @@ -7,13 +8,15 @@ import { UpdatePassword } from "./update-password"; export const dynamic = "force-dynamic"; export default function SecurityPageClient() { + const t = useTranslations("app.dub.co/(dashboard)/account/settings/security"); + const { loading, user } = useUser(); if (loading) { return (
-

Password

+

{t("password")}

diff --git a/apps/web/app/app.dub.co/(dashboard)/account/settings/security/request-set-password.tsx b/apps/web/app/app.dub.co/(dashboard)/account/settings/security/request-set-password.tsx index 804a810214..0b3f16b398 100644 --- a/apps/web/app/app.dub.co/(dashboard)/account/settings/security/request-set-password.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/account/settings/security/request-set-password.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import useUser from "@/lib/swr/use-user"; import { Button } from "@dub/ui"; @@ -7,6 +8,8 @@ import { toast } from "sonner"; // Displayed when the user doesn't have a password set for their account export const RequestSetPassword = () => { + const t = useTranslations("app.dub.co/(dashboard)/account/settings/security"); + const { user } = useUser(); const [sending, setSending] = useState(false); @@ -38,20 +41,21 @@ export const RequestSetPassword = () => { return (
-

Password

+

{t("password-label")}

{user?.provider && ( <> - Your account is managed by{" "} - {user?.provider}.{" "} + {t("account-provider-info", { + component0: {user?.provider}, + })} )} - You can set a password to use with your Dub account. + {t("password-description")}

); diff --git a/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/step-page.tsx b/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/step-page.tsx index 1c4ad6032b..2e6507931a 100644 --- a/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/step-page.tsx +++ b/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/step-page.tsx @@ -1,6 +1,7 @@ import { Icon } from "@dub/ui"; import { cn } from "@dub/utils"; import { Crown } from "lucide-react"; +import { useTranslations } from "next-intl"; import { PropsWithChildren, ReactNode } from "react"; export function StepPage({ @@ -17,6 +18,8 @@ export function StepPage({ paidPlanRequired?: boolean; className?: string; }>) { + const t = useTranslations("app.dub.co/(onboarding)/onboarding/(steps)"); + return (
- Paid plan required + {t("paid-plan-required")}
)}

diff --git a/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/workspace/page.tsx b/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/workspace/page.tsx index 4388fabf00..9b9384297e 100644 --- a/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/workspace/page.tsx +++ b/apps/web/app/app.dub.co/(onboarding)/onboarding/(steps)/workspace/page.tsx @@ -1,8 +1,13 @@ import { GridPlus } from "@dub/ui/icons"; +import { useTranslations } from "next-intl"; import { StepPage } from "../step-page"; import { Form } from "./form"; export default function Workspace() { + const t = useTranslations( + "app.dub.co/(onboarding)/onboarding/(steps)/workspace", + ); + return ( - What is a workspace? + {t("what-is-a-workspace")} } > diff --git a/apps/web/app/app.dub.co/(onboarding)/onboarding/next-button.tsx b/apps/web/app/app.dub.co/(onboarding)/onboarding/next-button.tsx index 77d4867ada..5c77a798a6 100644 --- a/apps/web/app/app.dub.co/(onboarding)/onboarding/next-button.tsx +++ b/apps/web/app/app.dub.co/(onboarding)/onboarding/next-button.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import { OnboardingStep } from "@/lib/onboarding/types"; import { Button, ButtonProps } from "@dub/ui"; @@ -8,12 +9,14 @@ export function NextButton({ step, ...rest }: { step: OnboardingStep } & ButtonProps) { + const t = useTranslations("app.dub.co/(onboarding)/onboarding"); + const { continueTo, isLoading, isSuccessful } = useOnboardingProgress(); return (

- Sign in to Framer Partners + {t("sign-in-framer-partners")}

- Not a Framer Partner?  - - Apply today - + {t("not-a-framer-partner-apply-today", { + component0: ( + + {t("not-a-framer-partner-apply-today_component0")} + + ), + })}

diff --git a/apps/web/app/partners.dub.co/(auth)/(default)/layout.tsx b/apps/web/app/partners.dub.co/(auth)/(default)/layout.tsx index 04ae4865af..312e48657c 100644 --- a/apps/web/app/partners.dub.co/(auth)/(default)/layout.tsx +++ b/apps/web/app/partners.dub.co/(auth)/(default)/layout.tsx @@ -1,10 +1,13 @@ import { Grid, Wordmark } from "@dub/ui"; +import { useTranslations } from "next-intl"; export default function PartnerAuthLayout({ children, }: { children: React.ReactNode; }) { + const t = useTranslations("partners.dub.co/(auth)/(default)"); + return (
@@ -14,14 +17,17 @@ export default function PartnerAuthLayout({
- Partner + + {t("partner-label")} +
{children}

- © {new Date().getFullYear()} Dub Technologies, Inc. + © {new Date().getFullYear()} + {t("dub-technologies-inc")}

- Privacy Policy + {t("privacy-policy")} - Terms of Service + {t("terms-of-service")}
- Payout details + {t("payout-details")}
); } diff --git a/apps/web/ui/account/user-id.tsx b/apps/web/ui/account/user-id.tsx index 9ca30c226f..34a868089e 100644 --- a/apps/web/ui/account/user-id.tsx +++ b/apps/web/ui/account/user-id.tsx @@ -1,9 +1,12 @@ "use client"; +import { useTranslations } from "next-intl"; import { CopyButton } from "@dub/ui"; import { useSession } from "next-auth/react"; export default function UserId() { + const t = useTranslations("../ui/account"); + const { data: session } = useSession() as | { data: { user: { id: string } }; @@ -15,9 +18,9 @@ export default function UserId() {
-

Your User ID

+

{t("user-id-label")}

- This is your unique account identifier on Dub. + {t("account-identifier-description")}

{session?.user?.id ? ( diff --git a/apps/web/ui/analytics/events/export-button.tsx b/apps/web/ui/analytics/events/export-button.tsx index f1830490b4..9d3877f041 100644 --- a/apps/web/ui/analytics/events/export-button.tsx +++ b/apps/web/ui/analytics/events/export-button.tsx @@ -1,10 +1,13 @@ import useWorkspace from "@/lib/swr/use-workspace"; import { Button, Download, TooltipContent } from "@dub/ui"; +import { useTranslations } from "next-intl"; import { useContext } from "react"; import { toast } from "sonner"; import { EventsContext } from "./events-provider"; export default function ExportButton({ onClick }: { onClick?: () => void }) { + const t = useTranslations("../ui/analytics/events"); + const { exportQueryString } = useContext(EventsContext); const { slug, plan } = useWorkspace(); @@ -35,11 +38,11 @@ export default function ExportButton({ onClick }: { onClick?: () => void }) { variant="outline" icon={} className="h-9 justify-start px-2 text-black" - text="Download as CSV" + text={t("download-as-csv")} disabledTooltip={ needsHigherPlan && ( diff --git a/apps/web/ui/analytics/events/filter-button.tsx b/apps/web/ui/analytics/events/filter-button.tsx index 8401793169..20e1bdde98 100644 --- a/apps/web/ui/analytics/events/filter-button.tsx +++ b/apps/web/ui/analytics/events/filter-button.tsx @@ -1,8 +1,11 @@ import { useRouterStuff } from "@dub/ui"; import { FilterBars } from "@dub/ui/icons"; +import { useTranslations } from "next-intl"; import Link from "next/link"; export default function FilterButton({ set }: { set: Record }) { + const t = useTranslations("../ui/analytics/events"); + const { queryParams } = useRouterStuff(); return ( @@ -17,7 +20,7 @@ export default function FilterButton({ set }: { set: Record }) { } className="block rounded-md border border-transparent bg-white p-0.5 text-gray-600 transition-colors hover:border-gray-200 hover:bg-gray-100 hover:text-gray-950" > - Filter + {t("filter")}
diff --git a/apps/web/ui/analytics/export-button.tsx b/apps/web/ui/analytics/export-button.tsx index 24d1cca62b..705025d584 100644 --- a/apps/web/ui/analytics/export-button.tsx +++ b/apps/web/ui/analytics/export-button.tsx @@ -1,5 +1,6 @@ import { Button } from "@dub/ui"; import { Download } from "@dub/ui/icons"; +import { useTranslations } from "next-intl"; import { Dispatch, SetStateAction, useContext, useState } from "react"; import { toast } from "sonner"; import { AnalyticsContext } from "./analytics-provider"; @@ -9,6 +10,8 @@ export default function ExportButton({ }: { setOpenPopover: Dispatch>; }) { + const t = useTranslations("../ui/analytics"); + const [loading, setLoading] = useState(false); const { queryString } = useContext(AnalyticsContext); @@ -41,7 +44,7 @@ export default function ExportButton({ return (
diff --git a/apps/web/ui/analytics/locations.tsx b/apps/web/ui/analytics/locations.tsx index df8381a001..c1cfaac232 100644 --- a/apps/web/ui/analytics/locations.tsx +++ b/apps/web/ui/analytics/locations.tsx @@ -7,6 +7,7 @@ import { OfficeBuilding, } from "@dub/ui/icons"; import { CONTINENTS, COUNTRIES, REGIONS } from "@dub/utils"; +import { useTranslations } from "next-intl"; import { useContext, useState } from "react"; import { AnalyticsCard } from "./analytics-card"; import { AnalyticsLoadingSpinner } from "./analytics-loading-spinner"; @@ -16,6 +17,8 @@ import ContinentIcon from "./continent-icon"; import { useAnalyticsFilterOption } from "./utils"; export default function Locations() { + const t = useTranslations("../ui/analytics"); + const { queryParams, searchParams } = useRouterStuff(); const { selectedTab, saleUnit } = useContext(AnalyticsContext); @@ -93,7 +96,7 @@ export default function Locations() { /> ) : (
-

No data available

+

{t("no-data-available")}

) ) : ( diff --git a/apps/web/ui/analytics/top-links.tsx b/apps/web/ui/analytics/top-links.tsx index 1f2c4f362c..c7c9f01260 100644 --- a/apps/web/ui/analytics/top-links.tsx +++ b/apps/web/ui/analytics/top-links.tsx @@ -1,6 +1,7 @@ import { LinkLogo, useRouterStuff } from "@dub/ui"; import { Globe, Hyperlink } from "@dub/ui/icons"; import { getApexDomain } from "@dub/utils"; +import { useTranslations } from "next-intl"; import { useContext, useState } from "react"; import { AnalyticsCard } from "./analytics-card"; import { AnalyticsLoadingSpinner } from "./analytics-loading-spinner"; @@ -9,6 +10,8 @@ import BarList from "./bar-list"; import { useAnalyticsFilterOption } from "./utils"; export default function TopLinks() { + const t = useTranslations("../ui/analytics"); + const { queryParams, searchParams } = useRouterStuff(); const { selectedTab, saleUnit } = useContext(AnalyticsContext); @@ -80,7 +83,7 @@ export default function TopLinks() { /> ) : (
-

No data available

+

{t("no-data-available")}

) ) : ( diff --git a/apps/web/ui/auth/login/email-sign-in.tsx b/apps/web/ui/auth/login/email-sign-in.tsx index d8709445ea..dafd5436fb 100644 --- a/apps/web/ui/auth/login/email-sign-in.tsx +++ b/apps/web/ui/auth/login/email-sign-in.tsx @@ -4,6 +4,7 @@ import { InputPassword } from "@dub/ui/icons"; import { cn } from "@dub/utils"; import { Mail } from "lucide-react"; import { signIn } from "next-auth/react"; +import { useTranslations } from "next-intl"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { useContext, useState } from "react"; @@ -11,6 +12,8 @@ import { toast } from "sonner"; import { errorCodes, LoginFormContext } from "./login-form"; export const EmailSignIn = ({ redirectTo }: { redirectTo?: string }) => { + const t = useTranslations("../ui/auth/login"); + const router = useRouter(); const searchParams = useSearchParams(); const next = searchParams?.get("next"); @@ -130,7 +133,7 @@ export const EmailSignIn = ({ redirectTo }: { redirectTo?: string }) => { name="email" autoFocus={!isMobile && !showPasswordField} type="email" - placeholder="panic@thedis.co" + placeholder={t("email-address")} autoComplete="email" required value={email} @@ -151,14 +154,16 @@ export const EmailSignIn = ({ redirectTo }: { redirectTo?: string }) => { type="password" autoFocus={!isMobile} value={password} - placeholder="Password (optional)" + placeholder={t("password-optional")} onChange={(e) => setPassword(e.target.value)} />
)}
)} @@ -136,7 +139,9 @@ export default function DomainCard({ props }: { props: DomainProps }) {

{nFormatter(props.link?.clicks || 0)} - clicks + + {t("clicks")} +

@@ -229,15 +234,17 @@ export default function DomainCard({ props }: { props: DomainProps }) {
- Good news! Your DNS records are set up correctly, but it can - take some time for them to propagate globally.{" "} - - Learn more. - + {t("dns-setup-success", { + component0: ( + + {t("dns-setup-success_component0")} + + ), + })}
) : ( @@ -267,6 +274,8 @@ function Menu({ }; groupHover: boolean; }) { + const t = useTranslations("../ui/domains"); + const { primary, archived, slug: domain, registeredDomain } = props; const isDubProvisioned = !!registeredDomain; @@ -371,10 +380,10 @@ function Menu({

- Link Settings + {t("link-settings")}

@@ -120,7 +123,11 @@ function ArchiveDomainModal({ onClick={handleArchiveRequest} autoFocus loading={archiving} - text={`Confirm ${props.archived ? "unarchive" : "archive"}`} + text={t("confirm-archive-unarchive", { + propsArchivedUnarchiveArchive: props.archived + ? "unarchive" + : "archive", + })} />
diff --git a/apps/web/ui/modals/delete-link-modal.tsx b/apps/web/ui/modals/delete-link-modal.tsx index 656eb0a4ac..9abffc0441 100644 --- a/apps/web/ui/modals/delete-link-modal.tsx +++ b/apps/web/ui/modals/delete-link-modal.tsx @@ -3,6 +3,7 @@ import useWorkspace from "@/lib/swr/use-workspace"; import { LinkProps } from "@/lib/types"; import { Button, LinkLogo, Modal, useMediaQuery } from "@dub/ui"; import { getApexDomain, linkConstructor } from "@dub/utils"; +import { useTranslations } from "next-intl"; import { Dispatch, SetStateAction, @@ -33,6 +34,8 @@ function DeleteLinkModalInner({ setShowDeleteLinkModal, props, }: DeleteLinkModalProps) { + const t = useTranslations("../ui/modals"); + const { id } = useWorkspace(); const [deleting, setDeleting] = useState(false); const apexDomain = getApexDomain(props.url); @@ -53,10 +56,11 @@ function DeleteLinkModalInner({ <>
-

Delete {shortlink}

+

+ {t("delete-shortlink", { shortlink: shortlink })} +

- Warning: Deleting this link will remove all of its analytics. This - action cannot be undone – proceed with caution. + {t("warning-deleting-link-analytics")}

@@ -82,8 +86,9 @@ function DeleteLinkModalInner({ >
-
@@ -113,7 +117,7 @@ function ImportBitlyModal({ {isLoading || !workspaceId ? (
-

Connecting to Bitly

+

{t("connecting-to-bitly")}

) : groups ? (

- Domains + {t("domains-header")}

@@ -193,7 +197,9 @@ function ImportBitlyModal({ {tags?.length > 0 && (

- {tags.length} tags found. Import all? + {t("tags-found-import-all", { + tagsLength: tags.length, + })}

{ @@ -215,14 +221,13 @@ function ImportBitlyModal({

- It looks like you don't have any Bitly groups with custom - domains (non bit.ly domains). + {t("no-bitly-groups-warning")}

)}
)} diff --git a/apps/web/ui/modals/link-builder/draft-controls.tsx b/apps/web/ui/modals/link-builder/draft-controls.tsx index acd1bb4bcb..54788d2ba0 100644 --- a/apps/web/ui/modals/link-builder/draft-controls.tsx +++ b/apps/web/ui/modals/link-builder/draft-controls.tsx @@ -3,6 +3,7 @@ import { AnimatedSizeContainer, Button, Popover, useMediaQuery } from "@dub/ui"; import { CircleCheck, CircleInfo, LoadingCircle, Xmark } from "@dub/ui/icons"; import { cn, nanoid, punycode, timeAgo, truncate } from "@dub/utils"; import { ChevronDown } from "lucide-react"; +import { useTranslations } from "next-intl"; import { forwardRef, SVGProps, @@ -181,6 +182,8 @@ function DraftOption({ onSelect: () => void; onDelete: () => void; }) { + const t = useTranslations("../ui/modals/link-builder"); + const { isMobile } = useMediaQuery(); // Memoize time so it doesn't change on rerender @@ -206,7 +209,7 @@ function DraftOption({ {draft.link.key ? ( punycode(draft.link.key) ) : ( - (link) + {t("link")} )}
diff --git a/apps/web/ui/modals/link-builder/password-modal.tsx b/apps/web/ui/modals/link-builder/password-modal.tsx index 1233598ce8..df5810b29d 100644 --- a/apps/web/ui/modals/link-builder/password-modal.tsx +++ b/apps/web/ui/modals/link-builder/password-modal.tsx @@ -10,6 +10,7 @@ import { } from "@dub/ui"; import { Eye, EyeSlash, InputPassword, Shuffle } from "@dub/ui/icons"; import { cn, nanoid } from "@dub/utils"; +import { useTranslations } from "next-intl"; import { Dispatch, SetStateAction, @@ -45,6 +46,8 @@ function PasswordModalInner({ }: { setShowPasswordModal: Dispatch>; }) { + const t = useTranslations("../ui/modals/link-builder"); + const { isMobile } = useMediaQuery(); const inputRef = useRef(null); const { @@ -93,11 +96,11 @@ function PasswordModalInner({ >
-

Link Password

+

{t("link-password")}

@@ -108,14 +111,19 @@ function PasswordModalInner({ - Press P{" "} - to open this quickly + {t("quick-access-instruction", { + component0: ( + + {t("quick-access-instruction_component0")} + + ), + })}
} side="right" > - P + {t("shortcut-key-p")}
@@ -124,7 +132,7 @@ function PasswordModalInner({
- Password + {t("password-label")}
- Remove password + {t("remove-password")} )}
@@ -194,7 +202,7 @@ function PasswordModalInner({
) : (
- Failed to load templates + {t("failed-to-load-templates")}
)} @@ -90,12 +93,14 @@ function UTMTemplateList({ data: UtmTemplateProps[]; onLoad: (params: Record) => void; }) { + const t = useTranslations("../ui/modals/link-builder"); + const { setValue } = useFormContext(); return data.length ? (
- UTM Templates + {t("utm-templates")} {data.map((template) => ( ) : (
- No templates found + {t("no-templates-found")}
); } diff --git a/apps/web/ui/modals/link-qr-modal.tsx b/apps/web/ui/modals/link-qr-modal.tsx index f2140c85be..93cbb7b29b 100644 --- a/apps/web/ui/modals/link-qr-modal.tsx +++ b/apps/web/ui/modals/link-qr-modal.tsx @@ -30,6 +30,7 @@ import { } from "@dub/ui/icons"; import { API_DOMAIN, cn, DUB_QR_LOGO, linkConstructor } from "@dub/utils"; import { AnimatePresence, motion } from "framer-motion"; +import { useTranslations } from "next-intl"; import { Dispatch, PropsWithChildren, @@ -92,6 +93,8 @@ function LinkQRModalInner({ showLinkQRModal: boolean; setShowLinkQRModal: Dispatch>; } & LinkQRModalProps) { + const t = useTranslations("../ui/modals"); + const { id: workspaceId, slug, plan, logo: workspaceLogo } = useWorkspace(); const id = useId(); const { isMobile } = useMediaQuery(); @@ -152,11 +155,11 @@ function LinkQRModalInner({ >
-

QR Code

+

{t("qr-code-title")}

@@ -167,14 +170,19 @@ function LinkQRModalInner({ - Press Q{" "} - to open this quickly + {t("qr-code-quick-open-instruction", { + component0: ( + + {t("qr-code-quick-open-instruction_component0")} + + ), + })}
} side="right" > - Q + {t("qr-code-quick-open-key")}
@@ -184,12 +192,12 @@ function LinkQRModalInner({
- QR Code Preview + {t("qr-code-preview-title")} @@ -257,12 +265,12 @@ function LinkQRModalInner({ className="text-sm font-medium text-gray-700" htmlFor={`${id}-show-logo`} > - Logo + {t("qr-code-logo-label")} @@ -278,7 +286,7 @@ function LinkQRModalInner({ disabledTooltip={ !plan || plan === "free" ? ( - QR Code Color + {t("qr-code-color-label")}
@@ -361,7 +369,7 @@ function LinkQRModalInner({
@@ -386,6 +394,8 @@ function DownloadPopover({ qrData: ReturnType; props: QRLinkProps; }>) { + const t = useTranslations("../ui/modals"); + const anchorRef = useRef(null); function download(url: string, extension: string) { @@ -411,7 +421,7 @@ function DownloadPopover({ className="rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100" > } /> @@ -426,7 +436,7 @@ function DownloadPopover({ className="rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100" > } /> @@ -441,7 +451,7 @@ function DownloadPopover({ className="rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100" > } /> @@ -470,6 +480,8 @@ function CopyPopover({ qrData: ReturnType; props: QRLinkProps; }>) { + const t = useTranslations("../ui/modals"); + const [openPopover, setOpenPopover] = useState(false); const [copiedURL, copyUrlToClipboard] = useCopyToClipboard(2000); const [copiedImage, copyImageToClipboard] = useCopyToClipboard(2000); @@ -504,7 +516,7 @@ function CopyPopover({ className="rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100" > @@ -532,7 +544,7 @@ function CopyPopover({ className="rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100" > diff --git a/apps/web/ui/modals/remove-teammate-modal.tsx b/apps/web/ui/modals/remove-teammate-modal.tsx index 2945116fd2..d4bc19c4e9 100644 --- a/apps/web/ui/modals/remove-teammate-modal.tsx +++ b/apps/web/ui/modals/remove-teammate-modal.tsx @@ -2,6 +2,7 @@ import useWorkspace from "@/lib/swr/use-workspace"; import { UserProps } from "@/lib/types"; import { Avatar, BlurImage, Button, Logo, Modal, useMediaQuery } from "@dub/ui"; import { useSession } from "next-auth/react"; +import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import { Dispatch, @@ -24,6 +25,8 @@ function RemoveTeammateModal({ user: UserProps; invite?: boolean; }) { + const t = useTranslations("../ui/modals"); + const router = useRouter(); const [removing, setRemoving] = useState(false); const { id: workspaceId, name: workspaceName, logo } = useWorkspace(); @@ -40,7 +43,7 @@ function RemoveTeammateModal({ {logo ? (

- {invite - ? "This will revoke " - : session?.user?.email === email - ? "You're about to leave " - : "This will remove "} - - {session?.user?.email === email ? workspaceName : name || email} - - {invite - ? "'s invitation to join your workspace. " - : session?.user?.email === email - ? ". You will lose all access to this workspace. " - : " from your workspace. "} - Are you sure you want to continue? + {t("workspace-removal-confirmation", { + inviteThisWillRevokeSessionUserEmailEmailYouReAboutToLeaveThisWillRemove: + invite + ? "This will revoke " + : session?.user?.email === email + ? "You're about to leave " + : "This will remove ", + component0: ( + + {session?.user?.email === email ? workspaceName : name || email} + + ), + inviteSInvitationToJoinYourWorkspaceSessionUserEmailEmailYouWillLoseAllAccessToThisWorkspaceFromYourWorkspace: + invite + ? "'s invitation to join your workspace. " + : session?.user?.email === email + ? ". You will lose all access to this workspace. " + : " from your workspace. ", + })}

@@ -82,7 +90,7 @@ function RemoveTeammateModal({
-
); diff --git a/apps/web/ui/oauth-apps/add-edit-integration-form.tsx b/apps/web/ui/oauth-apps/add-edit-integration-form.tsx index f3dcdc4004..9be11bcfe4 100644 --- a/apps/web/ui/oauth-apps/add-edit-integration-form.tsx +++ b/apps/web/ui/oauth-apps/add-edit-integration-form.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import { addEditIntegration } from "@/lib/actions/add-edit-integration"; import { clientAccessCheck } from "@/lib/api/tokens/permissions"; @@ -19,6 +20,8 @@ export default function AddEditIntegrationForm({ }: { integration: NewOrExistingIntegration; }) { + const t = useTranslations("../ui/oauth-apps"); + const { id: workspaceId, role } = useWorkspace(); const [screenshots, setScreenshots] = useState< { @@ -145,7 +148,7 @@ export default function AddEditIntegrationForm({
@@ -162,7 +165,7 @@ export default function AddEditIntegrationForm({ onChange={(e) => setData({ ...data, name: e.target.value })} autoFocus autoComplete="off" - placeholder="My App" + placeholder={t("my-app-name")} disabled={!canManageApp} />
@@ -171,7 +174,7 @@ export default function AddEditIntegrationForm({
@@ -195,7 +198,9 @@ export default function AddEditIntegrationForm({
@@ -208,7 +213,7 @@ export default function AddEditIntegrationForm({ "cursor-not-allowed bg-gray-50": !canManageApp, }, )} - placeholder="Add a description" + placeholder={t("add-description-placeholder")} value={description || ""} maxLength={120} onChange={(e) => { @@ -221,7 +226,9 @@ export default function AddEditIntegrationForm({
@@ -234,7 +241,7 @@ export default function AddEditIntegrationForm({ "cursor-not-allowed bg-gray-50": !canManageApp, }, )} - placeholder="## My Awesome Integration" + placeholder={t("my-awesome-integration-title")} value={readme || ""} maxLength={1000} onChange={(e) => { @@ -247,7 +254,9 @@ export default function AddEditIntegrationForm({
@@ -316,7 +325,7 @@ export default function AddEditIntegrationForm({ required value={developer} onChange={(e) => setData({ ...data, developer: e.target.value })} - placeholder="Acme Inc." + placeholder={t("acme-inc")} disabled={!canManageApp} />
@@ -324,7 +333,9 @@ export default function AddEditIntegrationForm({
@@ -339,7 +350,7 @@ export default function AddEditIntegrationForm({ required value={website} onChange={(e) => setData({ ...data, website: e.target.value })} - placeholder="https://acme.com" + placeholder={t("acme-website-url")} disabled={!canManageApp} />
diff --git a/apps/web/ui/oauth-apps/oauth-app-credentials.tsx b/apps/web/ui/oauth-apps/oauth-app-credentials.tsx index 9db15ff2ba..10eead49f7 100644 --- a/apps/web/ui/oauth-apps/oauth-app-credentials.tsx +++ b/apps/web/ui/oauth-apps/oauth-app-credentials.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import { CopyButton } from "@dub/ui"; @@ -11,6 +12,8 @@ export default function OAuthAppCredentials({ clientSecret: string | null; partialClientSecret: string; }) { + const t = useTranslations("../ui/oauth-apps"); + if (!clientId) { return null; } @@ -18,7 +21,9 @@ export default function OAuthAppCredentials({ return (
- +

{clientId}

@@ -28,7 +33,7 @@ export default function OAuthAppCredentials({ {clientSecret && (

@@ -39,8 +44,7 @@ export default function OAuthAppCredentials({

- Be sure to copy your client secret. You won’t be able to see it - again. + {t("client-secret-warning")}
)} @@ -48,7 +52,7 @@ export default function OAuthAppCredentials({ {!clientSecret && partialClientSecret && (

diff --git a/apps/web/ui/shared/pro-badge-tooltip.tsx b/apps/web/ui/shared/pro-badge-tooltip.tsx index cd8b6fb46e..7a40391092 100644 --- a/apps/web/ui/shared/pro-badge-tooltip.tsx +++ b/apps/web/ui/shared/pro-badge-tooltip.tsx @@ -1,6 +1,7 @@ import useWorkspace from "@/lib/swr/use-workspace"; import { BadgeTooltip, InfoTooltip, type TooltipProps } from "@dub/ui"; import { Crown } from "lucide-react"; +import { useTranslations } from "next-intl"; /** * A dynamic badge/icon w/ tooltip based on the workspace plan: @@ -9,13 +10,15 @@ import { Crown } from "lucide-react"; * For a Pro workspace: an info icon (question mark circle) */ export function ProBadgeTooltip(props: Omit) { + const t = useTranslations("../ui/shared"); + const { plan } = useWorkspace(); return plan === "free" ? (

-

Pro

+

{t("pro")}

) : ( diff --git a/apps/web/ui/webhooks/webhook-events.tsx b/apps/web/ui/webhooks/webhook-events.tsx index 5305bf5c82..b5ee52ce40 100644 --- a/apps/web/ui/webhooks/webhook-events.tsx +++ b/apps/web/ui/webhooks/webhook-events.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import { WebhookEventProps } from "@/lib/types"; import { @@ -20,6 +21,8 @@ export type EventListProps = PropsWithChildren<{ }>; const WebhookEvent = ({ event }: { event: WebhookEventProps }) => { + const t = useTranslations("../ui/webhooks"); + const [highlighter, setHighlighter] = useState(null); const [responseBody, setResponseBody] = useState(""); const [requestBody, setRequestBody] = useState(""); @@ -145,9 +148,9 @@ const WebhookEvent = ({ event }: { event: WebhookEventProps }) => {
-

Response

+

{t("response-message")}

-

HTTP status code

+

{t("http-status-code")}

{event.http_status}

{ />
-

Request

+

{t("request-message")}

-

Back to webhooks

+

+ {t("back-to-webhooks")} +

{isLoading || !webhook ? ( @@ -130,7 +135,7 @@ export default function WebhookHeader({ webhookId }: { webhookId: string }) {
@@ -37,6 +39,8 @@ export function ConversionTrackingToggle() { export function ConversionTrackingToggleSwitch( props: ComponentProps, ) { + const t = useTranslations("../ui/workspaces"); + const { slug: workspaceSlug, plan, @@ -64,7 +68,7 @@ export function ConversionTrackingToggleSwitch( disabledTooltip={ plan === "free" || plan === "pro" ? ( diff --git a/apps/web/ui/workspaces/create-workspace-form.tsx b/apps/web/ui/workspaces/create-workspace-form.tsx index b2ece3ac95..348ac65f0c 100644 --- a/apps/web/ui/workspaces/create-workspace-form.tsx +++ b/apps/web/ui/workspaces/create-workspace-form.tsx @@ -1,4 +1,5 @@ "use client"; +import { useTranslations } from "next-intl"; import { AlertCircleFill } from "@/ui/shared/icons"; import { Button, InfoTooltip, useMediaQuery } from "@dub/ui"; @@ -23,6 +24,8 @@ export function CreateWorkspaceForm({ onSuccess?: (data: FormData) => void; className?: string; }) { + const t = useTranslations("../ui/workspaces"); + const { update } = useSession(); const plausible = usePlausible(); @@ -87,7 +90,7 @@ export function CreateWorkspaceForm({