feat(i18n): localize inventory categories UI

This commit is contained in:
2026-06-12 23:01:33 +02:00
parent 589b042e7c
commit e9a07eb28e
14 changed files with 315 additions and 17 deletions
@@ -13,6 +13,7 @@ export default async function EditCategoryPage({
const { categoryId } = await params
const category = await CategoryService.findById(categoryId)
const { dictionary } = await getI18n()
const copy = dictionary.inventory.categories
if (!category) {
notFound()
@@ -21,10 +22,11 @@ export default async function EditCategoryPage({
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">Edit Category</h1>
<h1 className="text-2xl font-bold">{copy.edit.title}</h1>
</div>
<EditCategoryForm
category={category}
formCopy={copy.form}
submitButtonCopy={dictionary.common.submitButton}
/>
</div>
@@ -0,0 +1,4 @@
import type { Dictionary } from "@/i18n/dictionaries"
export type CategoryFormCopy = Dictionary["inventory"]["categories"]["form"]
export type CategoryDeleteCopy = Dictionary["inventory"]["categories"]["delete"]
@@ -7,10 +7,14 @@ import { toast } from "sonner"
import { deleteCategoryAction } from "@/actions/category.actions"
import { Button } from "@/components/ui/button"
import type { CategoryDeleteCopy } from "./category.copy"
export default function DeleteCategoryButton({
categoryId,
copy,
}: {
categoryId: string
copy: CategoryDeleteCopy
}) {
const router = useRouter()
const [isPending, startTransition] = useTransition()
@@ -28,7 +32,7 @@ export default function DeleteCategoryButton({
toast.success(response.message)
router.refresh()
} else {
toast.error(response.message ?? "Unknown error")
toast.error(response.message ?? copy.unknownError)
}
})
}
@@ -42,6 +46,7 @@ export default function DeleteCategoryButton({
size="icon"
variant="outline"
disabled={isPending}
aria-label={isPending ? copy.pending : copy.label}
>
<Trash />
</Button>
@@ -14,12 +14,15 @@ import {
updateCategorySchema,
} from "@/schemas/category.schema"
import type { CategorySummary } from "@/types"
import type { CategoryFormCopy } from "./category.copy"
export default function EditCategoryForm({
category,
formCopy,
submitButtonCopy,
}: {
category: CategorySummary
formCopy: CategoryFormCopy
submitButtonCopy: SubmitButtonCopy
}) {
const router = useRouter()
@@ -64,12 +67,12 @@ export default function EditCategoryForm({
<input type="hidden" {...register("id")} />
<div className="flex flex-col gap-2">
<label htmlFor="name" className="mb-2 block text-lg">
Name
{formCopy.nameLabel}
</label>
<input
type="text"
id="name"
placeholder="Category name"
placeholder={formCopy.namePlaceholder}
{...register("name")}
className={`w-full rounded-lg border px-4 py-2 ${
errors.name ? "border-error" : ""
@@ -82,7 +85,7 @@ export default function EditCategoryForm({
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Update Category
{formCopy.updateSubmit}
</SubmitButton>
</form>
)
@@ -13,10 +13,13 @@ import {
type CreateCategoryFormType,
createCategorySchema,
} from "@/schemas/category.schema"
import type { CategoryFormCopy } from "./category.copy"
export default function NewCategoryForm({
formCopy,
submitButtonCopy,
}: {
formCopy: CategoryFormCopy
submitButtonCopy: SubmitButtonCopy
}) {
const router = useRouter()
@@ -56,12 +59,12 @@ export default function NewCategoryForm({
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col gap-2">
<label htmlFor="name" className="mb-2 block text-lg">
Name
{formCopy.nameLabel}
</label>
<input
type="text"
id="name"
placeholder="Category name"
placeholder={formCopy.namePlaceholder}
{...register("name")}
className={`w-full rounded-lg border px-4 py-2 ${
errors.name ? "border-error" : ""
@@ -74,7 +77,7 @@ export default function NewCategoryForm({
isSubmitting={isSubmitting}
isSubmitSuccessful={isSubmitSuccessful}
>
Create Category
{formCopy.createSubmit}
</SubmitButton>
</form>
)
@@ -4,13 +4,17 @@ import NewCategoryForm from "../_components/new.category.form"
export default async function NewCategoryPage() {
const { dictionary } = await getI18n()
const copy = dictionary.inventory.categories
return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold">New Category</h1>
<h1 className="text-2xl font-bold">{copy.new.title}</h1>
</div>
<NewCategoryForm submitButtonCopy={dictionary.common.submitButton} />
<NewCategoryForm
formCopy={copy.form}
submitButtonCopy={dictionary.common.submitButton}
/>
</div>
)
}
@@ -4,6 +4,7 @@ import Link from "next/link"
import PageHeader from "@/components/common/pageheader"
import PaginationButtons from "@/components/common/pagination"
import { Button } from "@/components/ui/button"
import { getI18n } from "@/i18n/server"
import { CategoryService } from "@/services/category.service"
import DeleteCategoryButton from "./_components/delete.category.button"
@@ -23,18 +24,21 @@ export default async function Items(props: {
pageSize: 10,
search,
})
const { dictionary } = await getI18n()
const copy = dictionary.inventory.categories
return (
<div className="flex flex-col gap-4">
<PageHeader
title="Categories"
title={copy.list.title}
addLabel={copy.list.addLabel}
link="/inventory/categories/new"
data={categories}
/>
{categories.length === 0 && currentPage === 1 && (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-4">
No Categories found.
{copy.list.empty}
</div>
</div>
)}
@@ -44,13 +48,13 @@ export default async function Items(props: {
<thead className="border-b">
<tr>
<th scope="col" className="p-4">
Name
{copy.list.columns.name}
</th>
<th scope="col" className="p-4">
Items
{copy.list.columns.items}
</th>
<th scope="col" className="p-4">
Actions
{copy.list.columns.actions}
</th>
</tr>
</thead>
@@ -68,12 +72,16 @@ export default async function Items(props: {
className="btn btn-primary"
variant="outline"
size="icon"
aria-label={copy.list.actions.edit}
>
<Pencil />
</Button>
</Link>
{category._count.items === 0 && (
<DeleteCategoryButton categoryId={category.id} />
<DeleteCategoryButton
categoryId={category.id}
copy={copy.delete}
/>
)}
</td>
</tr>