diff options
Diffstat (limited to 'apps/web/components/settings')
| -rw-r--r-- | apps/web/components/settings/AISettings.tsx | 34 | ||||
| -rw-r--r-- | apps/web/components/settings/AddApiKey.tsx | 32 | ||||
| -rw-r--r-- | apps/web/components/settings/ApiKeySettings.tsx | 14 | ||||
| -rw-r--r-- | apps/web/components/settings/ChangePassword.tsx | 20 | ||||
| -rw-r--r-- | apps/web/components/settings/DeleteApiKey.tsx | 4 | ||||
| -rw-r--r-- | apps/web/components/settings/FeedSettings.tsx | 35 | ||||
| -rw-r--r-- | apps/web/components/settings/ImportExport.tsx | 20 | ||||
| -rw-r--r-- | apps/web/components/settings/UserDetails.tsx | 10 | ||||
| -rw-r--r-- | apps/web/components/settings/UserOptions.tsx | 55 | ||||
| -rw-r--r-- | apps/web/components/settings/sidebar/ModileSidebar.tsx | 4 | ||||
| -rw-r--r-- | apps/web/components/settings/sidebar/Sidebar.tsx | 4 | ||||
| -rw-r--r-- | apps/web/components/settings/sidebar/items.tsx | 19 |
12 files changed, 176 insertions, 75 deletions
diff --git a/apps/web/components/settings/AISettings.tsx b/apps/web/components/settings/AISettings.tsx index 0a8db147..79a9e558 100644 --- a/apps/web/components/settings/AISettings.tsx +++ b/apps/web/components/settings/AISettings.tsx @@ -20,6 +20,7 @@ import { } from "@/components/ui/select"; import { toast } from "@/components/ui/use-toast"; import { useClientConfig } from "@/lib/clientConfig"; +import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { zodResolver } from "@hookform/resolvers/zod"; import { Plus, Save, Trash2 } from "lucide-react"; @@ -34,6 +35,7 @@ import { } from "@hoarder/shared/types/prompts"; export function PromptEditor() { + const { t } = useTranslation(); const apiUtils = api.useUtils(); const form = useForm<z.infer<typeof zNewPromptSchema>>({ @@ -117,7 +119,7 @@ export function PromptEditor() { className="items-center" > <Plus className="mr-2 size-4" /> - Add + {t("actions.add")} </ActionButton> </form> </Form> @@ -125,6 +127,7 @@ export function PromptEditor() { } export function PromptRow({ prompt }: { prompt: ZPrompt }) { + const { t } = useTranslation(); const apiUtils = api.useUtils(); const { mutateAsync: updatePrompt, isPending: isUpdating } = api.prompts.update.useMutation({ @@ -169,11 +172,7 @@ export function PromptRow({ prompt }: { prompt: ZPrompt }) { return ( <FormItem className="hidden"> <FormControl> - <Input - placeholder="Add a custom prompt" - type="hidden" - {...field} - /> + <Input type="hidden" {...field} /> </FormControl> <FormMessage /> </FormItem> @@ -234,7 +233,7 @@ export function PromptRow({ prompt }: { prompt: ZPrompt }) { className="items-center" > <Save className="mr-2 size-4" /> - Save + {t("actions.save")} </ActionButton> <ActionButton loading={isDeleting} @@ -244,7 +243,7 @@ export function PromptRow({ prompt }: { prompt: ZPrompt }) { type="button" > <Trash2 className="mr-2 size-4" /> - Delete + {t("actions.delete")} </ActionButton> </form> </Form> @@ -252,15 +251,16 @@ export function PromptRow({ prompt }: { prompt: ZPrompt }) { } export function TaggingRules() { + const { t } = useTranslation(); const { data: prompts, isLoading } = api.prompts.list.useQuery(); return ( <div className="mt-2 flex flex-col gap-2"> - <div className="w-full text-xl font-medium sm:w-1/3">Tagging Rules</div> + <div className="w-full text-xl font-medium sm:w-1/3"> + {t("settings.ai.tagging_rules")} + </div> <p className="mb-1 text-xs italic text-muted-foreground"> - Prompts that you add here will be included as rules to the model during - tag generation. You can view the final prompts in the prompt preview - section. + {t("settings.ai.tagging_rule_description")} </p> {isLoading && <FullPageSpinner />} {prompts && prompts.length == 0 && ( @@ -276,14 +276,15 @@ export function TaggingRules() { } export function PromptDemo() { + const { t } = useTranslation(); const { data: prompts } = api.prompts.list.useQuery(); const clientConfig = useClientConfig(); return ( <div className="flex flex-col gap-2"> <div className="mb-4 w-full text-xl font-medium sm:w-1/3"> - Prompt Preview + {t("settings.ai.prompt_preview")} </div> - <p>Text Prompt</p> + <p>{t("settings.ai.text_prompt")}</p> <code className="whitespace-pre-wrap rounded-md bg-muted p-3 text-sm text-muted-foreground"> {buildTextPrompt( clientConfig.inference.inferredTagLang, @@ -294,7 +295,7 @@ export function PromptDemo() { /* context length */ 1024 /* The value here doesn't matter */, ).trim()} </code> - <p>Image Prompt</p> + <p>{t("settings.ai.images_prompt")}</p> <code className="whitespace-pre-wrap rounded-md bg-muted p-3 text-sm text-muted-foreground"> {buildImagePrompt( clientConfig.inference.inferredTagLang, @@ -308,12 +309,13 @@ export function PromptDemo() { } export default function AISettings() { + const { t } = useTranslation(); return ( <> <div className="rounded-md border bg-background p-4"> <div className="mb-2 flex flex-col gap-3"> <div className="w-full text-2xl font-medium sm:w-1/3"> - AI Settings + {t("settings.ai.ai_settings")} </div> <TaggingRules /> </div> diff --git a/apps/web/components/settings/AddApiKey.tsx b/apps/web/components/settings/AddApiKey.tsx index 34fd2df7..00e70d3f 100644 --- a/apps/web/components/settings/AddApiKey.tsx +++ b/apps/web/components/settings/AddApiKey.tsx @@ -27,17 +27,18 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { toast } from "@/components/ui/use-toast"; +import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; function ApiKeySuccess({ apiKey }: { apiKey: string }) { + const { t } = useTranslation(); return ( <div> <div className="py-4"> - Note: please copy the key and store it somewhere safe. Once you close - the dialog, you won't be able to access it again. + {t("settings.api_keys.key_success_please_copy")} </div> <div className="flex space-x-2 pt-2"> <Input value={apiKey} readOnly /> @@ -52,6 +53,7 @@ function ApiKeySuccess({ apiKey }: { apiKey: string }) { } function AddApiKeyForm({ onSuccess }: { onSuccess: (key: string) => void }) { + const { t } = useTranslation(); const formSchema = z.object({ name: z.string(), }); @@ -62,7 +64,10 @@ function AddApiKeyForm({ onSuccess }: { onSuccess: (key: string) => void }) { router.refresh(); }, onError: () => { - toast({ description: "Something went wrong", variant: "destructive" }); + toast({ + description: t("common.something_went_wrong"), + variant: "destructive", + }); }, }); @@ -95,12 +100,16 @@ function AddApiKeyForm({ onSuccess }: { onSuccess: (key: string) => void }) { render={({ field }) => { return ( <FormItem className="flex-1"> - <FormLabel>Name</FormLabel> + <FormLabel>{t("common.name")}</FormLabel> <FormControl> - <Input type="text" placeholder="Name" {...field} /> + <Input + type="text" + placeholder={t("common.name")} + {...field} + /> </FormControl> <FormDescription> - Give your API key a unique name + {t("settings.api_keys.new_api_key_desc")} </FormDescription> <FormMessage /> </FormItem> @@ -112,7 +121,7 @@ function AddApiKeyForm({ onSuccess }: { onSuccess: (key: string) => void }) { type="submit" loading={mutator.isPending} > - Create + {t("actions.create")} </ActionButton> </form> </Form> @@ -120,17 +129,20 @@ function AddApiKeyForm({ onSuccess }: { onSuccess: (key: string) => void }) { } export default function AddApiKey() { + const { t } = useTranslation(); const [key, setKey] = useState<string | undefined>(undefined); const [dialogOpen, setDialogOpen] = useState<boolean>(false); return ( <Dialog open={dialogOpen} onOpenChange={setDialogOpen}> <DialogTrigger asChild> - <Button>New API Key</Button> + <Button>{t("settings.api_keys.new_api_key")}</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle> - {key ? "Key was successfully created" : "Create API key"} + {key + ? t("settings.api_keys.key_success") + : t("settings.api_keys.new_api_key")} </DialogTitle> <DialogDescription> {key ? ( @@ -147,7 +159,7 @@ export default function AddApiKey() { variant="outline" onClick={() => setKey(undefined)} > - Close + {t("actions.close")} </Button> </DialogClose> </DialogFooter> diff --git a/apps/web/components/settings/ApiKeySettings.tsx b/apps/web/components/settings/ApiKeySettings.tsx index 4d43be7a..8f07e5a4 100644 --- a/apps/web/components/settings/ApiKeySettings.tsx +++ b/apps/web/components/settings/ApiKeySettings.tsx @@ -6,27 +6,31 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { useTranslation } from "@/lib/i18n/server"; import { api } from "@/server/api/client"; import AddApiKey from "./AddApiKey"; import DeleteApiKey from "./DeleteApiKey"; export default async function ApiKeys() { + const { t } = await useTranslation(); const keys = await api.apiKeys.list(); return ( <div> <div className="flex items-center justify-between"> - <div className="mb-2 text-lg font-medium">API Keys</div> + <div className="mb-2 text-lg font-medium"> + {t("settings.api_keys.api_keys")} + </div> <AddApiKey /> </div> <div className="mt-2"> <Table> <TableHeader> <TableRow> - <TableHead>Name</TableHead> - <TableHead>Key</TableHead> - <TableHead>Created At</TableHead> - <TableHead>Action</TableHead> + <TableHead>{t("common.name")}</TableHead> + <TableHead>{t("common.key")}</TableHead> + <TableHead>{t("common.created_at")}</TableHead> + <TableHead>{t("common.action")}</TableHead> </TableRow> </TableHeader> <TableBody> diff --git a/apps/web/components/settings/ChangePassword.tsx b/apps/web/components/settings/ChangePassword.tsx index aa27f223..e9f426a6 100644 --- a/apps/web/components/settings/ChangePassword.tsx +++ b/apps/web/components/settings/ChangePassword.tsx @@ -12,6 +12,7 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { toast } from "@/components/ui/use-toast"; +import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; @@ -19,6 +20,7 @@ import { useForm } from "react-hook-form"; import { zChangePasswordSchema } from "@hoarder/shared/types/users"; export function ChangePassword() { + const { t } = useTranslation(); const form = useForm<z.infer<typeof zChangePasswordSchema>>({ resolver: zodResolver(zChangePasswordSchema), defaultValues: { @@ -55,7 +57,7 @@ export function ChangePassword() { return ( <div className="flex flex-col sm:flex-row"> <div className="mb-4 w-full text-lg font-medium sm:w-1/3"> - Change Password + {t("settings.info.change_password")} </div> <Form {...form}> <form @@ -68,11 +70,11 @@ export function ChangePassword() { render={({ field }) => { return ( <FormItem className="flex-1"> - <FormLabel>Current Password</FormLabel> + <FormLabel>{t("settings.info.current_password")}</FormLabel> <FormControl> <Input type="password" - placeholder="Current Password" + placeholder={t("settings.info.current_password")} {...field} /> </FormControl> @@ -87,11 +89,11 @@ export function ChangePassword() { render={({ field }) => { return ( <FormItem className="flex-1"> - <FormLabel>New Password</FormLabel> + <FormLabel>{t("settings.info.new_password")}</FormLabel> <FormControl> <Input type="password" - placeholder="New Password" + placeholder={t("settings.info.new_password")} {...field} /> </FormControl> @@ -106,11 +108,13 @@ export function ChangePassword() { render={({ field }) => { return ( <FormItem className="flex-1"> - <FormLabel>Confirm New Password</FormLabel> + <FormLabel> + {t("settings.info.confirm_new_password")} + </FormLabel> <FormControl> <Input type="Password" - placeholder="Confirm New Password" + placeholder={t("settings.info.confirm_new_password")} {...field} /> </FormControl> @@ -124,7 +128,7 @@ export function ChangePassword() { type="submit" loading={mutator.isPending} > - Save + {t("actions.save")} </ActionButton> </form> </Form> diff --git a/apps/web/components/settings/DeleteApiKey.tsx b/apps/web/components/settings/DeleteApiKey.tsx index e2334c44..4efb7ea8 100644 --- a/apps/web/components/settings/DeleteApiKey.tsx +++ b/apps/web/components/settings/DeleteApiKey.tsx @@ -5,6 +5,7 @@ import { ActionButton } from "@/components/ui/action-button"; import ActionConfirmingDialog from "@/components/ui/action-confirming-dialog"; import { Button } from "@/components/ui/button"; import { toast } from "@/components/ui/use-toast"; +import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { Trash } from "lucide-react"; @@ -15,6 +16,7 @@ export default function DeleteApiKey({ name: string; id: string; }) { + const { t } = useTranslation(); const router = useRouter(); const mutator = api.apiKeys.revoke.useMutation({ onSuccess: () => { @@ -43,7 +45,7 @@ export default function DeleteApiKey({ mutator.mutate({ id }, { onSuccess: () => setDialogOpen(false) }) } > - Delete + {t("actions.delete")} </ActionButton> )} > diff --git a/apps/web/components/settings/FeedSettings.tsx b/apps/web/components/settings/FeedSettings.tsx index 4880132c..e3999cb5 100644 --- a/apps/web/components/settings/FeedSettings.tsx +++ b/apps/web/components/settings/FeedSettings.tsx @@ -14,6 +14,7 @@ import { import { FullPageSpinner } from "@/components/ui/full-page-spinner"; import { Input } from "@/components/ui/input"; import { toast } from "@/components/ui/use-toast"; +import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; import { cn } from "@/lib/utils"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -59,6 +60,7 @@ import { import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; export function FeedsEditorDialog() { + const { t } = useTranslation(); const [open, setOpen] = React.useState(false); const apiUtils = api.useUtils(); @@ -92,7 +94,7 @@ export function FeedsEditorDialog() { <DialogTrigger asChild> <Button> <Plus className="mr-2 size-4" /> - Add a Subscription + {t("settings.feeds.add_a_subscription")} </Button> </DialogTrigger> <DialogContent> @@ -164,6 +166,7 @@ export function FeedsEditorDialog() { } export function EditFeedDialog({ feed }: { feed: ZFeed }) { + const { t } = useTranslation(); const apiUtils = api.useUtils(); const [open, setOpen] = React.useState(false); React.useEffect(() => { @@ -198,7 +201,7 @@ export function EditFeedDialog({ feed }: { feed: ZFeed }) { <DialogTrigger asChild> <Button variant="secondary"> <Edit className="mr-2 size-4" /> - Edit + {t("actions.edit")} </Button> </DialogTrigger> <DialogContent> @@ -233,7 +236,7 @@ export function EditFeedDialog({ feed }: { feed: ZFeed }) { render={({ field }) => { return ( <FormItem className="flex-1"> - <FormLabel>Name</FormLabel> + <FormLabel>{t("common.name")}</FormLabel> <FormControl> <Input placeholder="Feed name" type="text" {...field} /> </FormControl> @@ -248,7 +251,7 @@ export function EditFeedDialog({ feed }: { feed: ZFeed }) { render={({ field }) => { return ( <FormItem className="flex-1"> - <FormLabel>URL</FormLabel> + <FormLabel>{t("common.url")}</FormLabel> <FormControl> <Input placeholder="Feed url" type="text" {...field} /> </FormControl> @@ -262,7 +265,7 @@ export function EditFeedDialog({ feed }: { feed: ZFeed }) { <DialogFooter> <DialogClose asChild> <Button type="button" variant="secondary"> - Close + {t("actions.close")} </Button> </DialogClose> <ActionButton @@ -274,7 +277,7 @@ export function EditFeedDialog({ feed }: { feed: ZFeed }) { className="items-center" > <Save className="mr-2 size-4" /> - Save + {t("actions.save")} </ActionButton> </DialogFooter> </DialogContent> @@ -283,6 +286,7 @@ export function EditFeedDialog({ feed }: { feed: ZFeed }) { } export function FeedRow({ feed }: { feed: ZFeed }) { + const { t } = useTranslation(); const apiUtils = api.useUtils(); const { mutate: deleteFeed, isPending: isDeleting } = api.feeds.delete.useMutation({ @@ -340,7 +344,7 @@ export function FeedRow({ feed }: { feed: ZFeed }) { onClick={() => fetchNow({ feedId: feed.id })} > <ArrowDownToLine className="mr-2 size-4" /> - Fetch Now + {t("actions.fetch_now")} </ActionButton> <ActionConfirmingDialog title={`Delete Feed "${feed.name}"?`} @@ -354,13 +358,13 @@ export function FeedRow({ feed }: { feed: ZFeed }) { type="button" > <Trash2 className="mr-2 size-4" /> - Delete + {t("actions.delete")} </ActionButton> )} > <Button variant="destructive" disabled={isDeleting}> <Trash2 className="mr-2 size-4" /> - Delete + {t("actions.delete")} </Button> </ActionConfirmingDialog> </TableCell> @@ -369,6 +373,7 @@ export function FeedRow({ feed }: { feed: ZFeed }) { } export default function FeedSettings() { + const { t } = useTranslation(); const { data: feeds, isLoading } = api.feeds.list.useQuery(); return ( <> @@ -376,12 +381,14 @@ export default function FeedSettings() { <div className="flex flex-col gap-2"> <div className="flex items-center justify-between"> <span className="flex items-center gap-2 text-lg font-medium"> - RSS Subscriptions + {t("settings.feeds.rss_subscriptions")} <Tooltip> <TooltipTrigger className="text-muted-foreground"> <FlaskConical size={15} /> </TooltipTrigger> - <TooltipContent side="bottom">Experimental</TooltipContent> + <TooltipContent side="bottom"> + {t("common.experimental")} + </TooltipContent> </Tooltip> </span> <FeedsEditorDialog /> @@ -396,11 +403,11 @@ export default function FeedSettings() { <Table> <TableHeader> <TableRow> - <TableHead>Name</TableHead> - <TableHead>URL</TableHead> + <TableHead>{t("common.name")}</TableHead> + <TableHead>{t("common.url")}</TableHead> <TableHead>Last Fetch</TableHead> <TableHead>Last Status</TableHead> - <TableHead>Actions</TableHead> + <TableHead>{t("common.actions")}</TableHead> </TableRow> </TableHeader> <TableBody> diff --git a/apps/web/components/settings/ImportExport.tsx b/apps/web/components/settings/ImportExport.tsx index 7889b4d8..5cb35def 100644 --- a/apps/web/components/settings/ImportExport.tsx +++ b/apps/web/components/settings/ImportExport.tsx @@ -7,6 +7,7 @@ import { buttonVariants } from "@/components/ui/button"; import FilePickerButton from "@/components/ui/file-picker-button"; import { Progress } from "@/components/ui/progress"; import { toast } from "@/components/ui/use-toast"; +import { useTranslation } from "@/lib/i18n/client"; import { ParsedBookmark, parseHoarderBookmarkFile, @@ -31,6 +32,7 @@ import { import { BookmarkTypes } from "@hoarder/shared/types/bookmarks"; export function ExportButton() { + const { t } = useTranslation(); return ( <Link href="/api/bookmarks/export" @@ -40,12 +42,13 @@ export function ExportButton() { )} > <Download /> - <p>Export Links and Notes</p> + <p>{t("settings.import.export_links_and_notes")}</p> </Link> ); } export function ImportExportRow() { + const { t } = useTranslation(); const router = useRouter(); const [importProgress, setImportProgress] = useState<{ @@ -145,7 +148,7 @@ export function ImportExportRow() { }, onSuccess: async (resp) => { const importList = await createList({ - name: `Imported Bookmarks`, + name: t("settings.import.imported_bookmarks"), icon: "⬆️", }); setImportProgress({ done: 0, total: resp.length }); @@ -211,7 +214,7 @@ export function ImportExportRow() { } > <Upload /> - <p>Import Bookmarks from HTML file</p> + <p>{t("settings.import.import_bookmarks_from_html_file")}</p> </FilePickerButton> <FilePickerButton @@ -224,7 +227,7 @@ export function ImportExportRow() { } > <Upload /> - <p>Import Bookmarks from Pocket export</p> + <p>{t("settings.import.import_bookmarks_from_pocket_export")}</p> </FilePickerButton> <FilePickerButton loading={false} @@ -236,7 +239,7 @@ export function ImportExportRow() { } > <Upload /> - <p>Import Bookmarks from Omnivore export</p> + <p>{t("settings.import.import_bookmarks_from_omnivore_export")}</p> </FilePickerButton> <FilePickerButton loading={false} @@ -248,7 +251,7 @@ export function ImportExportRow() { } > <Upload /> - <p>Import Bookmarks from Hoarder export</p> + <p>{t("settings.import.import_bookmarks_from_hoarder_export")}</p> </FilePickerButton> <ExportButton /> </div> @@ -269,9 +272,12 @@ export function ImportExportRow() { } export default function ImportExport() { + const { t } = useTranslation(); return ( <div className="flex w-full flex-col gap-2"> - <p className="mb-4 text-lg font-medium">Import / Export Bookmarks</p> + <p className="mb-4 text-lg font-medium"> + {t("settings.import.import_export_bookmarks")} + </p> <ImportExportRow /> </div> ); diff --git a/apps/web/components/settings/UserDetails.tsx b/apps/web/components/settings/UserDetails.tsx index 471a6e09..af6698ad 100644 --- a/apps/web/components/settings/UserDetails.tsx +++ b/apps/web/components/settings/UserDetails.tsx @@ -1,24 +1,26 @@ import { Input } from "@/components/ui/input"; +import { useTranslation } from "@/lib/i18n/server"; import { api } from "@/server/api/client"; export default async function UserDetails() { + const { t } = await useTranslation(); const whoami = await api.users.whoami(); const details = [ { - label: "Name", + label: t("common.name"), value: whoami.name ?? undefined, }, { - label: "Email", + label: t("common.email"), value: whoami.email ?? undefined, }, ]; return ( - <div className="mb-8 flex w-full flex-col sm:flex-row"> + <div className="flex w-full flex-col sm:flex-row"> <div className="mb-4 w-full text-lg font-medium sm:w-1/3"> - Basic Details + {t("settings.info.basic_details")} </div> <div className="w-full"> {details.map(({ label, value }) => ( diff --git a/apps/web/components/settings/UserOptions.tsx b/apps/web/components/settings/UserOptions.tsx new file mode 100644 index 00000000..38dc1520 --- /dev/null +++ b/apps/web/components/settings/UserOptions.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { useTranslation } from "@/lib/i18n/client"; +import { useInterfaceLang } from "@/lib/userLocalSettings/bookmarksLayout"; +import { updateInterfaceLang } from "@/lib/userLocalSettings/userLocalSettings"; + +import { langNameMappings } from "@hoarder/shared/langs"; + +import { Label } from "../ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; + +const LanguageSelect = () => { + const lang = useInterfaceLang(); + return ( + <Select + value={lang} + onValueChange={async (val) => { + await updateInterfaceLang(val); + }} + > + <SelectTrigger> + <SelectValue /> + </SelectTrigger> + <SelectContent> + {Object.entries(langNameMappings).map(([lang, name]) => ( + <SelectItem key={lang} value={lang}> + {name} + </SelectItem> + ))} + </SelectContent> + </Select> + ); +}; + +export function UserOptions() { + const { t } = useTranslation(); + + return ( + <div className="flex flex-col sm:flex-row"> + <div className="mb-4 w-full text-lg font-medium sm:w-1/3"> + {t("settings.info.options")} + </div> + <div className="flex w-full flex-col gap-2"> + <Label>{t("settings.info.interface_lang")}</Label> + <LanguageSelect /> + </div> + </div> + ); +} diff --git a/apps/web/components/settings/sidebar/ModileSidebar.tsx b/apps/web/components/settings/sidebar/ModileSidebar.tsx index 2016c931..cbed9ef9 100644 --- a/apps/web/components/settings/sidebar/ModileSidebar.tsx +++ b/apps/web/components/settings/sidebar/ModileSidebar.tsx @@ -1,12 +1,14 @@ import MobileSidebarItem from "@/components/shared/sidebar/ModileSidebarItem"; +import { useTranslation } from "@/lib/i18n/server"; import { settingsSidebarItems } from "./items"; export default async function MobileSidebar() { + const { t } = await useTranslation(); return ( <aside className="w-full"> <ul className="flex justify-between space-x-2 border-b-black px-5 py-2 pt-5"> - {settingsSidebarItems.map((item) => ( + {settingsSidebarItems(t).map((item) => ( <MobileSidebarItem key={item.name} logo={item.icon} diff --git a/apps/web/components/settings/sidebar/Sidebar.tsx b/apps/web/components/settings/sidebar/Sidebar.tsx index 247e0916..a1b61e98 100644 --- a/apps/web/components/settings/sidebar/Sidebar.tsx +++ b/apps/web/components/settings/sidebar/Sidebar.tsx @@ -1,5 +1,6 @@ import { redirect } from "next/navigation"; import SidebarItem from "@/components/shared/sidebar/SidebarItem"; +import { useTranslation } from "@/lib/i18n/server"; import { getServerAuthSession } from "@/server/auth"; import serverConfig from "@hoarder/shared/config"; @@ -7,6 +8,7 @@ import serverConfig from "@hoarder/shared/config"; import { settingsSidebarItems } from "./items"; export default async function Sidebar() { + const { t } = await useTranslation(); const session = await getServerAuthSession(); if (!session) { redirect("/"); @@ -16,7 +18,7 @@ export default async function Sidebar() { <aside className="flex h-[calc(100vh-64px)] w-60 flex-col gap-5 border-r p-4 "> <div> <ul className="space-y-2 text-sm font-medium"> - {settingsSidebarItems.map((item) => ( + {settingsSidebarItems(t).map((item) => ( <SidebarItem key={item.name} logo={item.icon} diff --git a/apps/web/components/settings/sidebar/items.tsx b/apps/web/components/settings/sidebar/items.tsx index 047ee233..43dfabdd 100644 --- a/apps/web/components/settings/sidebar/items.tsx +++ b/apps/web/components/settings/sidebar/items.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { TFunction } from "i18next"; import { ArrowLeft, Download, @@ -8,38 +9,40 @@ import { User, } from "lucide-react"; -export const settingsSidebarItems: { +export const settingsSidebarItems = ( + t: TFunction, +): { name: string; icon: JSX.Element; path: string; -}[] = [ +}[] => [ { - name: "Back To App", + name: t("settings.back_to_app"), icon: <ArrowLeft size={18} />, path: "/dashboard/bookmarks", }, { - name: "User Info", + name: t("settings.info.user_info"), icon: <User size={18} />, path: "/settings/info", }, { - name: "AI Settings", + name: t("settings.ai.ai_settings"), icon: <Sparkles size={18} />, path: "/settings/ai", }, { - name: "RSS Subscriptions", + name: t("settings.feeds.rss_subscriptions"), icon: <Rss size={18} />, path: "/settings/feeds", }, { - name: "Import / Export", + name: t("settings.import.import_export"), icon: <Download size={18} />, path: "/settings/import", }, { - name: "API Keys", + name: t("settings.api_keys.api_keys"), icon: <KeyRound size={18} />, path: "/settings/api-keys", }, |
