refactor(i18n): convert remaining useMessages to useTranslations
- FAQSection: useTranslations with dynamic keys via type assertion - ClipboardApp: useTranslations for JSX, keep useMessages for hooks - SendTabPanel: useTranslations for html and roomStatus namespaces - RetrieveTabPanel: useTranslations for html, roomStatus, and ClipboardApp - FileListDisplay: useTranslations for FileListDisplay namespace - FileUploadHandler: useTranslations for fileUploadHandler namespace Only ClipboardApp.tsx retains useMessages for hooks requiring full messages object.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
import React, { useRef, useCallback, useEffect, useMemo } from "react";
|
||||
import { useMessages } from "next-intl";
|
||||
import { useMessages, useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useRichTextToPlainText from "../hooks/useRichTextToPlainText";
|
||||
import QRCodeComponent from "./ClipboardApp/ShareCard";
|
||||
@@ -19,7 +19,10 @@ import { getCachedId } from "@/lib/roomIdCache";
|
||||
import { useConnectionFeedback } from "@/hooks/useConnectionFeedback";
|
||||
|
||||
const ClipboardApp = () => {
|
||||
// Keep useMessages for hooks that need the full message object
|
||||
const messages = useMessages();
|
||||
// Use useTranslations for static keys in JSX
|
||||
const tHtml = useTranslations("text.ClipboardApp.html");
|
||||
const { shareMessage, retrieveMessage, putMessageInMs } =
|
||||
useClipboardAppMessages();
|
||||
|
||||
@@ -192,7 +195,7 @@ const ClipboardApp = () => {
|
||||
id="send-tab"
|
||||
aria-selected={activeTab === "send"}
|
||||
>
|
||||
{messages.text.ClipboardApp.html.senderTab}
|
||||
{tHtml("senderTab")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={activeTab === "retrieve" ? "default" : "outline"}
|
||||
@@ -202,15 +205,15 @@ const ClipboardApp = () => {
|
||||
id="retrieve-tab"
|
||||
aria-selected={activeTab === "retrieve"}
|
||||
>
|
||||
{messages.text.ClipboardApp.html.retrieveTab}
|
||||
{tHtml("retrieveTab")}
|
||||
</Button>
|
||||
</div>
|
||||
<Card className="border-4 sm:border-8 shadow-md">
|
||||
<CardHeader className="px-3 sm:px-6 py-3 sm:py-6">
|
||||
<CardTitle className="text-lg sm:text-xl">
|
||||
{activeTab === "send"
|
||||
? messages.text.ClipboardApp.html.shareTitleLabel
|
||||
: messages.text.ClipboardApp.html.retrieveTitleLabel}
|
||||
? tHtml("shareTitleLabel")
|
||||
: tHtml("retrieveTitleLabel")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-3 sm:px-6">
|
||||
@@ -250,7 +253,7 @@ const ClipboardApp = () => {
|
||||
<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">
|
||||
{messages.text.ClipboardApp.html.retrieveMethodTitle}
|
||||
{tHtml("retrieveMethodTitle")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0 px-3 sm:px-6">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useMessages } from "next-intl";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Download, Trash2 } from "lucide-react";
|
||||
import { Tooltip } from "@/components/Tooltip";
|
||||
@@ -66,7 +66,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
saveType,
|
||||
largeFileThreshold = 500 * 1024 * 1024, // 500MB default
|
||||
}) => {
|
||||
const messages = useMessages();
|
||||
const t = useTranslations("text.FileListDisplay");
|
||||
|
||||
// Get the cleaning method of the store
|
||||
const { clearSendProgress, clearReceiveProgress } = useFileTransferStore();
|
||||
@@ -300,25 +300,20 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
// Get download count
|
||||
const downloadCount = downloadCounts[item.fileId] || 0;
|
||||
|
||||
if (messages === null) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 min-w-0 flex-shrink-0">
|
||||
{progress && progress.progress < 1 ? ( //Show progress or completed
|
||||
<div className="w-full sm:w-auto">
|
||||
<TransferProgress
|
||||
message={
|
||||
mode === "sender"
|
||||
? messages.text.FileListDisplay.sendingLabel
|
||||
: messages.text.FileListDisplay.receivingLabel
|
||||
mode === "sender" ? t("sendingLabel") : t("receivingLabel")
|
||||
}
|
||||
progress={progress}
|
||||
/>
|
||||
</div>
|
||||
) : showCompletion ? (
|
||||
<span className="text-sm text-green-500 whitespace-nowrap">
|
||||
{messages.text.FileListDisplay.finishedLabel}
|
||||
{t("finishedLabel")}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -342,7 +337,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
{/* display download Num*/}
|
||||
{mode === "sender" && (
|
||||
<span className="text-xs sm:text-sm whitespace-nowrap">
|
||||
{messages.text.FileListDisplay.downloadCountLabel}: {downloadCount}
|
||||
{t("downloadCountLabel")}: {downloadCount}
|
||||
</span>
|
||||
)}
|
||||
{mode === "sender" && onDelete && (
|
||||
@@ -360,9 +355,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
className="text-xs sm:text-sm px-2 sm:px-3"
|
||||
>
|
||||
<Trash2 className="h-3 w-3 sm:h-4 sm:w-4 sm:mr-2" />
|
||||
<span className="hidden sm:inline">
|
||||
{messages.text.FileListDisplay.deleteLabel}
|
||||
</span>
|
||||
<span className="hidden sm:inline">{t("deleteLabel")}</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -375,7 +368,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
const formatSize = formatFileSize(item.size);
|
||||
const tooltipContent = isFolder
|
||||
? `${formatFolderTips(
|
||||
messages!.text.FileListDisplay.folderSummaryTemplate,
|
||||
t("folderSummaryTemplate"),
|
||||
item.name,
|
||||
item.fileCount || 0,
|
||||
formatSize
|
||||
@@ -398,7 +391,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
<span className="text-xs sm:text-sm text-muted-foreground">
|
||||
{isFolder
|
||||
? `${formatFolderDis(
|
||||
messages!.text.FileListDisplay.folderInlineTemplate,
|
||||
t("folderInlineTemplate"),
|
||||
item.fileCount || 0,
|
||||
formatSize
|
||||
)}`
|
||||
@@ -412,9 +405,6 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
if (messages === null) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{(singleFiles.length > 0 || folders.length > 0) && (
|
||||
@@ -424,17 +414,13 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
<div className="mb-2">
|
||||
<AutoPopupDialog
|
||||
storageKey="Choose-location-popup-shown"
|
||||
title={messages.text.FileListDisplay.popupDialogTitle}
|
||||
description={
|
||||
messages.text.FileListDisplay.popupDialogDescription
|
||||
}
|
||||
title={t("popupDialogTitle")}
|
||||
description={t("popupDialogDescription")}
|
||||
condition={() => needPickLocation}
|
||||
/>
|
||||
{/* Regular reminder to select the save directory */}
|
||||
<div className="flex items-center">
|
||||
<p className="text-red-500 mb-2">
|
||||
{messages.text.FileListDisplay.chooseSavePathTip}
|
||||
</p>
|
||||
<p className="text-red-500 mb-2">{t("chooseSavePathTip")}</p>
|
||||
{onLocationPick && (
|
||||
<Button
|
||||
onClick={async () => {
|
||||
@@ -445,7 +431,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
size="sm"
|
||||
className="mr-2 text-red-500"
|
||||
>
|
||||
{messages.text.FileListDisplay.chooseSavePathLabel}
|
||||
{t("chooseSavePathLabel")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, {
|
||||
useRef,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { useMessages } from "next-intl";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Upload } from "lucide-react";
|
||||
import { FileMeta, CustomFile } from "@/types/webrtc";
|
||||
@@ -41,19 +41,17 @@ interface FileUploadHandlerProps {
|
||||
const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
onFilePicked,
|
||||
}) => {
|
||||
const messages = useMessages();
|
||||
const t = useTranslations("text.fileUploadHandler");
|
||||
|
||||
const folderInputRef = useRef<HTMLInputElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
// File selector -- message prompt
|
||||
const [fileText, setFileText] = useState<string>(
|
||||
messages.text.fileUploadHandler.noFileChosenTip
|
||||
);
|
||||
const [fileText, setFileText] = useState<string>(t("noFileChosenTip"));
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setFileText(messages.text.fileUploadHandler.noFileChosenTip);
|
||||
}, [messages.text.fileUploadHandler.noFileChosenTip]);
|
||||
setFileText(t("noFileChosenTip"));
|
||||
}, [t]);
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(newFiles: CustomFile[]) => {
|
||||
@@ -64,16 +62,13 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
const folderNum = newFiles.filter((file) => file.folderName).length;
|
||||
|
||||
const choose_dis = formatFileChosen(
|
||||
messages!.text.fileUploadHandler.fileChosenTemplate,
|
||||
t("fileChosenTemplate"),
|
||||
fileNum,
|
||||
folderNum
|
||||
);
|
||||
|
||||
setFileText(choose_dis);
|
||||
setTimeout(
|
||||
() => setFileText(messages!.text.fileUploadHandler.noFileChosenTip),
|
||||
2000
|
||||
);
|
||||
setTimeout(() => setFileText(t("noFileChosenTip")), 2000);
|
||||
// Reset the file input
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
@@ -82,7 +77,7 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
folderInputRef.current.value = "";
|
||||
}
|
||||
},
|
||||
[messages, onFilePicked]
|
||||
[t, onFilePicked]
|
||||
);
|
||||
|
||||
// Click to upload file processing
|
||||
@@ -148,7 +143,7 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
onClick={handleZoneClick}
|
||||
>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{messages.text.fileUploadHandler.chooseFileTip}
|
||||
{t("chooseFileTip")}
|
||||
</p>
|
||||
<Upload className="h-12 w-12 mx-auto mb-4 text-primary" />
|
||||
<p className="text-sm text-muted-foreground">{fileText}</p>
|
||||
@@ -177,10 +172,10 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl font-semibold">
|
||||
{messages.text.fileUploadHandler.chosenDiagTitle}
|
||||
{t("chosenDiagTitle")}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="mt-2 text-muted-foreground">
|
||||
{messages.text.fileUploadHandler.chosenDiagDescription}
|
||||
{t("chosenDiagDescription")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex justify-center gap-4 mt-6">
|
||||
@@ -188,13 +183,13 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
onClick={handleSelectFile}
|
||||
className="px-4 py-2 rounded transition-colors bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
{messages.text.fileUploadHandler.selectFileLabel}
|
||||
{t("selectFileLabel")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSelectFolder}
|
||||
className="px-4 py-2 rounded transition-colors bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||
>
|
||||
{messages.text.fileUploadHandler.selectFolderLabel}
|
||||
{t("selectFolderLabel")}
|
||||
</button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useMessages } from "next-intl";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -48,7 +48,9 @@ export function RetrieveTabPanel({
|
||||
retrieveMessage,
|
||||
handleLeaveRoom,
|
||||
}: RetrieveTabPanelProps) {
|
||||
const messages = useMessages();
|
||||
const tHtml = useTranslations("text.ClipboardApp.html");
|
||||
const tRoomStatus = useTranslations("text.ClipboardApp.roomStatus");
|
||||
const t = useTranslations("text.ClipboardApp");
|
||||
// Get the status from the store
|
||||
const {
|
||||
retrieveRoomStatusText,
|
||||
@@ -61,25 +63,24 @@ export function RetrieveTabPanel({
|
||||
} = useFileTransferStore();
|
||||
|
||||
const onLocationPick = useCallback(async (): Promise<boolean> => {
|
||||
if (!messages) return false; // Should not happen if panel is rendered
|
||||
if (!window.showDirectoryPicker) {
|
||||
putMessageInMs(messages.text.ClipboardApp.pickSaveUnsupported, false);
|
||||
putMessageInMs(t("pickSaveUnsupported"), false);
|
||||
return false;
|
||||
}
|
||||
if (!window.confirm(messages.text.ClipboardApp.pickSaveMsg)) return false;
|
||||
if (!window.confirm(t("pickSaveMsg"))) return false;
|
||||
try {
|
||||
const directoryHandle = await window.showDirectoryPicker();
|
||||
await setReceiverDirectoryHandle(directoryHandle);
|
||||
putMessageInMs(messages.text.ClipboardApp.pickSaveSuccess, false);
|
||||
putMessageInMs(t("pickSaveSuccess"), false);
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
if (err.name !== "AbortError") {
|
||||
console.error("Failed to set up folder receive:", err);
|
||||
putMessageInMs(messages.text.ClipboardApp.pickSaveError, false);
|
||||
putMessageInMs(t("pickSaveError"), false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, [messages, putMessageInMs, setReceiverDirectoryHandle]);
|
||||
}, [t, putMessageInMs, setReceiverDirectoryHandle]);
|
||||
|
||||
const handleFileRequestFromPanel = useCallback(
|
||||
(meta: FileMeta) => {
|
||||
@@ -100,15 +101,15 @@ export function RetrieveTabPanel({
|
||||
<div className="mb-3 text-sm text-muted-foreground">
|
||||
{retrieveRoomStatusText ||
|
||||
(isReceiverInRoom
|
||||
? messages.text.ClipboardApp.roomStatus.connectedLabel
|
||||
: messages.text.ClipboardApp.roomStatus.receiverEmptyMessage)}
|
||||
? tRoomStatus("connectedLabel")
|
||||
: tRoomStatus("receiverEmptyMessage"))}
|
||||
</div>
|
||||
<div className="space-y-3 mb-4">
|
||||
{/* Room ID input section */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<ReadClipboardButton
|
||||
title={messages.text.ClipboardApp.html.readClipboardLabel}
|
||||
title={tHtml("readClipboardLabel")}
|
||||
onRead={setRetrieveRoomIdInput}
|
||||
/>
|
||||
{/* Save/Use Cached ID Button placed after Paste button */}
|
||||
@@ -122,9 +123,7 @@ export function RetrieveTabPanel({
|
||||
aria-label="Retrieve Room ID"
|
||||
value={retrieveRoomIdInput}
|
||||
onChange={(e) => setRetrieveRoomIdInput(e.target.value)}
|
||||
placeholder={
|
||||
messages.text.ClipboardApp.html.retrieveRoomIdPlaceholder
|
||||
}
|
||||
placeholder={tHtml("retrieveRoomIdPlaceholder")}
|
||||
className="flex-1 min-w-0"
|
||||
/>
|
||||
</div>
|
||||
@@ -138,7 +137,7 @@ export function RetrieveTabPanel({
|
||||
ref={retrieveJoinRoomBtnRef}
|
||||
disabled={isReceiverInRoom || !retrieveRoomIdInput.trim()}
|
||||
>
|
||||
{messages.text.ClipboardApp.html.joinRoomLabel}
|
||||
{tHtml("joinRoomLabel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={isAnyFileTransferring ? "destructive" : "outline"}
|
||||
@@ -147,8 +146,8 @@ export function RetrieveTabPanel({
|
||||
className="w-full sm:w-auto px-4 order-2"
|
||||
>
|
||||
{isAnyFileTransferring
|
||||
? messages.text.ClipboardApp.roomStatus.leaveRoomLabel + " ⚠️"
|
||||
: messages.text.ClipboardApp.roomStatus.leaveRoomLabel}
|
||||
? tRoomStatus("leaveRoomLabel") + " ⚠️"
|
||||
: tRoomStatus("leaveRoomLabel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,7 +158,7 @@ export function RetrieveTabPanel({
|
||||
</div>
|
||||
<div className="flex justify-start">
|
||||
<WriteClipboardButton
|
||||
title={messages.text.ClipboardApp.html.copyLabel}
|
||||
title={tHtml("copyLabel")}
|
||||
textToCopy={richTextToPlainText(retrievedContent)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useMessages } from "next-intl";
|
||||
import { useTranslations } from "next-intl";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -59,7 +59,8 @@ export function SendTabPanel({
|
||||
handleLeaveSenderRoom,
|
||||
putMessageInMs,
|
||||
}: SendTabPanelProps) {
|
||||
const messages = useMessages();
|
||||
const tHtml = useTranslations("text.ClipboardApp.html");
|
||||
const tRoomStatus = useTranslations("text.ClipboardApp.roomStatus");
|
||||
// Get the status from the store
|
||||
const {
|
||||
shareContent,
|
||||
@@ -138,17 +139,17 @@ export function SendTabPanel({
|
||||
<div className="mb-3 text-sm text-muted-foreground">
|
||||
{shareRoomStatusText ||
|
||||
(isSenderInRoom
|
||||
? messages.text.ClipboardApp.roomStatus.onlyOneMessage
|
||||
: messages.text.ClipboardApp.roomStatus.senderEmptyMessage)}
|
||||
? tRoomStatus("onlyOneMessage")
|
||||
: tRoomStatus("senderEmptyMessage"))}
|
||||
</div>
|
||||
<RichTextEditor value={shareContent} onChange={updateShareContent} />
|
||||
<div className="flex flex-col sm:flex-row gap-2 my-3">
|
||||
<ReadClipboardButton
|
||||
title={messages.text.ClipboardApp.html.pasteLabel}
|
||||
title={tHtml("pasteLabel")}
|
||||
onRead={updateShareContent}
|
||||
/>
|
||||
<WriteClipboardButton
|
||||
title={messages.text.ClipboardApp.html.copyLabel}
|
||||
title={tHtml("copyLabel")}
|
||||
textToCopy={richTextToPlainText(shareContent)}
|
||||
/>
|
||||
</div>
|
||||
@@ -166,7 +167,7 @@ export function SendTabPanel({
|
||||
{/* Room ID input section */}
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{messages.text.ClipboardApp.html.inputRoomIdTip}
|
||||
{tHtml("inputRoomIdTip")}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Input
|
||||
@@ -175,9 +176,7 @@ export function SendTabPanel({
|
||||
onChange={handleInputChange}
|
||||
onPaste={handlePaste}
|
||||
className="flex-1 min-w-0"
|
||||
placeholder={
|
||||
messages.text.ClipboardApp.html.retrieveRoomIdPlaceholder
|
||||
}
|
||||
placeholder={tHtml("retrieveRoomIdPlaceholder")}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -186,8 +185,8 @@ export function SendTabPanel({
|
||||
disabled={isSenderInRoom}
|
||||
>
|
||||
{isSimpleIdMode
|
||||
? messages.text.ClipboardApp.html.generateRandomIdTip
|
||||
: messages.text.ClipboardApp.html.generateSimpleIdTip}
|
||||
? tHtml("generateRandomIdTip")
|
||||
: tHtml("generateSimpleIdTip")}
|
||||
</Button>
|
||||
{/* Save/Use Cached ID Button in between */}
|
||||
<CachedIdActionButton
|
||||
@@ -206,7 +205,7 @@ export function SendTabPanel({
|
||||
onClick={() => joinRoom(true, inputFieldValue.trim())}
|
||||
disabled={isSenderInRoom || !inputFieldValue.trim()}
|
||||
>
|
||||
{messages.text.ClipboardApp.html.joinRoomLabel}
|
||||
{tHtml("joinRoomLabel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -216,9 +215,7 @@ export function SendTabPanel({
|
||||
<AnimatedButton
|
||||
className="flex-1 order-1"
|
||||
onClick={generateShareLinkAndBroadcast}
|
||||
loadingText={
|
||||
messages.text.ClipboardApp.html.syncSendingLoadingLabel
|
||||
}
|
||||
loadingText={tHtml("syncSendingLoadingLabel")}
|
||||
disabled={
|
||||
!isSenderInRoom ||
|
||||
(sendFiles.length === 0 && shareContent.trim() === "") ||
|
||||
@@ -226,7 +223,7 @@ export function SendTabPanel({
|
||||
isAnyFileTransferring
|
||||
}
|
||||
>
|
||||
{messages.text.ClipboardApp.html.syncSendingLabel}
|
||||
{tHtml("syncSendingLabel")}
|
||||
</AnimatedButton>
|
||||
<Button
|
||||
variant={isAnyFileTransferring ? "destructive" : "outline"}
|
||||
@@ -235,8 +232,8 @@ export function SendTabPanel({
|
||||
className="w-full sm:w-auto px-4 order-2"
|
||||
>
|
||||
{isAnyFileTransferring
|
||||
? messages.text.ClipboardApp.roomStatus.leaveRoomLabel + " ⚠️"
|
||||
: messages.text.ClipboardApp.roomStatus.leaveRoomLabel}
|
||||
? tRoomStatus("leaveRoomLabel") + " ⚠️"
|
||||
: tRoomStatus("leaveRoomLabel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useMessages } from "next-intl";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@@ -8,40 +8,13 @@ import {
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
|
||||
interface FAQMessage {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface FAQ {
|
||||
question: string;
|
||||
answer: string;
|
||||
}
|
||||
|
||||
const generateFAQs = (messages: { text: { faqs: FAQMessage } }): FAQ[] => {
|
||||
const faqs: FAQ[] = [];
|
||||
const faqsData = messages.text.faqs;
|
||||
|
||||
// Get the total number of questions (by finding keys starting with question_)
|
||||
const questionKeys = Object.keys(faqsData).filter((key) =>
|
||||
key.startsWith("question_")
|
||||
);
|
||||
|
||||
// Automatically generate FAQ array based on the number of questions
|
||||
questionKeys.forEach((qKey) => {
|
||||
const index = qKey.split("_")[1]; // Get the numeric index
|
||||
const aKey = `answer_${index}`;
|
||||
|
||||
if (faqsData[aKey]) {
|
||||
// Ensure the corresponding answer exists
|
||||
faqs.push({
|
||||
question: faqsData[qKey],
|
||||
answer: faqsData[aKey],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return faqs;
|
||||
};
|
||||
// Static FAQ count based on messages structure (indices 0-13)
|
||||
const FAQ_COUNT = 14;
|
||||
|
||||
interface FAQSectionProps {
|
||||
isInToolPage?: boolean; // Whether it is in the tool page (e.g. homepage)
|
||||
@@ -57,8 +30,19 @@ export default function FAQSection({
|
||||
showTitle = true,
|
||||
titleClassName = "",
|
||||
}: FAQSectionProps) {
|
||||
const messages = useMessages();
|
||||
const faqs = generateFAQs(messages);
|
||||
const t = useTranslations("text.faqs");
|
||||
|
||||
// Generate FAQs using useTranslations with indexed keys
|
||||
// We use type assertion since next-intl doesn't support dynamic keys in type system
|
||||
const faqs: FAQ[] = [];
|
||||
for (let i = 0; i < FAQ_COUNT; i++) {
|
||||
const question = t(`question_${i}` as never);
|
||||
const answer = t(`answer_${i}` as never);
|
||||
// Only add if both question and answer exist (not fallback keys)
|
||||
if (question && answer && !question.startsWith("question_")) {
|
||||
faqs.push({ question, answer });
|
||||
}
|
||||
}
|
||||
|
||||
// Set default styles for different scenarios
|
||||
const containerClasses = `container mx-auto px-4 py-8 ${className}`;
|
||||
@@ -69,13 +53,9 @@ export default function FAQSection({
|
||||
<div className={containerClasses}>
|
||||
{showTitle &&
|
||||
(isInToolPage ? (
|
||||
<h2 className={`text-3xl ${titleClasses}`}>
|
||||
{messages.text.faqs.faqLabel}
|
||||
</h2>
|
||||
<h2 className={`text-3xl ${titleClasses}`}>{t("faqLabel")}</h2>
|
||||
) : (
|
||||
<h1 className={`text-4xl ${titleClasses}`}>
|
||||
{messages.text.faqs.faqLabel}
|
||||
</h1>
|
||||
<h1 className={`text-4xl ${titleClasses}`}>{t("faqLabel")}</h1>
|
||||
))}
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{faqs.map((faq, index) => (
|
||||
|
||||
Reference in New Issue
Block a user