diff --git a/components/dashboard/AboutSettings.module.css b/components/dashboard/AboutSettings.module.css new file mode 100644 index 0000000..3e4699b --- /dev/null +++ b/components/dashboard/AboutSettings.module.css @@ -0,0 +1,27 @@ +.container { + display: flex; + flex-direction: column; + gap: 12px; +} + +.row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 14px; + border-radius: 8px; + background-color: var(--secondary); +} + +.label { + font-size: 0.9rem; + font-weight: 600; + color: var(--secondary-text); +} + +.value { + font-size: 0.9rem; + color: var(--primary-text); + font-family: monospace; +} diff --git a/components/dashboard/AboutSettings.tsx b/components/dashboard/AboutSettings.tsx new file mode 100644 index 0000000..68c9992 --- /dev/null +++ b/components/dashboard/AboutSettings.tsx @@ -0,0 +1,21 @@ +import styles from "./AboutSettings.module.css"; + +const AboutSettings = () => { + const version = process.env.NEXT_PUBLIC_APP_VERSION ?? "dev"; + const buildId = process.env.NEXT_PUBLIC_COMMIT_SHA ?? "local"; + + return ( +
+
+ Version + v{version} +
+
+ Build + {buildId} +
+
+ ); +}; + +export default AboutSettings; diff --git a/components/dashboard/DashboardModal.tsx b/components/dashboard/DashboardModal.tsx index 5394355..98b9593 100644 --- a/components/dashboard/DashboardModal.tsx +++ b/components/dashboard/DashboardModal.tsx @@ -13,81 +13,49 @@ import CollaboratorsSettings from "./project/CollaboratorsSettings"; import styles from "./DashboardModal.module.css"; import ExportProject from "./project/ExportProject"; -import { FileDown, Folder, Keyboard, KeyRound, Palette, PanelsTopLeft, User, Users } from "lucide-react"; +import { FileDown, Folder, Globe, Keyboard, KeyRound, Palette, PanelsTopLeft, User, Users } from "lucide-react"; +import { useTranslations } from "next-intl"; import KeybindsSettings from "./preferences/KeybindsSettings"; import AppearanceSettings from "./preferences/AppearanceSettings"; +import LanguageSettings from "./preferences/LanguageSettings"; import SecuritySettings from "./account/SecuritySettings"; import ProfileSettings from "./account/ProfileSettings"; import LayoutSettings from "./project/LayoutSettings"; import DashboardLogin from "./account/DashboardLogin"; - -const PROJECT_MENU: MenuSection = { - group: "Project", - items: [ - { - id: "General", - label: "General", - icon: , - }, - { - id: "Layout", - label: "Layout", - icon: , - }, - { - id: "Export", - label: "Import/Export", - icon: , - }, - { - id: "Collaborators", - label: "Collaborators", - icon: , - }, - ], -}; - -const PREFERENCES_MENU: MenuSection = { - group: "Preferences", - items: [ - { - id: "Keybinds", - label: "Keybinds", - icon: , - }, - { - id: "Appearance", - label: "Appearance", - icon: , - }, - ], -}; - -const ACCOUNT_MENU: MenuSection = { - group: "Account", - items: [ - { - id: "Profile", - label: "Profile", - icon: , - }, - { - id: "Security", - label: "Security", - icon: , - } /* - { - id: "Settings", - label: "Settings", - icon: , - },*/, - ], -}; +import AboutSettings from "./AboutSettings"; const DashboardModal = () => { const { isOpen, closeDashboard, activeTab, setActiveTab } = useContext(DashboardContext); const { project, isYjsReady } = useContext(ProjectContext); const { user } = useCookieUser(); + const t = useTranslations("modal"); + + const PROJECT_MENU = useMemo(() => ({ + group: t("groups.project"), + items: [ + { id: "General", label: t("tabs.General"), icon: }, + { id: "Layout", label: t("tabs.Layout"), icon: }, + { id: "Export", label: t("tabs.Export"), icon: }, + { id: "Collaborators", label: t("tabs.Collaborators"), icon: }, + ], + }), [t]); + + const PREFERENCES_MENU = useMemo(() => ({ + group: t("groups.preferences"), + items: [ + { id: "Keybinds", label: t("tabs.Keybinds"), icon: }, + { id: "Appearance", label: t("tabs.Appearance"), icon: }, + { id: "Language", label: t("tabs.Language"), icon: }, + ], + }), [t]); + + const ACCOUNT_MENU = useMemo(() => ({ + group: t("groups.account"), + items: [ + { id: "Profile", label: t("tabs.Profile"), icon: }, + { id: "Security", label: t("tabs.Security"), icon: }, + ], + }), [t]); // We're in a project if either: // - We have API membership data (cloud project), OR @@ -103,7 +71,7 @@ const DashboardModal = () => { sections.push(PREFERENCES_MENU); if (isSignedIn) sections.push(ACCOUNT_MENU); return sections; - }, [isInProject, isSignedIn]); + }, [isInProject, isSignedIn, PROJECT_MENU, PREFERENCES_MENU, ACCOUNT_MENU]); // If active tab is a project tab but we're not in a project, or an account tab but not signed in, switch to first available tab useEffect(() => { @@ -139,7 +107,7 @@ const DashboardModal = () => {
-

{activeTab}

+

{t(`tabs.${activeTab}` as Parameters[0])}

@@ -152,11 +120,14 @@ const DashboardModal = () => { {/* Preferences tabs */} {activeTab === "Keybinds" && } {activeTab === "Appearance" && } + {activeTab === "Language" && } {/* Account tabs - only when signed in */} {isSignedIn && activeTab === "Profile" && setDangerOpen((v) => !v)} />} {isSignedIn && activeTab === "Security" && } {/* Login tab - only when signed out */} {!isSignedIn && activeTab === "Login" && } + {/* About tab */} + {activeTab === "About" && }
diff --git a/components/dashboard/DashboardSidebar.tsx b/components/dashboard/DashboardSidebar.tsx index 6679f12..06c74aa 100644 --- a/components/dashboard/DashboardSidebar.tsx +++ b/components/dashboard/DashboardSidebar.tsx @@ -3,7 +3,8 @@ import { ReactNode, useContext } from "react"; import { mutate } from "swr"; import { DashboardContext } from "@src/context/DashboardContext"; -import { LogIn, LogOut } from "lucide-react"; +import { Info, LogIn, LogOut } from "lucide-react"; +import { useTranslations } from "next-intl"; import styles from "./DashboardModal.module.css"; import { redirect } from "next/navigation"; @@ -21,7 +22,9 @@ export type Category = | "Settings" | "Keybinds" | "Appearance" - | "Login"; + | "Language" + | "Login" + | "About"; export interface MenuItem { id: Category; @@ -43,6 +46,8 @@ interface SidebarMenuProps { const SidebarMenu = ({ structure, activeTab, onTabChange }: SidebarMenuProps) => { const { closeDashboard } = useContext(DashboardContext); const { user } = useCookieUser(); + const t = useTranslations("sidebar"); + const tModal = useTranslations("modal"); const onLogOut = async () => { await logout(); @@ -60,7 +65,7 @@ const SidebarMenu = ({ structure, activeTab, onTabChange }: SidebarMenuProps) => return ( ); diff --git a/components/dashboard/account/ProfileSettings.tsx b/components/dashboard/account/ProfileSettings.tsx index 62bb2be..3f5b273 100644 --- a/components/dashboard/account/ProfileSettings.tsx +++ b/components/dashboard/account/ProfileSettings.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import { editUserInfo, logout } from "@src/lib/utils/requests"; import { useRouter } from "next/navigation"; import { ArrowRight, Trash2 } from "lucide-react"; +import { useTranslations } from "next-intl"; import form from "./../../utils/Form.module.css"; import sharedStyles from "../project/ProjectSettings.module.css"; @@ -12,8 +13,6 @@ import dangerStyles from "../project/DangerZone.module.css"; import { ApiResponse } from "@src/lib/utils/api-utils"; import { useUser } from "@src/lib/utils/hooks"; -const DELETE_CONFIRMATION_PHRASE = "I confirm my account deletion"; - const PRESET_COLORS = [ "#ef4444", // red "#f97316", // orange @@ -28,6 +27,9 @@ const PRESET_COLORS = [ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean; onDangerToggle: () => void }) => { const { user, mutate } = useUser(); const router = useRouter(); + const t = useTranslations("profile"); + const tCommon = useTranslations("common"); + const confirmPhrase = t("deleteConfirmPhrase"); const [username, setUsername] = useState(""); const [color, setColor] = useState(PRESET_COLORS[0]); @@ -86,15 +88,15 @@ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean; }); if (res.ok) { - setMessage({ type: "success", text: "Profile updated successfully" }); + setMessage({ type: "success", text: t("successMessage") }); setDirty(false); mutate(); } else { const data = (await res.json()) as ApiResponse; - setMessage({ type: "error", text: data.message || "Failed to update profile" }); + setMessage({ type: "error", text: data.message || t("failedUpdate") }); } } catch { - setMessage({ type: "error", text: "An error occurred while saving" }); + setMessage({ type: "error", text: t("errorSaving") }); } finally { setLoading(false); } @@ -106,13 +108,11 @@ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean;
-

Delete account

-

- Permanently delete your account and all associated data. This cannot be undone. -

+

{t("deleteAccount")}

+

{t("deleteAccountDesc")}

@@ -120,19 +120,16 @@ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean; {showDeleteDialog && (
-

Delete account

-

- This will permanently delete your account and all associated data. This action cannot be - undone. -

+

{t("deleteModalTitle")}

+

{t("deleteModalDesc")}

setDeleteConfirmInput(e.target.value)} autoComplete="off" @@ -141,10 +138,10 @@ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean;
@@ -168,31 +165,29 @@ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean;
{/* Email */}
- +
{/* Username */}
- + -

- This name will be visible to collaborators when you work on shared projects. -

+

{t("usernameHelp")}

{/* Color */}
- +
{PRESET_COLORS.map((presetColor) => ( @@ -202,7 +197,7 @@ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean; className={`${styles.colorPreset} ${color === presetColor ? styles.selected : ""}`} style={{ backgroundColor: presetColor }} onClick={() => handleColorChange(presetColor)} - aria-label={`Select color ${presetColor}`} + aria-label={t("selectColor", { color: presetColor })} /> ))}
@@ -212,7 +207,7 @@ const ProfileSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean; htmlFor="custom-color" className={`${styles.colorPreset} ${styles.customColorSwatch} ${!PRESET_COLORS.includes(color) ? styles.selected : ""}`} style={{ backgroundColor: color }} - title="Custom color" + title={t("customColor")} > -
diff --git a/components/dashboard/preferences/AppearanceSettings.tsx b/components/dashboard/preferences/AppearanceSettings.tsx index 565365b..2bfc6b0 100644 --- a/components/dashboard/preferences/AppearanceSettings.tsx +++ b/components/dashboard/preferences/AppearanceSettings.tsx @@ -8,6 +8,7 @@ import { UserTheme } from "@src/lib/utils/types"; import { editUserSettings } from "@src/lib/utils/requests"; import { useTheme } from "next-themes"; import { useSettings } from "@src/lib/utils/hooks"; +import { useTranslations } from "next-intl"; import Dropdown, { DropdownOption } from "@components/utils/Dropdown"; const THEME_COLORS: Record< @@ -70,6 +71,8 @@ const THEME_LABELS: Record = { const AppearanceSettings = () => { const { theme, setTheme } = useTheme(); const { settings, mutate } = useSettings(); + const t = useTranslations("appearance"); + const tCommon = useTranslations("common"); const themedEditor = settings?.themedEditor ?? false; @@ -110,7 +113,7 @@ const AppearanceSettings = () => {
{/* Theme Selection */}
- + setTheme(value)} @@ -118,20 +121,13 @@ const AppearanceSettings = () => { className={`${sharedStyles.input} ${styles.input}`} />

- {theme === "dark" && "Cozy, low-glare theme made for night owls and late-hour focus."} - {theme === "light" && "Crisp, airy theme that feels natural and comfortable during the day."} - {theme === "latte" && "Soft, cream-based theme that blends warmth with readability."} - {theme === "wonka" && "Velvety, cocoa-based theme that blends deep luxury with eye-resting focus"} - {theme === "mint" && - "Refreshing, mint-infused theme that blends botanical serenity with eye-resting balance"} - {theme === "blossom" && - "Gentle, petal-infused theme that blends floral warmth with eye-resting softness"} + {theme && t(`themeHelp.${theme}` as Parameters[0])}

{/* Editor Appearance */}
- +
{ {themedEditor &&
}
- Themed editor - - Use theme colors for the editor background and text - + {t("themedEditor")} + {t("themedEditorDesc")}
diff --git a/components/dashboard/preferences/KeybindsSettings.tsx b/components/dashboard/preferences/KeybindsSettings.tsx index 94ecc98..4b1b09b 100644 --- a/components/dashboard/preferences/KeybindsSettings.tsx +++ b/components/dashboard/preferences/KeybindsSettings.tsx @@ -10,6 +10,7 @@ import { useSettings } from "@src/lib/utils/hooks"; import { tinykeys } from "@node_modules/tinykeys/dist/tinykeys"; import { editUserSettings } from "@src/lib/utils/requests"; import { DEFAULT_KEYBINDS, DefaultKeyBind, prettyPrintKeybind, UserKeybindsMap } from "@src/lib/utils/keybinds"; +import { useTranslations } from "next-intl"; export type KeybindElementProps = { id: string; @@ -31,12 +32,13 @@ const KeybindElement = ({ startListening, }: KeybindElementProps) => { const effective = current || kb.defaultCombo; + const t = useTranslations("keybinds"); return (
{kb.label} - {`Default: ${prettyPrintKeybind(kb.defaultCombo)}`} + {t("defaultPrefix", { combo: prettyPrintKeybind(kb.defaultCombo) })}
@@ -53,13 +55,13 @@ const KeybindElement = ({ }} > {isListening ? ( - Type… (Esc to cancel) + {t("typing")} ) : tempCombo && isListening ? ( {prettyPrintKeybind(tempCombo)} ) : effective ? ( {prettyPrintKeybind(effective)} ) : ( - Not set + {t("notSet")} )}
@@ -68,9 +70,9 @@ const KeybindElement = ({ type="button" className={styles.clearBtn} onClick={() => resetBinding(id)} - title="Clear user binding" + title={t("resetTitle")} > - Reset + {t("reset")}
@@ -84,6 +86,7 @@ const KeybindsSettings = () => { updateSetting?: (key: string, value: any) => Promise; }; + const t = useTranslations("keybinds"); const [userKeybinds, setUserKeybinds] = useState({}); const [listeningFor, setListeningFor] = useState(null); const [tempCombo, setTempCombo] = useState(null); @@ -156,7 +159,7 @@ const KeybindsSettings = () => { e.preventDefault(); const combo = formatComboFromEvent(e); if (!combo) { - setTempCombo("Modifiers only — press a regular key"); + setTempCombo(t("modifiersOnly")); return; } setTempCombo(combo); @@ -221,7 +224,7 @@ const KeybindsSettings = () => { return (
- +
@@ -250,7 +253,7 @@ const KeybindsSettings = () => { onClick={resetDefaults} className={`${sharedStyles.formBtn} ${sharedStyles.danger}`} > - Reset to defaults + {t("resetDefaults")}
diff --git a/components/dashboard/preferences/LanguageSettings.tsx b/components/dashboard/preferences/LanguageSettings.tsx new file mode 100644 index 0000000..55282be --- /dev/null +++ b/components/dashboard/preferences/LanguageSettings.tsx @@ -0,0 +1,41 @@ +"use client"; + +import form from "./../../utils/Form.module.css"; +import sharedStyles from "../project/ProjectSettings.module.css"; +import { UserLanguage } from "@src/lib/utils/types"; +import { useLocale } from "@src/context/LocaleContext"; +import Dropdown, { DropdownOption } from "@components/utils/Dropdown"; +import { useTranslations } from "next-intl"; + +const LANGUAGE_OPTIONS: DropdownOption[] = [ + { value: "en", label: "English" }, + { value: "es", label: "Español" }, + { value: "fr", label: "Français" }, + { value: "de", label: "Deutsch" }, + { value: "pl", label: "Polski" }, + { value: "zh", label: "中文" }, + { value: "ko", label: "한국어" }, + { value: "ja", label: "日本語" }, +]; + +const LanguageSettings = () => { + const { locale, setLanguage } = useLocale(); + const t = useTranslations("language"); + + return ( +
+
+ + setLanguage(value as UserLanguage)} + options={LANGUAGE_OPTIONS} + className={sharedStyles.input} + /> +

{t("helpText")}

+
+
+ ); +}; + +export default LanguageSettings; diff --git a/components/dashboard/project/CollaboratorsSettings.module.css b/components/dashboard/project/CollaboratorsSettings.module.css index a4cda46..81df55c 100644 --- a/components/dashboard/project/CollaboratorsSettings.module.css +++ b/components/dashboard/project/CollaboratorsSettings.module.css @@ -207,6 +207,7 @@ visibility 0.2s; z-index: 100; white-space: nowrap; + width: max-content; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } @@ -222,7 +223,7 @@ font-weight: 700; color: var(--primary-text); margin-right: 6px; - min-width: 45px; + min-width: 80px; display: inline-block; } diff --git a/components/dashboard/project/CollaboratorsSettings.tsx b/components/dashboard/project/CollaboratorsSettings.tsx index faf5571..a71e1c7 100644 --- a/components/dashboard/project/CollaboratorsSettings.tsx +++ b/components/dashboard/project/CollaboratorsSettings.tsx @@ -1,6 +1,7 @@ "use client"; import { useContext, useMemo, useState } from "react"; +import { useTranslations } from "next-intl"; import { useCookieUser, useProjectCollaborators, useProjectInvites, useProjectMembership } from "@src/lib/utils/hooks"; import { CookieUser } from "@src/lib/utils/types"; import { ProjectRole } from "@prisma/client"; @@ -20,6 +21,7 @@ import { redirect } from "next/navigation"; const MAX_COLLABORATORS = 5; const CollaboratorsSettings = () => { + const t = useTranslations("collaborators"); const { membership } = useProjectMembership(); const { invites, mutate: mutateInvites } = useProjectInvites(membership?.project.id); const { collaborators, mutate: mutateCollaborators } = useProjectCollaborators(membership?.project.id); @@ -52,34 +54,31 @@ const CollaboratorsSettings = () => {
- Owner - Can delete the project and transfer ownership + {t("roles.owner")} + {t("roleDesc.owner")}
- Admin - Can invite, promote, demote, and kick collaborators + {t("roles.admin")} + {t("roleDesc.admin")}
- Editor - Can modify screenplay and other project content + {t("roles.editor")} + {t("roleDesc.editor")}
- Viewer - Read-only access. Cannot make any changes + {t("roles.viewer")} + {t("roleDesc.viewer")}
-

- Manage your team members and pending invitations. You can invite any non-Pro user to be part of your - project. The project remains collaborative until owner has Pro plan. -

+

{t("teamHelp")}

{/* Project Collaborators */}
@@ -126,6 +125,7 @@ interface MemberSlotProps { } const MemberSlot = ({ data, membership, mutateCollaborators, user }: MemberSlotProps) => { + const t = useTranslations("collaborators"); const { closeDashboard } = useContext(DashboardContext); const isOwner = data.role === ProjectRole.OWNER; @@ -162,7 +162,7 @@ const MemberSlot = ({ data, membership, mutateCollaborators, user }: MemberSlotP
- {data.user.email} {isSelf && "(you)"} + {data.user.email} {isSelf && t("you")}
@@ -173,14 +173,14 @@ const MemberSlot = ({ data, membership, mutateCollaborators, user }: MemberSlotP onChange={(e) => updateRole(e.target.value as ProjectRole)} disabled={!isAdmin || isSelf} > - - - - + + + + { }
@@ -196,6 +196,7 @@ interface InviteSlotProps { } const InviteSlot = ({ data, membership, mutateInvites }: InviteSlotProps) => { + const t = useTranslations("collaborators"); const canInvite = Roles.hasRoleOrGreater(membership.role, ProjectRole.ADMIN); const handleCancelInvite = async () => { const res = await deleteInvite(membership.project.id, data.email); @@ -209,12 +210,12 @@ const InviteSlot = ({ data, membership, mutateInvites }: InviteSlotProps) => {
{data.email} - Pending + {t("pending")}
@@ -228,6 +229,7 @@ interface EmptySlotProps { } const EmptySlot = ({ membership, mutateInvites }: EmptySlotProps) => { + const t = useTranslations("collaborators"); const [email, setEmail] = useState(""); const canInvite = Roles.hasRoleOrGreater(membership.role, ProjectRole.ADMIN); @@ -243,14 +245,14 @@ const EmptySlot = ({ membership, mutateInvites }: EmptySlotProps) => {
setEmail(e.target.value)} className={styles.miniInput} />
diff --git a/components/dashboard/project/DangerZone.tsx b/components/dashboard/project/DangerZone.tsx index 5491695..3417b27 100644 --- a/components/dashboard/project/DangerZone.tsx +++ b/components/dashboard/project/DangerZone.tsx @@ -6,6 +6,7 @@ import { redirectHome } from "@src/lib/utils/redirects"; import { useContext, useState } from "react"; import { DashboardContext } from "@src/context/DashboardContext"; import { Trash2 } from "lucide-react"; +import { useTranslations } from "next-intl"; import styles from "./DangerZone.module.css"; import form from "../../utils/Form.module.css"; @@ -21,6 +22,8 @@ const DangerZone = ({ projectId, isLocalOnly, isOpen }: DangerZoneProps) => { const { closeDashboard } = useContext(DashboardContext); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [loading, setLoading] = useState(false); + const t = useTranslations("dangerZone"); + const tCommon = useTranslations("common"); if (!projectId || !isOpen) return null; @@ -45,7 +48,7 @@ const DangerZone = ({ projectId, isLocalOnly, isOpen }: DangerZoneProps) => { }; const handleTransferOwnership = () => { - const newOwner = window.prompt("Enter the email of the new owner:"); + const newOwner = window.prompt(t("transferPrompt")); if (newOwner) { console.log(`Transferring ownership to: ${newOwner}`); // Add your transfer logic here @@ -59,13 +62,11 @@ const DangerZone = ({ projectId, isLocalOnly, isOpen }: DangerZoneProps) => { {!isLocalOnly && membership && (
-

Transfer ownership

-

- Transfer your owner role to another user. You will be given editor role. -

+

{t("transferOwnership")}

+

{t("transferDesc")}

)} @@ -73,13 +74,11 @@ const DangerZone = ({ projectId, isLocalOnly, isOpen }: DangerZoneProps) => { {/* Delete Project */}
-

Delete project

-

- Once you delete a project, there is no going back. Please be certain. -

+

{t("deleteProject")}

+

{t("deleteProjectDesc")}

@@ -88,11 +87,8 @@ const DangerZone = ({ projectId, isLocalOnly, isOpen }: DangerZoneProps) => { {showDeleteDialog && (
-

Delete project

-

- This action is permanent and cannot be undone. All data associated with this project will be - lost. -

+

{t("modalTitle")}

+

{t("modalDesc")}

diff --git a/components/dashboard/project/ExportProject.tsx b/components/dashboard/project/ExportProject.tsx index 8839d68..655ecaa 100644 --- a/components/dashboard/project/ExportProject.tsx +++ b/components/dashboard/project/ExportProject.tsx @@ -1,6 +1,7 @@ "use client"; import { useContext, useState, useRef } from "react"; +import { useTranslations } from "next-intl"; import { isTauri } from "@tauri-apps/api/core"; import { ProjectContext } from "@src/context/ProjectContext"; import { useCookieUser, useLocalProjectInfo, useProjectIdFromUrl } from "@src/lib/utils/hooks"; @@ -24,6 +25,7 @@ export enum ExportFormat { } const ExportProject = () => { + const t = useTranslations("export"); const { user } = useCookieUser(); const { project: membership, @@ -140,17 +142,17 @@ const ExportProject = () => { }; const formatOptions: DropdownOption[] = [ - { value: ExportFormat.PDF, label: "PDF Document (.pdf)" }, - { value: ExportFormat.FOUNTAIN, label: "Fountain (.fountain)" }, - { value: ExportFormat.FDX, label: "Final Draft (.fdx)" }, - { value: ExportFormat.SCRIPTIO, label: "Scriptio (.scriptio)" }, + { value: ExportFormat.PDF, label: t("formatOptions.pdf") }, + { value: ExportFormat.FOUNTAIN, label: t("formatOptions.fountain") }, + { value: ExportFormat.FDX, label: t("formatOptions.fdx") }, + { value: ExportFormat.SCRIPTIO, label: t("formatOptions.scriptio") }, ]; return (
{/* --- Import Section --- */}
- + {/* Hidden Input */} {
- Select File - Upload .fountain, .fdx, .scriptio, or .txt + {t("selectFile")} + {t("selectFileDesc")}
{/* --- Export Format Selection --- */}
- + setFormat(value as ExportFormat)} @@ -198,11 +200,10 @@ const ExportProject = () => { className={`${sharedStyles.input} ${styles.input}`} />

- {format === ExportFormat.PDF && "Standard industry format. Best for sharing and printing."} - {format === ExportFormat.FOUNTAIN && - "Plain text format based on markdown, great for compatibility."} - {format === ExportFormat.FDX && "Compatible with Final Draft industry software."} - {format === ExportFormat.SCRIPTIO && "Scriptio own format, to keep your project local"} + {format === ExportFormat.PDF && t("formatHelp.pdf")} + {format === ExportFormat.FOUNTAIN && t("formatHelp.fountain")} + {format === ExportFormat.FDX && t("formatHelp.fdx")} + {format === ExportFormat.SCRIPTIO && t("formatHelp.scriptio")}

@@ -217,8 +218,8 @@ const ExportProject = () => { {includeNotes &&
}
- Include Notes - Export inline notes. + {t("includeNotes")} + {t("includeNotesDesc")}
@@ -232,8 +233,8 @@ const ExportProject = () => { {includeWatermark &&
}
- Watermark - Overlay the author's name on pages. + {t("watermark")} + {t("watermarkDesc")}
)} @@ -251,15 +252,15 @@ const ExportProject = () => { {enablePassword &&
}
- Password Protection - Require a password to open the PDF. + {t("passwordProtection")} + {t("passwordProtectionDesc")}
{enablePassword && ( setPassword(e.target.value)} - placeholder="Enter password" + placeholder={t("passwordPlaceholder")} className={`${sharedStyles.input} ${styles.passwordInput}`} onClick={(e) => e.stopPropagation()} /> @@ -270,7 +271,9 @@ const ExportProject = () => {
diff --git a/components/dashboard/project/LayoutSettings.tsx b/components/dashboard/project/LayoutSettings.tsx index e2e0e63..3cdb80c 100644 --- a/components/dashboard/project/LayoutSettings.tsx +++ b/components/dashboard/project/LayoutSettings.tsx @@ -1,6 +1,7 @@ "use client"; import { useContext, useEffect, useState } from "react"; +import { useTranslations } from "next-intl"; import { ProjectContext } from "@src/context/ProjectContext"; import { PageFormat } from "@src/lib/utils/enums"; import { Check } from "lucide-react"; @@ -12,6 +13,7 @@ import styles from "./LayoutSettings.module.css"; import optionCard from "./OptionCard.module.css"; const LayoutSettings = () => { + const t = useTranslations("layout"); const { pageFormat, setPageFormat, @@ -70,7 +72,7 @@ const LayoutSettings = () => { return (
- + handleFormatChange(value as PageFormat)} @@ -78,14 +80,13 @@ const LayoutSettings = () => { className={`${sharedStyles.input} ${styles.input}`} />

- {pageFormat === "LETTER" && - "Standard format used in the United States. Industry standard for Hollywood screenplays."} - {pageFormat === "A4" && "International standard format. Common in Europe and most other countries."} + {pageFormat === "LETTER" && t("pageFormatHelp.letter")} + {pageFormat === "A4" && t("pageFormatHelp.a4")}

- +
setSceneHeadingBold(!sceneHeadingBold)} @@ -94,8 +95,8 @@ const LayoutSettings = () => { {sceneHeadingBold &&
}
- Bold - Scene headings will appear in bold + {t("bold")} + {t("boldDesc")}
@@ -107,8 +108,8 @@ const LayoutSettings = () => { {sceneHeadingDoubleSpace &&
}
- Extra space above - Add extra spacing before scene headings + {t("extraSpace")} + {t("extraSpaceDesc")}
@@ -123,8 +124,8 @@ const LayoutSettings = () => { {displaySceneNumbers &&
}
- Scene numbering - Show scene numbers in left margin + {t("sceneNumbering")} + {t("sceneNumberingDesc")}
{displaySceneNumbers && ( @@ -141,8 +142,8 @@ const LayoutSettings = () => { {sceneNumberOnRight &&
}
- Duplicate in right margin - Show number on both sides + {t("duplicateRight")} + {t("duplicateRightDesc")}
)} @@ -150,7 +151,7 @@ const LayoutSettings = () => {
- +
{ className={`${styles.contdConfirmBtn} ${hasMoreChanges ? styles.contdConfirmBtnActive : ""}`} disabled={!hasMoreChanges} onClick={commitMoreLabel} - title="Apply dialogue continuation label (bottom of page)" + title={t("moreTitle")} > @@ -187,7 +188,7 @@ const LayoutSettings = () => { className={`${styles.contdConfirmBtn} ${hasContdChanges ? styles.contdConfirmBtnActive : ""}`} disabled={!hasContdChanges} onClick={commitContdLabel} - title="Apply character continuation label (top of page)" + title={t("contdTitle")} > diff --git a/components/dashboard/project/ProjectSettings.tsx b/components/dashboard/project/ProjectSettings.tsx index a934369..ae43a6b 100644 --- a/components/dashboard/project/ProjectSettings.tsx +++ b/components/dashboard/project/ProjectSettings.tsx @@ -1,6 +1,7 @@ "use client"; import { cropImageBase64 } from "@src/lib/utils/misc"; +import { useTranslations } from "next-intl"; import { editProject } from "@src/lib/utils/requests"; import { useContext, useEffect, useState } from "react"; import { isTauri } from "@tauri-apps/api/core"; @@ -14,6 +15,7 @@ import styles from "./ProjectSettings.module.css"; import dangerStyles from "./DangerZone.module.css"; const ProjectSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean; onDangerToggle: () => void }) => { + const t = useTranslations("projectSettings"); const { membership, mutate } = useProjectMembership(); const { setProjectTitle: setContextTitle } = useContext(ProjectContext); const projectId = useProjectIdFromUrl(); @@ -102,59 +104,56 @@ const ProjectSettings = ({ dangerOpen, onDangerToggle }: { dangerOpen: boolean;
{/* Title */}
- + setDirty(true)} className={styles.input} - placeholder="Enter project name..." + placeholder={t("titlePlaceholder")} />
{/* Author */}
- + setDirty(true)} className={styles.input} - placeholder="Author name..." + placeholder={t("authorPlaceholder")} />
{/* Description */}
- +