Implemented project-wide localization plumbing and converted the visible UI/app messages to translation-ready strings.

This commit is contained in:
2026-05-18 00:16:37 +03:30
parent b49b59a056
commit c6606d9602
60 changed files with 1376 additions and 345 deletions

View File

@@ -2,10 +2,13 @@ import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import * as React from "react"
import { useTranslation } from "@/lib/translations"
import { cn } from "@/lib/utils"
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
const { t } = useTranslation()
return <nav aria-label={t("breadcrumb")} data-slot="breadcrumb" {...props} />
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
@@ -84,6 +87,8 @@ function BreadcrumbEllipsis({
className,
...props
}: React.ComponentProps<"span">) {
const { t } = useTranslation()
return (
<span
data-slot="breadcrumb-ellipsis"
@@ -93,7 +98,7 @@ function BreadcrumbEllipsis({
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
<span className="sr-only">{t("More")}</span>
</span>
)
}

View File

@@ -2,6 +2,7 @@ import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react"
import { useTranslatedChildren } from "@/lib/translations"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
@@ -39,19 +40,23 @@ function Button({
variant,
size,
asChild = false,
children,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
const translatedChildren = useTranslatedChildren(children)
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
>
{translatedChildren}
</Comp>
)
}

View File

@@ -2,6 +2,7 @@ import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import * as React from "react"
import { useTranslation } from "@/lib/translations"
import { cn } from "@/lib/utils"
function Dialog({
@@ -49,6 +50,8 @@ function DialogContent({
children,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
const { t } = useTranslation()
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
@@ -63,7 +66,7 @@ function DialogContent({
{children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
<XIcon />
<span className="sr-only">Close</span>
<span className="sr-only">{t("Close")}</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>

View File

@@ -1,12 +1,16 @@
import * as LabelPrimitive from "@radix-ui/react-label"
import * as React from "react"
import { useTranslatedChildren } from "@/lib/translations"
import { cn } from "@/lib/utils"
function Label({
className,
children,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
const translatedChildren = useTranslatedChildren(children)
return (
<LabelPrimitive.Root
data-slot="label"
@@ -15,7 +19,9 @@ function Label({
className
)}
{...props}
/>
>
{translatedChildren}
</LabelPrimitive.Root>
)
}

View File

@@ -2,6 +2,7 @@ import * as SheetPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"
import * as React from "react"
import { useTranslation } from "@/lib/translations"
import { cn } from "@/lib/utils"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
@@ -50,6 +51,8 @@ function SheetContent({
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"
}) {
const { t } = useTranslation()
return (
<SheetPortal>
<SheetOverlay />
@@ -72,7 +75,7 @@ function SheetContent({
{children}
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
<XIcon className="size-4" />
<span className="sr-only">Close</span>
<span className="sr-only">{t("Close")}</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>

View File

@@ -21,6 +21,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip"
import { useIsMobile } from "@/hooks/use-mobile"
import { useTranslation } from "@/lib/translations"
import { cn } from "@/lib/utils"
const SIDEBAR_COOKIE_NAME = "sidebar_state"
@@ -160,6 +161,7 @@ function Sidebar({
collapsible?: "offcanvas" | "icon" | "none"
}) {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
const { t } = useTranslation()
if (collapsible === "none") {
return (
@@ -180,8 +182,10 @@ function Sidebar({
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetHeader className="sr-only">
<SheetTitle>Sidebar</SheetTitle>
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
<SheetTitle>{t("Sidebar")}</SheetTitle>
<SheetDescription>
{t("Displays the mobile sidebar.")}
</SheetDescription>
</SheetHeader>
<SheetContent
data-sidebar="sidebar"
@@ -252,6 +256,7 @@ function SidebarTrigger({
...props
}: React.ComponentProps<typeof Button>) {
const { toggleSidebar, isMobile, state } = useSidebar()
const { t } = useTranslation()
return (
<Button
@@ -267,22 +272,23 @@ function SidebarTrigger({
{...props}
>
{isMobile || state === "collapsed" ? <PanelLeftOpenIcon /> : <PanelLeftCloseIcon />}
<span className="sr-only">Toggle sidebar</span>
<span className="sr-only">{t("Toggle sidebar")}</span>
</Button>
)
}
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
const { toggleSidebar } = useSidebar()
const { t } = useTranslation()
return (
<button
data-sidebar="rail"
data-slot="sidebar-rail"
aria-label="Toggle sidebar"
aria-label={t("Toggle sidebar")}
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle sidebar"
title={t("Toggle sidebar")}
className={cn(
"hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",

View File

@@ -1,12 +1,15 @@
import { Loader2Icon } from "lucide-react"
import { useTranslation } from "@/lib/translations"
import { cn } from "@/lib/utils"
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
const { t } = useTranslation()
return (
<Loader2Icon
role="status"
aria-label="Loading"
aria-label={t("Loading")}
className={cn("size-4 animate-spin", className)}
{...props}
/>