Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add i18n #11

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions app/host/_components/ShareOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,44 @@
import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast";
import { Copy, Link as LinkIcon } from "lucide-react";
import { useTranslations } from "next-intl";

interface ShareOptionsProps {
roomId: string;
}

export function ShareOptions({ roomId }: ShareOptionsProps) {
const t = useTranslations("ShareOptions");
const { toast } = useToast();

function copyRoomId() {
navigator.clipboard.writeText(roomId);
toast({
title: "Room code copied!",
description: "Share this code with others to let them join your room."
title: t("code-copied"),
description: t("code-copied-desc")
});
}

function copyShareableLink() {
const shareableUrl = `${window.location.origin}/join?room=${roomId}`;
navigator.clipboard.writeText(shareableUrl);
toast({
title: "Shareable link copied!",
description: "Share this link with others to let them join your room directly."
title: t("link-copied"),
description: t("link-copied-desc")
});
}

return (
<div className="space-y-6">
<div className="space-y-2">
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>Room Code</span>
<span>{t("room-code")}</span>
<Button variant="ghost" size="sm" className="gap-2" onClick={copyRoomId} disabled={!roomId}>
<Copy className="h-4 w-4" />
Copy Code
{t("copy-code-btn")}
</Button>
</div>
<code className="block w-full p-3 bg-gray-100 dark:bg-gray-800 rounded-lg text-sm font-mono">{roomId || "Generating room code..."}</code>
<code className="block w-full p-3 bg-gray-100 dark:bg-gray-800 rounded-lg text-sm font-mono">{roomId || t("generating-code")}</code>
</div>

<div className="relative">
Expand All @@ -52,13 +54,13 @@ export function ShareOptions({ roomId }: ShareOptionsProps) {

<div className="space-y-2">
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>Shareable Link</span>
<span>{t("shareable-link")}</span>
<Button variant="ghost" size="sm" className="gap-2" onClick={copyShareableLink} disabled={!roomId}>
<LinkIcon className="h-4 w-4" />
Copy Link
{t("copy-link-btn")}
</Button>
</div>
<code className="block w-full p-3 bg-gray-100 dark:bg-gray-800 rounded-lg text-sm font-mono truncate">{roomId ? `${window.location.origin}/join?room=${roomId}` : "Generating link..."}</code>
<code className="block w-full p-3 bg-gray-100 dark:bg-gray-800 rounded-lg text-sm font-mono truncate">{roomId ? `${window.location.origin}/join?room=${roomId}` : t("generating-link")}</code>
</div>
</div>
);
Expand Down
29 changes: 16 additions & 13 deletions app/host/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { ToastAction } from "@/components/ui/toast";
import { useToast } from "@/hooks/use-toast";
import { ArrowLeft, Monitor, Users } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
import Peer from "peerjs";
import { useEffect, useState } from "react";
import { ShareOptions } from "./_components/ShareOptions";

export default function HostPage() {
const tc = useTranslations("Common");
const t = useTranslations("HostPage");
const [roomId, setRoomId] = useState("");
const [peer, setPeer] = useState<Peer | null>(null);
const [activeStream, setActiveStream] = useState<MediaStream | null>(null);
Expand Down Expand Up @@ -50,12 +53,12 @@ export default function HostPage() {
if (!activeStream) {
if (connections.length > 0) {
toast({
title: "New viewer connected",
description: "Click to start sharing your screen.",
title: t("new-viewer"),
description: t("new-viewer-desc"),
duration: Infinity,
action: (
<ToastAction
altText="Start sharing"
altText={t("start-sharing")}
onClick={async () => {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({
Expand All @@ -66,13 +69,13 @@ export default function HostPage() {
} catch (err) {
console.error("Screen sharing error:", err);
toast({
title: "Screen sharing error",
description: "Failed to start screen sharing. Please try again.",
title: t("share-error"),
description: t("share-error-desc"),
variant: "destructive"
});
}
}}>
Start Sharing
{t("start-sharing")}
</ToastAction>
)
});
Expand Down Expand Up @@ -104,8 +107,8 @@ export default function HostPage() {
setRoomId("");

toast({
title: "Session ended",
description: "Your screen sharing session has been terminated."
title: t("session-ended"),
description: t("session-ended-desc")
});

router.push("/");
Expand All @@ -117,33 +120,33 @@ export default function HostPage() {
<Button variant="outline" asChild>
<Link href="/" className="flex items-center gap-2">
<ArrowLeft className="h-4 w-4" />
Back to Home
{tc("back-to-home")}
</Link>
</Button>

<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Monitor className="h-6 w-6" />
Your Screen Sharing Room
{t("title")}
</CardTitle>
<CardDescription>Share your room code or link with others to let them view your screen. To share audio as well, ensure you're using Chrome or Edge, and select the option to share a tab.</CardDescription>
<CardDescription>{t("description")}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<ShareOptions roomId={roomId} />

<div className="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<div className="flex items-center gap-2">
<Users className="h-5 w-5 text-gray-500" />
<span className="text-sm text-gray-500">Current Viewers</span>
<span className="text-sm text-gray-500">{t("current-viewers")}</span>
</div>
<span className="text-lg font-semibold">{connections.length}</span>
</div>

{activeStream && (
<div className="flex justify-end pt-4">
<Button variant="destructive" onClick={endSession} className="flex items-center gap-2">
Stop sharing
{t("stop-sharing")}
</Button>
</div>
)}
Expand Down
29 changes: 16 additions & 13 deletions app/join/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Input } from "@/components/ui/input";
import { useToast } from "@/hooks/use-toast";
import { ArrowLeft, Users } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import Peer from "peerjs";
import { useEffect, useRef, useState } from "react";

export default function JoinPage() {
const tc = useTranslations("Common");
const t = useTranslations("JoinPage");
const [roomId, setRoomId] = useState("");
const [isConnecting, setIsConnecting] = useState(false);
const [activeStream, setActiveStream] = useState<MediaStream | null>(null);
Expand Down Expand Up @@ -42,8 +45,8 @@ export default function JoinPage() {
function joinRoom(roomIdToJoin: string = roomId) {
if (!roomIdToJoin.trim()) {
toast({
title: "Room code required",
description: "Please enter a valid room code.",
title: t("code-required"),
description: t("code-required-desc"),
variant: "destructive"
});
return;
Expand All @@ -59,8 +62,8 @@ export default function JoinPage() {

connection.on("open", () => {
toast({
title: "Connected!",
description: "Waiting for host to share their screen..."
title: t("connected"),
description: t("connected-desc")
});
});

Expand All @@ -76,8 +79,8 @@ export default function JoinPage() {
setRoomId("");
setActiveStream(null);
toast({
title: "Disconnected",
description: "The session has been ended.",
title: t("disconnected"),
description: t("disconnected-desc"),
variant: "destructive"
});
});
Expand All @@ -87,8 +90,8 @@ export default function JoinPage() {
console.error("Peer error:", err);
setIsConnecting(false);
toast({
title: "Connection failed",
description: "Could not connect to the room. Please check the room code and try again.",
title: t("connection-failed"),
description: t("connection-failed-desc"),
variant: "destructive"
});
});
Expand All @@ -100,24 +103,24 @@ export default function JoinPage() {
<Button variant="outline" asChild>
<Link href="/" className="flex items-center gap-2">
<ArrowLeft className="h-4 w-4" />
Back to Home
{tc("back-to-home")}
</Link>
</Button>

<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-6 w-6" />
Join a Room
{t("title")}
</CardTitle>
<CardDescription>Enter the room code to join and view the shared screen</CardDescription>
<CardDescription>{t("description")}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{!activeStream ? (
<div className="space-y-4">
<Input placeholder="Enter room code" value={roomId} onChange={(e) => setRoomId(e.target.value)} disabled={isConnecting} />
<Input placeholder={t("enter-code")} value={roomId} onChange={(e) => setRoomId(e.target.value)} disabled={isConnecting} />
<Button className="w-full" onClick={() => joinRoom()} disabled={isConnecting || !roomId.trim()}>
{isConnecting ? "Connecting..." : "Join Room"}
{isConnecting ? t("connecting") : t("join-room")}
</Button>
</div>
) : (
Expand Down
53 changes: 36 additions & 17 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,53 @@ import { Clarity } from "@/components/Clarity";
import { Toaster } from "@/components/ui/toaster";

import type { Metadata } from "next";
import { NextIntlClientProvider } from "next-intl";
import { getLocale, getMessages, getTranslations } from "next-intl/server";
import { Inter } from "next/font/google";
import Link from "next/link";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
title: "Screen Share - Share Your Screen Instantly",
description: "Share your screen instantly with anyone using a simple room code. No downloads or sign-ups required.",
keywords: "screen sharing, webrtc, online screen share, browser screen sharing, free screen sharing"
} satisfies Metadata;
type Props = {
params: Promise<{ locale: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const t = await getTranslations({ locale: (await params).locale, namespace: "Common" });

return {
title: t("title"),
description: t("description"),
keywords: t("keywords")
};
}

export default async function RootLayout({ children }: { children: React.ReactNode }) {
const locale = await getLocale();
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages();
const t = await getTranslations({ locale, namespace: "Common" });

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<html lang={locale}>
<body className={inter.className}>
<main className="flex flex-col justify-between min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
{children}
<NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>
<footer className="py-8 px-4 text-center text-gray-500 text-sm">
Built by{" "}
<Link href="https://tonghohin.vercel.app" className="underline" target="_blank">
Hin
</Link>
. The source code is available on{" "}
<Link href="https://github.com/tonghohin/screen-sharing" className="underline" target="_blank">
Github
</Link>
.
{t.rich("footer", {
author: (chunks) => (
<Link href="https://tonghohin.vercel.app" className="underline" target="_blank">
{chunks}
</Link>
),
link: (chunks) => (
<Link href="https://github.com/tonghohin/screen-sharing" className="underline" target="_blank">
{chunks}
</Link>
)
})}
</footer>
</main>
<Clarity />
Expand Down
18 changes: 10 additions & 8 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Monitor, Users } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";

export default function Home() {
const t = useTranslations("Home");
return (
<div className="py-12 px-4">
<div className="max-w-4xl mx-auto space-y-8">
<div className="text-center space-y-4">
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-6xl">Share Your Screen Instantly</h1>
<p className="text-xl text-gray-600 dark:text-gray-300">Create a room, share the code, and start presenting to your audience in seconds.</p>
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-6xl">{t("header")}</h1>
<p className="text-xl text-gray-600 dark:text-gray-300">{t("description")}</p>
</div>

<div className="grid md:grid-cols-2 gap-6 mt-12">
<Card className="hover:shadow-lg transition-shadow">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Monitor className="h-6 w-6" />
Start Sharing
{t("start-title")}
</CardTitle>
<CardDescription>Create a room and share your screen with others</CardDescription>
<CardDescription>{t("start-desc")}</CardDescription>
</CardHeader>
<CardContent>
<Link href="/host">
<Button className="w-full">Create Room</Button>
<Button className="w-full">{t("create-room-btn")}</Button>
</Link>
</CardContent>
</Card>
Expand All @@ -32,14 +34,14 @@ export default function Home() {
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-6 w-6" />
Join a Room
{t("join-title")}
</CardTitle>
<CardDescription>Enter a room code to view someone's screen</CardDescription>
<CardDescription>{t("join-desc")}</CardDescription>
</CardHeader>
<CardContent>
<Link href="/join">
<Button variant="outline" className="w-full">
Join Room
{t("join-room-btn")}
</Button>
</Link>
</CardContent>
Expand Down
Loading