refactor(i18n): replace prop drilling with translation context
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,20 +1,16 @@
|
||||
"use client";
|
||||
import ClipboardApp from "@/components/ClipboardApp";
|
||||
import { useI18n } from "@/components/providers/TranslationProvider";
|
||||
import { cn } from "@/lib/utils";
|
||||
import SystemDiagram from "@/components/web/SystemDiagram";
|
||||
import FAQSection from "@/components/web/FAQSection";
|
||||
import HowItWorks from "@/components/web/HowItWorks";
|
||||
import YouTubePlayer from "@/components/common/YouTubePlayer";
|
||||
import KeyFeatures from "@/components/web/KeyFeatures";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import LazyLoadWrapper from "@/components/common/LazyLoadWrapper";
|
||||
|
||||
interface PageContentProps {
|
||||
messages: Messages;
|
||||
lang: string;
|
||||
}
|
||||
|
||||
export default function HomeClient({ messages, lang }: PageContentProps) {
|
||||
export default function HomeClient() {
|
||||
const { messages, lang } = useI18n();
|
||||
const youtube_videoId = lang === "zh" ? "I0RLCpcbUXs" : "ypt-po_R2Ds";
|
||||
const bilibili_videoId = lang === "zh" ? "BV1knrjYZEfn" : "BV1yErjYFEV7";
|
||||
return (
|
||||
@@ -41,7 +37,7 @@ export default function HomeClient({ messages, lang }: PageContentProps) {
|
||||
{/* How It Works Section */}
|
||||
<section aria-label="How It Works">
|
||||
<LazyLoadWrapper>
|
||||
<HowItWorks messages={messages} />
|
||||
<HowItWorks />
|
||||
</LazyLoadWrapper>
|
||||
</section>
|
||||
{/* Demo Video Section */}
|
||||
@@ -81,27 +77,19 @@ export default function HomeClient({ messages, lang }: PageContentProps) {
|
||||
{/* System Architecture Section */}
|
||||
<section aria-label="System Architecture">
|
||||
<LazyLoadWrapper>
|
||||
<SystemDiagram messages={messages} />
|
||||
<SystemDiagram />
|
||||
</LazyLoadWrapper>
|
||||
</section>
|
||||
{/* Key Features */}
|
||||
<section aria-label="Key Features">
|
||||
<LazyLoadWrapper>
|
||||
<KeyFeatures
|
||||
messages={messages}
|
||||
isInToolPage
|
||||
titleClassName="text-2xl md:text-3xl"
|
||||
/>
|
||||
<KeyFeatures isInToolPage titleClassName="text-2xl md:text-3xl" />
|
||||
</LazyLoadWrapper>
|
||||
</section>
|
||||
{/* FAQ Section */}
|
||||
<section aria-label="Frequently Asked Questions">
|
||||
<LazyLoadWrapper>
|
||||
<FAQSection
|
||||
messages={messages}
|
||||
isInToolPage
|
||||
titleClassName="text-2xl md:text-3xl"
|
||||
/>
|
||||
<FAQSection isInToolPage titleClassName="text-2xl md:text-3xl" />
|
||||
</LazyLoadWrapper>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { Messages } from "@/types/messages";
|
||||
"use client";
|
||||
|
||||
interface AboutContentProps {
|
||||
messages: Messages;
|
||||
lang: string;
|
||||
}
|
||||
import { useI18n } from "@/components/providers/TranslationProvider";
|
||||
|
||||
export default function AboutContent() {
|
||||
const { messages, lang } = useI18n();
|
||||
|
||||
export default function AboutContent({ messages, lang }: AboutContentProps) {
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold text-center mb-6">
|
||||
|
||||
@@ -31,12 +31,6 @@ export async function generateMetadata({
|
||||
};
|
||||
}
|
||||
|
||||
export default async function About({
|
||||
params: { lang },
|
||||
}: {
|
||||
params: { lang: string };
|
||||
}) {
|
||||
const messages = await getDictionary(lang);
|
||||
|
||||
return <AboutContent messages={messages} lang={lang} />;
|
||||
export default function About() {
|
||||
return <AboutContent />;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default async function BlogPage({
|
||||
{/* Articles List */}
|
||||
<div className="space-y-12">
|
||||
{posts.map((post) => (
|
||||
<ArticleListItem key={post.slug} post={post} lang={lang} messages={messages} />
|
||||
<ArticleListItem key={post.slug} post={post} />
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -57,13 +57,13 @@ export default async function TagPage({
|
||||
</div>
|
||||
|
||||
{/* Articles List */}
|
||||
<div className="space-y-12">
|
||||
{posts.length > 0 ? (
|
||||
posts.map((post) => (
|
||||
<ArticleListItem key={post.slug} post={post} lang={lang} messages={messages} />
|
||||
))
|
||||
) : (
|
||||
<p>{messages.text.blog.tagEmpty}</p>
|
||||
<div className="space-y-12">
|
||||
{posts.length > 0 ? (
|
||||
posts.map((post) => (
|
||||
<ArticleListItem key={post.slug} post={post} />
|
||||
))
|
||||
) : (
|
||||
<p>{messages.text.blog.tagEmpty}</p>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -58,7 +58,7 @@ export default async function FAQ({
|
||||
return (
|
||||
<>
|
||||
<JsonLd id="faq-ld" data={faqLd} />
|
||||
<FAQSection messages={messages} />
|
||||
<FAQSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,11 +32,6 @@ export async function generateMetadata({
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Features({
|
||||
params: { lang },
|
||||
}: {
|
||||
params: { lang: string };
|
||||
}) {
|
||||
const messages = await getDictionary(lang);
|
||||
return <KeyFeatures messages={messages} />;
|
||||
}
|
||||
export default function Features() {
|
||||
return <KeyFeatures />;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { Messages } from "@/types/messages";
|
||||
"use client";
|
||||
|
||||
interface HelpContentProps {
|
||||
messages: Messages;
|
||||
lang: string;
|
||||
}
|
||||
import { useI18n } from "@/components/providers/TranslationProvider";
|
||||
|
||||
export default function HelpContent() {
|
||||
const { messages, lang } = useI18n();
|
||||
|
||||
export default function HelpContent({ messages, lang }: HelpContentProps) {
|
||||
return (
|
||||
<div className="container mx-auto py-12">
|
||||
<h1 className="text-4xl font-bold mb-6">{messages.text.help.h1}</h1>
|
||||
|
||||
@@ -30,11 +30,6 @@ export async function generateMetadata({
|
||||
},
|
||||
};
|
||||
}
|
||||
export default async function Help({
|
||||
params: { lang },
|
||||
}: {
|
||||
params: { lang: string };
|
||||
}) {
|
||||
const messages = await getDictionary(lang);
|
||||
return <HelpContent messages={messages} lang={lang} />;
|
||||
export default function Help() {
|
||||
return <HelpContent />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "./globals.css";
|
||||
import Header from "@/components/web/Header";
|
||||
import Footer from "@/components/web/Footer";
|
||||
import { TranslationProvider } from "@/components/providers/TranslationProvider";
|
||||
import { ThemeProvider } from "@/components/web/theme-provider";
|
||||
import { getDictionary } from "@/lib/dictionary";
|
||||
import JsonLd from "@/components/seo/JsonLd";
|
||||
@@ -47,9 +48,11 @@ export default async function RootLayout({
|
||||
disableTransitionOnChange
|
||||
storageKey="theme-preference"
|
||||
>
|
||||
<Header messages={messages} lang={lang} />
|
||||
<div className="flex-1">{children}</div>
|
||||
<Footer messages={messages} lang={lang} />
|
||||
<TranslationProvider messages={messages} lang={lang}>
|
||||
<Header />
|
||||
<div className="flex-1">{children}</div>
|
||||
<Footer />
|
||||
</TranslationProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -61,7 +61,7 @@ export default async function Home({
|
||||
return (
|
||||
<>
|
||||
<JsonLd id="home-ld" data={webAppLd} />
|
||||
<HomeClient messages={messages} lang={lang} />
|
||||
<HomeClient />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Messages } from "@/types/messages";
|
||||
"use client";
|
||||
|
||||
interface PageContentProps {
|
||||
messages: Messages;
|
||||
}
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
|
||||
export default function PrivacyContent() {
|
||||
const messages = useMessages();
|
||||
|
||||
export default function PrivacyContent({ messages }: PageContentProps) {
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold text-center mb-6">
|
||||
|
||||
@@ -30,11 +30,6 @@ export async function generateMetadata({
|
||||
},
|
||||
};
|
||||
}
|
||||
export default async function Privacy({
|
||||
params: { lang },
|
||||
}: {
|
||||
params: { lang: string };
|
||||
}) {
|
||||
const messages = await getDictionary(lang);
|
||||
return <PrivacyContent messages={messages} />;
|
||||
export default function Privacy() {
|
||||
return <PrivacyContent />;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { Messages } from "@/types/messages";
|
||||
"use client";
|
||||
|
||||
interface PageContentProps {
|
||||
messages: Messages;
|
||||
}
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
|
||||
export default function TermsContent() {
|
||||
const messages = useMessages();
|
||||
|
||||
export default function TermsContent({ messages }: PageContentProps) {
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<h1 className="text-3xl font-bold text-center mb-6">
|
||||
|
||||
@@ -30,11 +30,6 @@ export async function generateMetadata({
|
||||
},
|
||||
};
|
||||
}
|
||||
export default async function TermsOfUse({
|
||||
params: { lang },
|
||||
}: {
|
||||
params: { lang: string };
|
||||
}) {
|
||||
const messages = await getDictionary(lang);
|
||||
return <TermsContent messages={messages} />;
|
||||
export default function TermsOfUse() {
|
||||
return <TermsContent />;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import React, { useRef, useCallback, useEffect, useMemo } from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useRichTextToPlainText from "../hooks/useRichTextToPlainText";
|
||||
import QRCodeComponent from "./ClipboardApp/ShareCard";
|
||||
@@ -18,13 +19,14 @@ import { getCachedId } from "@/lib/roomIdCache";
|
||||
import { useConnectionFeedback } from "@/hooks/useConnectionFeedback";
|
||||
|
||||
const ClipboardApp = () => {
|
||||
const messages = useMessages();
|
||||
const { shareMessage, retrieveMessage, putMessageInMs } =
|
||||
useClipboardAppMessages();
|
||||
|
||||
const dragCounter = useRef(0);
|
||||
const retrieveJoinRoomBtnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const { messages, isLoadingMessages } = usePageSetup({
|
||||
usePageSetup({
|
||||
setRetrieveRoomId: useFileTransferStore.getState().setRetrieveRoomIdInput,
|
||||
setActiveTab: useFileTransferStore.getState().setActiveTab,
|
||||
retrieveJoinRoomBtnRef,
|
||||
@@ -178,20 +180,9 @@ const ClipboardApp = () => {
|
||||
// Connection feedback observer (Hook)
|
||||
useConnectionFeedback({ messages, putMessageInMs });
|
||||
|
||||
if (isLoadingMessages || !messages) {
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8 w-full md:max-w-4xl">
|
||||
<div className="min-h-[1000px] w-full bg-gray-200/50 dark:bg-gray-800/50 rounded-lg animate-pulse">
|
||||
{" "}
|
||||
Loading Editor...{" "}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full mx-auto px-1 sm:px-1 py-3 sm:py-8 md:max-w-4xl md:container">
|
||||
<FullScreenDropZone isDragging={isDragging} messages={messages} />
|
||||
<FullScreenDropZone isDragging={isDragging} />
|
||||
<div className="flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-2 mb-4">
|
||||
<Button
|
||||
variant={activeTab === "send" ? "default" : "outline"}
|
||||
@@ -225,7 +216,6 @@ const ClipboardApp = () => {
|
||||
<CardContent className="px-3 sm:px-6">
|
||||
{activeTab === "send" ? (
|
||||
<SendTabPanel
|
||||
messages={messages}
|
||||
updateShareContent={updateShareContent}
|
||||
addFilesToSend={addFilesToSend}
|
||||
removeFileToSend={removeFileToSend}
|
||||
@@ -240,7 +230,6 @@ const ClipboardApp = () => {
|
||||
/>
|
||||
) : (
|
||||
<RetrieveTabPanel
|
||||
messages={messages}
|
||||
putMessageInMs={putMessageInMs}
|
||||
setRetrieveRoomIdInput={setRetrieveRoomIdInput}
|
||||
joinRoom={joinRoom}
|
||||
@@ -257,7 +246,7 @@ const ClipboardApp = () => {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
{activeTab === "send" && shareLink && messages && (
|
||||
{activeTab === "send" && shareLink && (
|
||||
<Card className="border-2 sm:border-4 shadow-md mt-2 sm:mt-4">
|
||||
<CardHeader className="pb-3 sm:pb-6">
|
||||
<CardTitle className="text-base sm:text-lg">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Tooltip from "@/components/Tooltip";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import { getCachedId, setCachedId } from "@/lib/roomIdCache";
|
||||
|
||||
/**
|
||||
@@ -42,7 +42,6 @@ import { getCachedId, setCachedId } from "@/lib/roomIdCache";
|
||||
*/
|
||||
|
||||
type Props = {
|
||||
messages: Messages;
|
||||
getInputValue: () => string;
|
||||
setInputValue: (val: string) => void;
|
||||
putMessageInMs: (
|
||||
@@ -69,7 +68,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function CachedIdActionButton({
|
||||
messages,
|
||||
getInputValue,
|
||||
setInputValue,
|
||||
putMessageInMs,
|
||||
@@ -82,6 +80,7 @@ export default function CachedIdActionButton({
|
||||
onUseCached,
|
||||
disabled = false,
|
||||
}: Props) {
|
||||
const messages = useMessages();
|
||||
const [hasCachedId, setHasCachedId] = useState<boolean>(false);
|
||||
const [showSaveOverride, setShowSaveOverride] = useState<boolean>(false);
|
||||
const clickCountRef = useRef(0);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Download, Trash2 } from "lucide-react";
|
||||
import { Tooltip } from "@/components/Tooltip";
|
||||
@@ -7,9 +8,6 @@ import { formatFileSize, generateFileId } from "@/lib/fileUtils";
|
||||
import { AutoPopupDialog } from "@/components/common/AutoPopupDialog";
|
||||
import { FileMeta, CustomFile, Progress } from "@/types/webrtc";
|
||||
import FileTransferButton from "./FileTransferButton";
|
||||
import { getDictionary } from "@/lib/dictionary";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import { useFileTransferStore } from "@/stores/fileTransferStore";
|
||||
import { supportsAutoDownload } from "@/lib/browserUtils";
|
||||
import { postLogToBackend } from "@/app/config/api";
|
||||
@@ -68,8 +66,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
saveType,
|
||||
largeFileThreshold = 500 * 1024 * 1024, // 500MB default
|
||||
}) => {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages | null>(null);
|
||||
const messages = useMessages();
|
||||
|
||||
// Get the cleaning method of the store
|
||||
const { clearSendProgress, clearReceiveProgress } = useFileTransferStore();
|
||||
@@ -111,12 +108,6 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDictionary(locale)
|
||||
.then((dict) => setMessages(dict))
|
||||
.catch((error) => console.error("Failed to load messages:", error));
|
||||
}, [locale]);
|
||||
|
||||
useEffect(() => {
|
||||
// Separate single files and folders
|
||||
const tempSingleFiles: FileMeta[] = [];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Download } from "lucide-react";
|
||||
import {
|
||||
@@ -7,9 +8,6 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { getDictionary } from "@/lib/dictionary";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface FileTransferButtonProps {
|
||||
onRequest: () => void;
|
||||
@@ -28,19 +26,13 @@ const FileTransferButton = ({
|
||||
isSavedToDisk,
|
||||
isPendingSave = false,
|
||||
}: FileTransferButtonProps) => {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages | null>(null);
|
||||
const messages = useMessages();
|
||||
// Button status judgment - 待保存状态时按钮应该可点击
|
||||
const isDisabled =
|
||||
isCurrentFileTransferring ||
|
||||
isSavedToDisk ||
|
||||
(isOtherFileTransferring && !isPendingSave);
|
||||
|
||||
useEffect(() => {
|
||||
getDictionary(locale)
|
||||
.then((dict) => setMessages(dict))
|
||||
.catch((error) => console.error("Failed to load messages:", error));
|
||||
}, [locale]);
|
||||
// Display different tooltips based on status
|
||||
const getTooltipContent = () => {
|
||||
if (isSavedToDisk)
|
||||
@@ -87,9 +79,6 @@ const FileTransferButton = ({
|
||||
};
|
||||
|
||||
const buttonStyles = getButtonStyles();
|
||||
if (messages === null) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return (
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {
|
||||
useRef,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Upload } from "lucide-react";
|
||||
import { FileMeta, CustomFile } from "@/types/webrtc";
|
||||
@@ -23,11 +24,6 @@ declare module "@/components/ui/input" {
|
||||
}
|
||||
}
|
||||
|
||||
import { getDictionary } from "@/lib/dictionary";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import { en } from "@/constants/messages/en"; // Import English dictionary as default
|
||||
|
||||
function formatFileChosen(
|
||||
template: string,
|
||||
fileNum: number,
|
||||
@@ -45,28 +41,19 @@ interface FileUploadHandlerProps {
|
||||
const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
onFilePicked,
|
||||
}) => {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages>(en); // Use English dictionary as initial value
|
||||
const messages = useMessages();
|
||||
|
||||
const folderInputRef = useRef<HTMLInputElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
// File selector -- message prompt
|
||||
const [fileText, setFileText] = useState<string>(
|
||||
en.text.fileUploadHandler.noFileChosenTip
|
||||
messages.text.fileUploadHandler.noFileChosenTip
|
||||
);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (locale !== "en") {
|
||||
// Only load other language packs if not English
|
||||
getDictionary(locale)
|
||||
.then((dict) => {
|
||||
setMessages(dict);
|
||||
setFileText(dict.text.fileUploadHandler.noFileChosenTip);
|
||||
})
|
||||
.catch((error) => console.error("Failed to load messages:", error));
|
||||
}
|
||||
}, [locale]);
|
||||
setFileText(messages.text.fileUploadHandler.noFileChosenTip);
|
||||
}, [messages.text.fileUploadHandler.noFileChosenTip]);
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(newFiles: CustomFile[]) => {
|
||||
@@ -154,9 +141,6 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
const handleSelectFolder = () => {
|
||||
folderInputRef.current?.click();
|
||||
};
|
||||
if (messages === null) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import React from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Upload } from "lucide-react";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface FullScreenDropZoneProps {
|
||||
isDragging: boolean;
|
||||
messages: Messages;
|
||||
}
|
||||
|
||||
const FullScreenDropZone: React.FC<FullScreenDropZoneProps> = ({
|
||||
isDragging,
|
||||
messages,
|
||||
}) => {
|
||||
const FullScreenDropZone: React.FC<FullScreenDropZoneProps> = ({ isDragging }) => {
|
||||
const messages = useMessages();
|
||||
|
||||
if (!isDragging) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -7,13 +8,11 @@ import {
|
||||
} from "@/components/common/clipboard_btn";
|
||||
import CachedIdActionButton from "@/components/ClipboardApp/CachedIdActionButton";
|
||||
import FileListDisplay from "@/components/ClipboardApp/FileListDisplay";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import type { FileMeta } from "@/types/webrtc";
|
||||
|
||||
import { useFileTransferStore } from "@/stores/fileTransferStore";
|
||||
|
||||
interface RetrieveTabPanelProps {
|
||||
messages: Messages;
|
||||
putMessageInMs: (
|
||||
message: string,
|
||||
isShareEnd?: boolean,
|
||||
@@ -36,7 +35,6 @@ interface RetrieveTabPanelProps {
|
||||
}
|
||||
|
||||
export function RetrieveTabPanel({
|
||||
messages,
|
||||
putMessageInMs,
|
||||
setRetrieveRoomIdInput,
|
||||
joinRoom,
|
||||
@@ -50,6 +48,7 @@ export function RetrieveTabPanel({
|
||||
retrieveMessage,
|
||||
handleLeaveRoom,
|
||||
}: RetrieveTabPanelProps) {
|
||||
const messages = useMessages();
|
||||
// Get the status from the store
|
||||
const {
|
||||
retrieveRoomStatusText,
|
||||
@@ -114,7 +113,6 @@ export function RetrieveTabPanel({
|
||||
/>
|
||||
{/* Save/Use Cached ID Button placed after Paste button */}
|
||||
<CachedIdActionButton
|
||||
messages={messages}
|
||||
getInputValue={() => retrieveRoomIdInput}
|
||||
setInputValue={setRetrieveRoomIdInput}
|
||||
putMessageInMs={putMessageInMs}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
import { FileUploadHandler } from "@/components/ClipboardApp/FileUploadHandler";
|
||||
import FileListDisplay from "@/components/ClipboardApp/FileListDisplay";
|
||||
import AnimatedButton from "@/components/ui/AnimatedButton";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import type { CustomFile, FileMeta } from "@/types/webrtc";
|
||||
|
||||
import { useFileTransferStore } from "@/stores/fileTransferStore";
|
||||
@@ -29,7 +29,6 @@ const RichTextEditor = dynamic(
|
||||
);
|
||||
|
||||
interface SendTabPanelProps {
|
||||
messages: Messages;
|
||||
updateShareContent: (content: string) => void;
|
||||
addFilesToSend: (files: CustomFile[]) => void;
|
||||
removeFileToSend: (meta: FileMeta) => void;
|
||||
@@ -48,7 +47,6 @@ interface SendTabPanelProps {
|
||||
}
|
||||
|
||||
export function SendTabPanel({
|
||||
messages,
|
||||
updateShareContent,
|
||||
addFilesToSend,
|
||||
removeFileToSend,
|
||||
@@ -61,6 +59,7 @@ export function SendTabPanel({
|
||||
handleLeaveSenderRoom,
|
||||
putMessageInMs,
|
||||
}: SendTabPanelProps) {
|
||||
const messages = useMessages();
|
||||
// Get the status from the store
|
||||
const {
|
||||
shareContent,
|
||||
@@ -192,7 +191,6 @@ export function SendTabPanel({
|
||||
</Button>
|
||||
{/* Save/Use Cached ID Button in between */}
|
||||
<CachedIdActionButton
|
||||
messages={messages}
|
||||
getInputValue={() => inputFieldValue}
|
||||
setInputValue={setInputFieldValue}
|
||||
putMessageInMs={putMessageInMs}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Copy, Download, Check } from "lucide-react";
|
||||
import { WriteClipboardButton } from "../common/clipboard_btn";
|
||||
|
||||
import { getDictionary } from "@/lib/dictionary";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import type { Messages } from "@/types/messages";
|
||||
interface ShareCardProps {
|
||||
RoomID: string;
|
||||
shareLink: string;
|
||||
@@ -22,8 +19,7 @@ const QRCodeSVG = dynamic(
|
||||
}
|
||||
);
|
||||
const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages | null>(null);
|
||||
const messages = useMessages();
|
||||
const qrRef = useRef<HTMLDivElement>(null);
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||
|
||||
@@ -86,12 +82,6 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
|
||||
downloadQRCode(); // Fallback to download on any error
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
getDictionary(locale)
|
||||
.then((dict) => setMessages(dict))
|
||||
.catch((error) => console.error("Failed to load messages:", error));
|
||||
}, [locale]);
|
||||
|
||||
const downloadQRCode = () => {
|
||||
if (!qrRef.current) return;
|
||||
|
||||
@@ -116,9 +106,6 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
|
||||
};
|
||||
img.src = "data:image/svg+xml;base64," + btoa(svgData);
|
||||
};
|
||||
if (messages === null) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return (
|
||||
<div className="bg-primary/10 p-2 sm:p-4 rounded-lg border border-primary/20">
|
||||
<p className="text-primary mb-3 sm:mb-4 text-sm sm:text-base">
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useI18n } from "@/components/providers/TranslationProvider";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { type BlogPost } from "@/lib/blog";
|
||||
import { Messages } from "@/types/messages";
|
||||
|
||||
interface ArticleListItemProps {
|
||||
post: BlogPost;
|
||||
lang: string;
|
||||
messages: Messages;
|
||||
}
|
||||
|
||||
export function ArticleListItem({ post, lang, messages }: ArticleListItemProps) {
|
||||
export function ArticleListItem({ post }: ArticleListItemProps) {
|
||||
const { messages, lang } = useI18n();
|
||||
|
||||
return (
|
||||
<article className="bg-card rounded-xl shadow-lg hover:shadow-xl transition-shadow overflow-hidden">
|
||||
<div className="relative h-80 w-full">
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext } from "react";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
type TranslationContextValue = {
|
||||
messages: Messages;
|
||||
lang: string;
|
||||
};
|
||||
|
||||
const TranslationContext = createContext<TranslationContextValue | null>(null);
|
||||
|
||||
interface TranslationProviderProps extends TranslationContextValue {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function TranslationProvider({
|
||||
children,
|
||||
messages,
|
||||
lang,
|
||||
}: TranslationProviderProps) {
|
||||
return (
|
||||
<TranslationContext.Provider value={{ messages, lang }}>
|
||||
{children}
|
||||
</TranslationContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function useTranslationContext() {
|
||||
const context = useContext(TranslationContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"Translation hooks must be used within TranslationProvider"
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export function useMessages() {
|
||||
return useTranslationContext().messages;
|
||||
}
|
||||
|
||||
export function useLang() {
|
||||
return useTranslationContext().lang;
|
||||
}
|
||||
|
||||
export function useI18n() {
|
||||
return useTranslationContext();
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface FAQMessage {
|
||||
[key: string]: string;
|
||||
@@ -47,7 +49,6 @@ interface FAQSectionProps {
|
||||
showTitle?: boolean; // Whether to display the title
|
||||
titleClassName?: string; // Title style class
|
||||
lang?: string;
|
||||
messages: Messages;
|
||||
}
|
||||
// Control the level and style of the title through props, so it can be used on other pages as well as on a standalone page
|
||||
export default function FAQSection({
|
||||
@@ -55,8 +56,8 @@ export default function FAQSection({
|
||||
className = "",
|
||||
showTitle = true,
|
||||
titleClassName = "",
|
||||
messages,
|
||||
}: FAQSectionProps) {
|
||||
const messages = useMessages();
|
||||
const faqs = generateFAQs(messages);
|
||||
|
||||
// Set default styles for different scenarios
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Messages } from "@/types/messages";
|
||||
import { useI18n } from "@/components/providers/TranslationProvider";
|
||||
import { languageDisplayNames } from "@/constants/i18n-config";
|
||||
|
||||
interface FooterProps {
|
||||
messages: Messages;
|
||||
lang: string;
|
||||
}
|
||||
export function Footer() {
|
||||
const { messages, lang } = useI18n();
|
||||
|
||||
export function Footer({ messages, lang }: FooterProps) {
|
||||
return (
|
||||
<footer className="bg-background border-t mt-auto">
|
||||
<div className="container mx-auto px-4 py-6">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useI18n } from "@/components/providers/TranslationProvider";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -7,22 +8,14 @@ import { Button } from "@/components/ui/button";
|
||||
import Image from "next/image";
|
||||
import { Menu, X, Github } from "lucide-react";
|
||||
import LanguageSwitcher from "@/components/LanguageSwitcher";
|
||||
import { Messages } from "@/types/messages";
|
||||
import ThemeToggle from "@/components/web/ThemeToggle";
|
||||
|
||||
/**
|
||||
* Props interface for the Header component
|
||||
*/
|
||||
interface HeaderProps {
|
||||
messages: Messages;
|
||||
lang: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header component providing navigation, language switching, and GitHub link
|
||||
* Features responsive design with mobile menu support
|
||||
*/
|
||||
const Header = ({ messages, lang }: HeaderProps) => {
|
||||
const Header = () => {
|
||||
const { messages, lang } = useI18n();
|
||||
const pathname = usePathname();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Image from "next/image";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface PageContentProps {
|
||||
messages: Messages;
|
||||
}
|
||||
export default function HowItWorks() {
|
||||
const messages = useMessages();
|
||||
|
||||
export default function HowItWorks({ messages }: PageContentProps) {
|
||||
const steps = [
|
||||
{
|
||||
number: 1,
|
||||
title: messages!.text.HowItWorks.step1Title,
|
||||
description: messages!.text.HowItWorks.step1Description,
|
||||
title: messages.text.HowItWorks.step1Title,
|
||||
description: messages.text.HowItWorks.step1Description,
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
title: messages!.text.HowItWorks.step2Title,
|
||||
description: messages!.text.HowItWorks.step2Description,
|
||||
title: messages.text.HowItWorks.step2Title,
|
||||
description: messages.text.HowItWorks.step2Description,
|
||||
},
|
||||
{
|
||||
number: 3,
|
||||
title: messages!.text.HowItWorks.step3Title,
|
||||
description: messages!.text.HowItWorks.step3Description,
|
||||
title: messages.text.HowItWorks.step3Title,
|
||||
description: messages.text.HowItWorks.step3Description,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -33,7 +33,9 @@ export default function HowItWorks({ messages }: PageContentProps) {
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-6">
|
||||
{messages.text.HowItWorks.h2}
|
||||
</h2>
|
||||
<p className="text-muted-foreground mb-8">{messages.text.HowItWorks.h2Description}</p>
|
||||
<p className="text-muted-foreground mb-8">
|
||||
{messages.text.HowItWorks.h2Description}
|
||||
</p>
|
||||
<Button className="bg-gradient-to-r from-purple-500 to-blue-500 hover:from-purple-600 hover:to-blue-600 text-white rounded-full px-8 py-6 text-lg">
|
||||
{messages.text.HowItWorks.tryNowLabel}
|
||||
</Button>
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import Image from "next/image";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface KeyFeaturesProps {
|
||||
isInToolPage?: boolean; // Whether it is in the tool page (e.g. homepage)
|
||||
className?: string; // Custom style class
|
||||
showTitle?: boolean; // Whether to display the title
|
||||
titleClassName?: string; // Title style class
|
||||
messages: Messages;
|
||||
}
|
||||
|
||||
export default function KeyFeatures({
|
||||
export default function KeyFeatures({
|
||||
isInToolPage = false,
|
||||
className = "",
|
||||
showTitle = true,
|
||||
titleClassName = "",
|
||||
messages
|
||||
}: KeyFeaturesProps) {
|
||||
const messages = useMessages();
|
||||
|
||||
// Set container styles
|
||||
const containerClasses = `container mx-auto px-4 py-8 ${className}`;
|
||||
const defaultTitleClasses = "font-semibold mb-6";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
import Image from "next/image";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface PageContentProps {
|
||||
messages: Messages;
|
||||
}
|
||||
export default function SystemDiagram() {
|
||||
const messages = useMessages();
|
||||
|
||||
export default function SystemDiagram({ messages }: PageContentProps) {
|
||||
return (
|
||||
<section className="py-16 bg-background">
|
||||
<div className="container mx-auto px-4">
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import { getDictionary } from "@/lib/dictionary";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import { useMessages } from "@/components/providers/TranslationProvider";
|
||||
|
||||
interface ClipboardMessages {
|
||||
copiedSuccess?: string;
|
||||
@@ -22,44 +20,22 @@ interface ClipboardActions {
|
||||
}
|
||||
|
||||
export const useClipboardActions = (): ClipboardActions => {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages | null>(null);
|
||||
const [isLoadingMessages, setIsLoadingMessages] = useState<boolean>(true);
|
||||
const messages = useMessages();
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||
const [isPasted, setIsPasted] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [clipboardMessages, setClipboardMessages] = useState<ClipboardMessages>(
|
||||
{}
|
||||
const clipboardMessages = useMemo<ClipboardMessages>(
|
||||
() => ({
|
||||
copiedSuccess: messages.text.clipboard_btn.copiedLabel,
|
||||
pastedSuccess: messages.text.clipboard_btn.pastedLabel,
|
||||
copyError: "Failed to copy.",
|
||||
readError: "Failed to read clipboard.",
|
||||
loading: "Loading...",
|
||||
}),
|
||||
[messages]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoadingMessages(true);
|
||||
getDictionary(locale)
|
||||
.then((dict) => {
|
||||
setMessages(dict);
|
||||
setClipboardMessages({
|
||||
copiedSuccess: dict.text.clipboard_btn.copiedLabel,
|
||||
pastedSuccess: dict.text.clipboard_btn.pastedLabel,
|
||||
copyError: "Failed to copy.",
|
||||
readError: "Failed to read clipboard.",
|
||||
loading: "Loading...",
|
||||
});
|
||||
setIsLoadingMessages(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to load messages for useClipboardActions:", err);
|
||||
setError("Failed to load messages");
|
||||
setClipboardMessages({
|
||||
// Provide fallbacks even on error
|
||||
copyError: "Failed to copy.",
|
||||
readError: "Failed to read clipboard.",
|
||||
loading: "Loading...",
|
||||
});
|
||||
setIsLoadingMessages(false);
|
||||
});
|
||||
}, [locale]);
|
||||
|
||||
const copyText = useCallback(
|
||||
async (textToCopy: string) => {
|
||||
setError(null);
|
||||
@@ -158,7 +134,7 @@ export const useClipboardActions = (): ClipboardActions => {
|
||||
readClipboard,
|
||||
isCopied,
|
||||
isPasted,
|
||||
isLoadingMessages,
|
||||
isLoadingMessages: false,
|
||||
error,
|
||||
clipboardMessages,
|
||||
};
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { getDictionary } from "@/lib/dictionary";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import { useEffect } from "react";
|
||||
import { trackReferrer } from "@/lib/tracking";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface UsePageSetupProps {
|
||||
setRetrieveRoomId: (roomId: string) => void;
|
||||
@@ -15,27 +12,6 @@ export function usePageSetup({
|
||||
setActiveTab,
|
||||
retrieveJoinRoomBtnRef,
|
||||
}: UsePageSetupProps) {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages | null>(null);
|
||||
const [isLoadingMessages, setIsLoadingMessages] = useState(true);
|
||||
|
||||
// Load internationalization messages
|
||||
useEffect(() => {
|
||||
setIsLoadingMessages(true);
|
||||
getDictionary(locale)
|
||||
.then((dict) => {
|
||||
setMessages(dict);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to load messages:", error);
|
||||
// Optionally set some default/fallback messages or an error state
|
||||
setMessages(null); // Or some error indicator
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoadingMessages(false);
|
||||
});
|
||||
}, [locale]);
|
||||
|
||||
// Track referrer and handle URL 'roomId' parameter
|
||||
useEffect(() => {
|
||||
// Guard in SSR
|
||||
@@ -56,5 +32,4 @@ export function usePageSetup({
|
||||
}
|
||||
}, [setRetrieveRoomId, setActiveTab, retrieveJoinRoomBtnRef]); // Dependencies are stable setters and a ref
|
||||
|
||||
return { messages, isLoadingMessages };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user