diff --git a/src/components/auth/RecordlySignInDialog.tsx b/src/components/auth/RecordlySignInDialog.tsx index fbe3d12e..6c9657cc 100644 --- a/src/components/auth/RecordlySignInDialog.tsx +++ b/src/components/auth/RecordlySignInDialog.tsx @@ -1,3 +1,4 @@ +import { useI18n } from "@/contexts/I18nContext"; import { GoogleLogo, SignOut, XLogo } from "@phosphor-icons/react"; import type { User } from "@supabase/supabase-js"; import { type FormEvent, useEffect, useState } from "react"; @@ -32,16 +33,20 @@ type Props = { onAuthenticated: () => void; }; -function friendlyAuthError(error: unknown, action: string): string { +function friendlyAuthError( + error: unknown, + action: string, + t: ReturnType["t"], +): string { const message = error instanceof Error ? error.message : String(error); if (/unsupported provider|provider is not enabled/i.test(message)) { if (action === "google") { - return "Google sign-in isn't enabled yet. Use email for now, or ask your Recordly administrator to connect Google."; + return t("editor.cloud.googleUnavailable"); } if (action === "x") { - return "X sign-in isn't enabled yet. Use email for now, or ask your Recordly administrator to connect X."; + return t("editor.cloud.xUnavailable"); } - return "This sign-in method isn't enabled for Recordly yet. Use email for now."; + return t("editor.cloud.providerUnavailable"); } return message; } @@ -55,16 +60,19 @@ export function RecordlySignInDialog({ callbackError, onAuthenticated, }: Props) { + const { t } = useI18n(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [busy, setBusy] = useState(); const [message, setMessage] = useState(); + const [resetSent, setResetSent] = useState(false); useEffect(() => { if (!open) { setPassword(""); setBusy(undefined); setMessage(undefined); + setResetSent(false); } }, [open]); @@ -75,10 +83,11 @@ export function RecordlySignInDialog({ const run = async (label: string, action: () => Promise) => { setBusy(label); setMessage(undefined); + setResetSent(false); try { await action(); } catch (error) { - setMessage(friendlyAuthError(error, label)); + setMessage(friendlyAuthError(error, label, t)); } finally { setBusy(undefined); } @@ -95,12 +104,13 @@ export function RecordlySignInDialog({ const forgotPassword = () => { if (!email.trim()) { - setMessage("Enter your email address first."); + setMessage(t("editor.cloud.enterEmail")); return; } void run("reset", async () => { await sendPasswordReset(email.trim()); - setMessage("Password reset email sent."); + setMessage(t("editor.cloud.resetSent")); + setResetSent(true); }); }; @@ -110,17 +120,19 @@ export function RecordlySignInDialog({ - + - {user ? "Your Recordly account" : "Sign into Recordly"} + {user + ? t("editor.cloud.accountHeading") + : t("editor.cloud.signInHeading")} {user ? user.email : reason === "share" - ? "Sign in to publish this video and manage its shared link." - : "Access your recordings and shared links."} + ? t("editor.cloud.signInShareDescription") + : t("editor.cloud.signInDescription")} @@ -132,7 +144,9 @@ export function RecordlySignInDialog({ onPress={() => void run("signout", signOutRecordly)} > - {busy === "signout" ? "Signing out…" : "Sign out"} + {busy === "signout" + ? t("editor.cloud.signingOut") + : t("editor.cloud.signOut")} ) : ( <> @@ -161,7 +175,9 @@ export function RecordlySignInDialog({
- or + + {t("editor.cloud.or")} +
@@ -173,7 +189,7 @@ export function RecordlySignInDialog({ isRequired isDisabled={Boolean(busy)} > - + - + @@ -199,26 +215,22 @@ export function RecordlySignInDialog({ isDisabled={disabled} onPress={forgotPassword} > - Forgot password? + {t("editor.cloud.forgotPassword")} )} {message || callbackError ? ( - + @@ -231,8 +243,7 @@ export function RecordlySignInDialog({ {!configured && !user && ( - Cloud sign-in isn’t available in this build yet. You can still - save videos to your computer. + {t("editor.cloud.unavailable")} )} diff --git a/src/components/launch/LaunchWindow.module.css b/src/components/launch/LaunchWindow.module.css index e429372a..e632b99d 100644 --- a/src/components/launch/LaunchWindow.module.css +++ b/src/components/launch/LaunchWindow.module.css @@ -196,6 +196,6 @@ } .micSelect option { - background-color: #1a1a22; + background-color: var(--surface); color: var(--foreground); } diff --git a/src/components/video-editor/cloud/CloudShareButton.tsx b/src/components/video-editor/cloud/CloudShareButton.tsx index 142c5937..2c611bcc 100644 --- a/src/components/video-editor/cloud/CloudShareButton.tsx +++ b/src/components/video-editor/cloud/CloudShareButton.tsx @@ -1,3 +1,4 @@ +import { useI18n } from "@/contexts/I18nContext"; import { Check, CloudArrowUp, Copy, ShareNetwork } from "@phosphor-icons/react"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "@/components/ui/toast"; @@ -35,6 +36,7 @@ export function CloudShareButton({ hideTrigger = false, authToken, }: Props) { + const { t } = useI18n(); const [internalOpen, setInternalOpen] = useState(false); const open = controlledOpen ?? internalOpen; const setOpen = useCallback( @@ -96,7 +98,7 @@ export function CloudShareButton({ setError(undefined); setShareUrl(undefined); try { - if (!authToken) throw new Error("Sign in to Recordly before creating a shared link."); + if (!authToken) throw new Error(t("editor.cloud.signInRequired")); let resolvedFilePath = filePath ?? preparedFileRef.current; if (!resolvedFilePath) { resolvedFilePath = await prepareFile?.(); @@ -105,8 +107,7 @@ export function CloudShareButton({ void window.electronAPI.discardExportedTemp(resolvedFilePath); return; } - if (!resolvedFilePath) - throw new Error("Could not prepare the current edit for sharing."); + if (!resolvedFilePath) throw new Error(t("editor.cloud.prepareFailed")); preparedFileRef.current = resolvedFilePath; } const nextUploadId = crypto.randomUUID(); @@ -121,12 +122,12 @@ export function CloudShareButton({ uploadId: nextUploadId, }); if (!result.success || !result.shareUrl) { - if (!result.canceled) setError(result.error || "Cloud upload failed."); + if (!result.canceled) setError(result.error || t("editor.cloud.uploadFailed")); return; } setProgress(100); setShareUrl(result.shareUrl); - toast.success("Share link created"); + toast.success(t("editor.cloud.linkCreated")); } catch (cause) { setError(cause instanceof Error ? cause.message : String(cause)); } finally { @@ -134,7 +135,7 @@ export function CloudShareButton({ setPhase("idle"); setUploadId(undefined); } - }, [authToken, filePath, notes, prepareFile, projectTitle]); + }, [authToken, filePath, notes, prepareFile, projectTitle, t]); const handleCancel = useCallback(async () => { cancelRequestedRef.current = true; @@ -144,10 +145,15 @@ export function CloudShareButton({ const copyShareUrl = useCallback(async () => { if (!shareUrl) return; - await navigator.clipboard.writeText(shareUrl); - setCopied(true); - toast.success("Link copied"); - }, [shareUrl]); + try { + await navigator.clipboard.writeText(shareUrl); + setCopied(true); + toast.success(t("editor.cloud.linkCopied")); + } catch { + setCopied(false); + toast.error(t("editor.cloud.copyFailed")); + } + }, [shareUrl, t]); return ( <> @@ -158,27 +164,26 @@ export function CloudShareButton({ onClick={() => setOpen(true)} disabled={!filePath && !prepareFile} className="inline-flex h-11 flex-1 items-center justify-center gap-2 rounded-lg border-foreground/10 bg-foreground/5 px-3 text-foreground hover:bg-foreground/10 disabled:opacity-40" - title="Create a shareable link" + title={t("editor.cloud.createLinkTitle")} > - Create link + + {t("editor.cloud.createLink")} + )} - Share to the cloud - - Publish the current edit to a Recordly viewing and feedback page. No - download is required. Shared videos are prepared at up to 1080p. - + {t("editor.cloud.heading")} + {t("editor.cloud.description")} {shareUrl ? (
- Your video is ready to share. + {t("editor.cloud.ready")}
@@ -188,7 +193,7 @@ export function CloudShareButton({ ) : ( )} - {copied ? "Copied" : "Copy"} + {copied ? t("editor.cloud.copied") : t("editor.cloud.copy")}
) : (
- +