feat(i18n): localize inventory items UI
This commit is contained in:
@@ -13,24 +13,26 @@ export default async function AddItem({
|
||||
const categories = await CategoryService.findAll()
|
||||
const item = await ItemService.findByIdWithAssetCount(itemId)
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.items
|
||||
|
||||
if (!item) {
|
||||
return <div>Item not found</div>
|
||||
return <div>{copy.edit.notFound}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{item?._count?.assets && item?._count.assets > 0 && (
|
||||
<div className="rounded-sm bg-red-100 p-4 text-red-800">
|
||||
<p>{`This item has already assets assigned to it.`}</p>
|
||||
<p>{copy.edit.hasAssetsWarning}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h1 className="text-2xl font-bold">Edit Item</h1>
|
||||
<h1 className="text-2xl font-bold">{copy.edit.title}</h1>
|
||||
</div>
|
||||
<UpdateItemForm
|
||||
categories={categories}
|
||||
item={item}
|
||||
formCopy={copy.form}
|
||||
submitButtonCopy={dictionary.common.submitButton}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { getI18n } from "@/i18n/server"
|
||||
import { AssetService } from "@/services/asset.service"
|
||||
import { ItemService } from "@/services/item.service"
|
||||
import { MovementService } from "@/services/movement.service"
|
||||
@@ -12,9 +13,11 @@ export default async function ItemPage({
|
||||
const item = await ItemService.findByIdWithCategory(itemId)
|
||||
const assets = await AssetService.findByItemId(itemId)
|
||||
const movements = await MovementService.findAllByItemId(itemId)
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.items.detail
|
||||
|
||||
if (!item) {
|
||||
return <div>Item not found</div>
|
||||
return <div>{copy.notFound}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -26,11 +29,11 @@ export default async function ItemPage({
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-x-8 gap-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Category</span>
|
||||
<span className="text-gray-600">{copy.labels.category}</span>
|
||||
<span>{item.category.name}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Stock</span>
|
||||
<span className="text-gray-600">{copy.labels.stock}</span>
|
||||
<span>{item.stock}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,15 @@ import { toast } from "sonner"
|
||||
import { deleteItemAction } from "@/actions/item.actions"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export default function DeleteItemButton({ itemId }: { itemId: string }) {
|
||||
import type { ItemDeleteCopy } from "./item.copy"
|
||||
|
||||
export default function DeleteItemButton({
|
||||
itemId,
|
||||
copy,
|
||||
}: {
|
||||
itemId: string
|
||||
copy: ItemDeleteCopy
|
||||
}) {
|
||||
const router = useRouter()
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
@@ -24,7 +32,7 @@ export default function DeleteItemButton({ itemId }: { itemId: string }) {
|
||||
toast.success(response.message)
|
||||
router.refresh()
|
||||
} else {
|
||||
toast.error(response.message ?? "Unknown error")
|
||||
toast.error(response.message ?? copy.unknownError)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -38,6 +46,7 @@ export default function DeleteItemButton({ itemId }: { itemId: string }) {
|
||||
size="icon"
|
||||
variant="outline"
|
||||
disabled={isPending}
|
||||
aria-label={isPending ? copy.pending : copy.label}
|
||||
>
|
||||
<Trash />
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { Dictionary } from "@/i18n/dictionaries"
|
||||
|
||||
export type ItemListCopy = Dictionary["inventory"]["items"]["list"]
|
||||
export type ItemDetailCopy = Dictionary["inventory"]["items"]["detail"]
|
||||
export type ItemFormCopy = Dictionary["inventory"]["items"]["form"]
|
||||
export type ItemDeleteCopy = Dictionary["inventory"]["items"]["delete"]
|
||||
@@ -15,11 +15,15 @@ import {
|
||||
} from "@/schemas/item.schema"
|
||||
import type { CategorySummary } from "@/types"
|
||||
|
||||
import type { ItemFormCopy } from "./item.copy"
|
||||
|
||||
export default function NewItemForm({
|
||||
categories,
|
||||
formCopy,
|
||||
submitButtonCopy,
|
||||
}: {
|
||||
categories: CategorySummary[]
|
||||
formCopy: ItemFormCopy
|
||||
submitButtonCopy: SubmitButtonCopy
|
||||
}) {
|
||||
const router = useRouter()
|
||||
@@ -61,12 +65,12 @@ export default function NewItemForm({
|
||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div>
|
||||
<label htmlFor="name" className="mb-2 block text-lg">
|
||||
Name
|
||||
{formCopy.nameLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder="Item name"
|
||||
placeholder={formCopy.namePlaceholder}
|
||||
{...register("name")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
/>
|
||||
@@ -74,14 +78,14 @@ export default function NewItemForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="categoryId" className="mb-2 block text-lg">
|
||||
Category
|
||||
{formCopy.categoryLabel}
|
||||
</label>
|
||||
<select
|
||||
id="categoryId"
|
||||
{...register("categoryId")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
>
|
||||
<option value="">Select a category</option>
|
||||
<option value="">{formCopy.categoryPlaceholder}</option>
|
||||
{categories?.map((category) => (
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
@@ -94,13 +98,13 @@ export default function NewItemForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="stock" className="mb-2 block text-lg">
|
||||
Stock
|
||||
{formCopy.stockLabel}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="stock"
|
||||
pattern="{[0-9]*}"
|
||||
placeholder="0"
|
||||
placeholder={formCopy.stockPlaceholder}
|
||||
min="0"
|
||||
{...register("stock")}
|
||||
className="w-full rounded-lg border px-4 py-2"
|
||||
@@ -124,7 +128,7 @@ export default function NewItemForm({
|
||||
isSubmitting={isSubmitting}
|
||||
isSubmitSuccessful={isSubmitSuccessful}
|
||||
>
|
||||
Create Item
|
||||
{formCopy.createSubmit}
|
||||
</SubmitButton>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -15,13 +15,17 @@ import {
|
||||
} from "@/schemas/item.schema"
|
||||
import type { CategorySummary, ItemWithAssetCount } from "@/types"
|
||||
|
||||
import type { ItemFormCopy } from "./item.copy"
|
||||
|
||||
export default function UpdateItemForm({
|
||||
categories,
|
||||
item,
|
||||
formCopy,
|
||||
submitButtonCopy,
|
||||
}: {
|
||||
categories: CategorySummary[]
|
||||
item: ItemWithAssetCount
|
||||
formCopy: ItemFormCopy
|
||||
submitButtonCopy: SubmitButtonCopy
|
||||
}) {
|
||||
const router = useRouter()
|
||||
@@ -72,12 +76,12 @@ export default function UpdateItemForm({
|
||||
{item?.id && <input type="hidden" name="id" value={item.id} />}
|
||||
<div>
|
||||
<label htmlFor="name" className="mb-2 block text-lg">
|
||||
Name
|
||||
{formCopy.nameLabel}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
placeholder="Item name"
|
||||
placeholder={formCopy.namePlaceholder}
|
||||
{...register("name")}
|
||||
className={`w-full rounded-lg border px-4 py-2`}
|
||||
/>
|
||||
@@ -85,7 +89,7 @@ export default function UpdateItemForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="categoryId" className="mb-2 block text-lg">
|
||||
Category
|
||||
{formCopy.categoryLabel}
|
||||
</label>
|
||||
<select
|
||||
id="categoryId"
|
||||
@@ -93,7 +97,7 @@ export default function UpdateItemForm({
|
||||
{...register("categoryId")}
|
||||
className={`w-full rounded-lg border px-4 py-2`}
|
||||
>
|
||||
<option value="">Select a category</option>
|
||||
<option value="">{formCopy.categoryPlaceholder}</option>
|
||||
{categories?.map((category) => (
|
||||
<option key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
@@ -106,13 +110,13 @@ export default function UpdateItemForm({
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="stock" className="mb-2 block text-lg">
|
||||
Stock
|
||||
{formCopy.stockLabel}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="stock"
|
||||
pattern="{[0-9]*}"
|
||||
placeholder="0"
|
||||
placeholder={formCopy.stockPlaceholder}
|
||||
min={item.stock}
|
||||
disabled={isDisabled}
|
||||
{...register("stock")}
|
||||
@@ -139,7 +143,7 @@ export default function UpdateItemForm({
|
||||
isSubmitting={isSubmitting}
|
||||
isSubmitSuccessful={isSubmitSuccessful}
|
||||
>
|
||||
Update Item
|
||||
{formCopy.updateSubmit}
|
||||
</SubmitButton>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -6,14 +6,16 @@ import NewItemForm from "../_components/new.item.form"
|
||||
export default async function NewItemPage() {
|
||||
const categories = await CategoryService.findAll()
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.items
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h1 className="text-2xl font-bold">New Item</h1>
|
||||
<h1 className="text-2xl font-bold">{copy.new.title}</h1>
|
||||
</div>
|
||||
<NewItemForm
|
||||
categories={categories}
|
||||
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 { ItemService } from "@/services/item.service"
|
||||
|
||||
import DeleteItemButton from "./_components/delete.item.button"
|
||||
@@ -22,19 +23,22 @@ export default async function ItemsPage(props: {
|
||||
pageSize: 10,
|
||||
search,
|
||||
})
|
||||
const { dictionary } = await getI18n()
|
||||
const copy = dictionary.inventory.items
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<PageHeader
|
||||
title="Items"
|
||||
title={copy.list.title}
|
||||
link="/inventory/items/new"
|
||||
addLabel={copy.list.addLabel}
|
||||
data={items}
|
||||
search={search}
|
||||
/>
|
||||
{items.length === 0 && currentPage === 1 && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
No items found.
|
||||
{copy.list.empty}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -44,19 +48,19 @@ export default async function ItemsPage(props: {
|
||||
<thead className="border-b">
|
||||
<tr>
|
||||
<th scope="col" className="p-4">
|
||||
Name
|
||||
{copy.list.columns.name}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Category
|
||||
{copy.list.columns.category}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Assets
|
||||
{copy.list.columns.assets}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Stock
|
||||
{copy.list.columns.stock}
|
||||
</th>
|
||||
<th scope="col" className="p-4">
|
||||
Actions
|
||||
{copy.list.columns.actions}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -69,17 +73,25 @@ export default async function ItemsPage(props: {
|
||||
<td className="p-4">{item.stock}</td>
|
||||
<td className="flex items-center gap-2 p-4">
|
||||
<Link href={`/inventory/items/${item.id}`} passHref>
|
||||
<Button variant="outline" size="icon">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={copy.list.actions.view}
|
||||
>
|
||||
<Eye />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={`/inventory/items/${item.id}/edit`} passHref>
|
||||
<Button variant="outline" size="icon">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={copy.list.actions.edit}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
</Link>
|
||||
{item._count.assets === 0 && item.stock === 0 && (
|
||||
<DeleteItemButton itemId={item.id} />
|
||||
<DeleteItemButton itemId={item.id} copy={copy.delete} />
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -113,6 +113,75 @@ export const en = {
|
||||
idRequired: "ID is required",
|
||||
},
|
||||
},
|
||||
items: {
|
||||
list: {
|
||||
title: "Items",
|
||||
addLabel: "Add Item",
|
||||
empty: "No items found.",
|
||||
columns: {
|
||||
name: "Name",
|
||||
category: "Category",
|
||||
assets: "Assets",
|
||||
stock: "Stock",
|
||||
actions: "Actions",
|
||||
},
|
||||
actions: {
|
||||
view: "View item",
|
||||
edit: "Edit item",
|
||||
delete: "Delete item",
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
notFound: "Item not found",
|
||||
labels: {
|
||||
category: "Category",
|
||||
stock: "Stock",
|
||||
},
|
||||
},
|
||||
new: {
|
||||
title: "New Item",
|
||||
},
|
||||
edit: {
|
||||
title: "Edit Item",
|
||||
notFound: "Item not found",
|
||||
hasAssetsWarning: "This item has already assets assigned to it.",
|
||||
},
|
||||
form: {
|
||||
nameLabel: "Name",
|
||||
namePlaceholder: "Item name",
|
||||
categoryLabel: "Category",
|
||||
categoryPlaceholder: "Select a category",
|
||||
stockLabel: "Stock",
|
||||
stockPlaceholder: "0",
|
||||
createSubmit: "Create Item",
|
||||
updateSubmit: "Update Item",
|
||||
},
|
||||
delete: {
|
||||
label: "Delete item",
|
||||
pending: "Deleting...",
|
||||
unknownError: "Unknown error",
|
||||
},
|
||||
actions: {
|
||||
createSuccess: "Item created successfully!",
|
||||
createFailure: "Error creating item",
|
||||
updateSuccess: "Item updated successfully!",
|
||||
updateFailure: "Failed to update item",
|
||||
deleteSuccess: "Item deleted successfully!",
|
||||
deleteFailure: "Failed to delete item",
|
||||
duplicateName: "Item already exists",
|
||||
notFound: "Item not found",
|
||||
hasAssets: "Cannot delete item with assets",
|
||||
hasStock: "Cannot delete item with stock",
|
||||
invalidStock: "Invalid stock",
|
||||
negativeStock: "Stock cannot be negative",
|
||||
},
|
||||
schema: {
|
||||
nameRequired: "Name is required",
|
||||
categoryRequired: "Category is required",
|
||||
stockRequired: "Stock is required",
|
||||
itemRequired: "Item is required",
|
||||
},
|
||||
},
|
||||
},
|
||||
login: {
|
||||
title: "Sign In",
|
||||
|
||||
@@ -116,6 +116,75 @@ export const es = {
|
||||
idRequired: "El ID es obligatorio",
|
||||
},
|
||||
},
|
||||
items: {
|
||||
list: {
|
||||
title: "Artículos",
|
||||
addLabel: "Agregar artículo",
|
||||
empty: "No se encontraron artículos.",
|
||||
columns: {
|
||||
name: "Nombre",
|
||||
category: "Categoría",
|
||||
assets: "Activos",
|
||||
stock: "Stock",
|
||||
actions: "Acciones",
|
||||
},
|
||||
actions: {
|
||||
view: "Ver artículo",
|
||||
edit: "Editar artículo",
|
||||
delete: "Eliminar artículo",
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
notFound: "Artículo no encontrado",
|
||||
labels: {
|
||||
category: "Categoría",
|
||||
stock: "Stock",
|
||||
},
|
||||
},
|
||||
new: {
|
||||
title: "Nuevo artículo",
|
||||
},
|
||||
edit: {
|
||||
title: "Editar artículo",
|
||||
notFound: "Artículo no encontrado",
|
||||
hasAssetsWarning: "Este artículo ya tiene activos asignados.",
|
||||
},
|
||||
form: {
|
||||
nameLabel: "Nombre",
|
||||
namePlaceholder: "Nombre del artículo",
|
||||
categoryLabel: "Categoría",
|
||||
categoryPlaceholder: "Selecciona una categoría",
|
||||
stockLabel: "Stock",
|
||||
stockPlaceholder: "0",
|
||||
createSubmit: "Crear artículo",
|
||||
updateSubmit: "Actualizar artículo",
|
||||
},
|
||||
delete: {
|
||||
label: "Eliminar artículo",
|
||||
pending: "Eliminando...",
|
||||
unknownError: "Error desconocido",
|
||||
},
|
||||
actions: {
|
||||
createSuccess: "Artículo creado correctamente",
|
||||
createFailure: "Error al crear el artículo",
|
||||
updateSuccess: "Artículo actualizado correctamente",
|
||||
updateFailure: "Error al actualizar el artículo",
|
||||
deleteSuccess: "Artículo eliminado correctamente",
|
||||
deleteFailure: "Error al eliminar el artículo",
|
||||
duplicateName: "El artículo ya existe",
|
||||
notFound: "Artículo no encontrado",
|
||||
hasAssets: "No se puede eliminar un artículo con activos",
|
||||
hasStock: "No se puede eliminar un artículo con stock",
|
||||
invalidStock: "Stock inválido",
|
||||
negativeStock: "El stock no puede ser negativo",
|
||||
},
|
||||
schema: {
|
||||
nameRequired: "El nombre es obligatorio",
|
||||
categoryRequired: "La categoría es obligatoria",
|
||||
stockRequired: "El stock es obligatorio",
|
||||
itemRequired: "El artículo es obligatorio",
|
||||
},
|
||||
},
|
||||
},
|
||||
login: {
|
||||
title: "Iniciar sesión",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { expect, type Page, test } from "@playwright/test"
|
||||
|
||||
async function setLocaleCookie(
|
||||
page: Page,
|
||||
locale: "en" | "es",
|
||||
baseURL?: string,
|
||||
) {
|
||||
await page.context().addCookies([
|
||||
{
|
||||
name: "stock-manager-locale",
|
||||
value: locale,
|
||||
url: baseURL ?? "http://127.0.0.1:3100",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
async function signInAsAdmin(page: Page, baseURL?: string) {
|
||||
await setLocaleCookie(page, "en", baseURL)
|
||||
await page.goto("/login")
|
||||
await page.getByLabel("Username").fill("admin")
|
||||
await page.getByLabel("Password").fill("admin-password")
|
||||
await page.getByRole("button", { name: "Sign In" }).click()
|
||||
await expect(page).toHaveURL("/")
|
||||
}
|
||||
|
||||
test.describe("inventory items localization", () => {
|
||||
test("renders item list and new form UI copy in Spanish", async ({
|
||||
baseURL,
|
||||
page,
|
||||
}) => {
|
||||
await signInAsAdmin(page, baseURL)
|
||||
await setLocaleCookie(page, "es", baseURL)
|
||||
|
||||
await page.goto("/inventory/items")
|
||||
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", "es")
|
||||
await expect(page.getByRole("heading", { name: "Artículos" })).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole("link", { name: /Agregar artículo/ }),
|
||||
).toBeVisible()
|
||||
await expect(page.getByText("No se encontraron artículos.")).toBeVisible()
|
||||
|
||||
await page.goto("/inventory/items/new")
|
||||
|
||||
await expect(
|
||||
page.getByRole("heading", { name: "Nuevo artículo" }),
|
||||
).toBeVisible()
|
||||
await expect(page.getByLabel("Nombre")).toBeVisible()
|
||||
await expect(page.getByPlaceholder("Nombre del artículo")).toBeVisible()
|
||||
await expect(page.getByLabel("Categoría")).toBeVisible()
|
||||
await expect(page.locator("select#categoryId")).toContainText(
|
||||
"Selecciona una categoría",
|
||||
)
|
||||
await expect(page.getByLabel("Stock")).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Crear artículo" }),
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -266,6 +266,148 @@ describe("i18n dictionaries", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("provides localized inventory item copy for English and Spanish", () => {
|
||||
expect(getDictionary("en").inventory.items).toEqual({
|
||||
list: {
|
||||
title: "Items",
|
||||
addLabel: "Add Item",
|
||||
empty: "No items found.",
|
||||
columns: {
|
||||
name: "Name",
|
||||
category: "Category",
|
||||
assets: "Assets",
|
||||
stock: "Stock",
|
||||
actions: "Actions",
|
||||
},
|
||||
actions: {
|
||||
view: "View item",
|
||||
edit: "Edit item",
|
||||
delete: "Delete item",
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
notFound: "Item not found",
|
||||
labels: {
|
||||
category: "Category",
|
||||
stock: "Stock",
|
||||
},
|
||||
},
|
||||
new: {
|
||||
title: "New Item",
|
||||
},
|
||||
edit: {
|
||||
title: "Edit Item",
|
||||
notFound: "Item not found",
|
||||
hasAssetsWarning: "This item has already assets assigned to it.",
|
||||
},
|
||||
form: {
|
||||
nameLabel: "Name",
|
||||
namePlaceholder: "Item name",
|
||||
categoryLabel: "Category",
|
||||
categoryPlaceholder: "Select a category",
|
||||
stockLabel: "Stock",
|
||||
stockPlaceholder: "0",
|
||||
createSubmit: "Create Item",
|
||||
updateSubmit: "Update Item",
|
||||
},
|
||||
delete: {
|
||||
label: "Delete item",
|
||||
pending: "Deleting...",
|
||||
unknownError: "Unknown error",
|
||||
},
|
||||
actions: {
|
||||
createSuccess: "Item created successfully!",
|
||||
createFailure: "Error creating item",
|
||||
updateSuccess: "Item updated successfully!",
|
||||
updateFailure: "Failed to update item",
|
||||
deleteSuccess: "Item deleted successfully!",
|
||||
deleteFailure: "Failed to delete item",
|
||||
duplicateName: "Item already exists",
|
||||
notFound: "Item not found",
|
||||
hasAssets: "Cannot delete item with assets",
|
||||
hasStock: "Cannot delete item with stock",
|
||||
invalidStock: "Invalid stock",
|
||||
negativeStock: "Stock cannot be negative",
|
||||
},
|
||||
schema: {
|
||||
nameRequired: "Name is required",
|
||||
categoryRequired: "Category is required",
|
||||
stockRequired: "Stock is required",
|
||||
itemRequired: "Item is required",
|
||||
},
|
||||
})
|
||||
|
||||
expect(getDictionary("es").inventory.items).toEqual({
|
||||
list: {
|
||||
title: "Artículos",
|
||||
addLabel: "Agregar artículo",
|
||||
empty: "No se encontraron artículos.",
|
||||
columns: {
|
||||
name: "Nombre",
|
||||
category: "Categoría",
|
||||
assets: "Activos",
|
||||
stock: "Stock",
|
||||
actions: "Acciones",
|
||||
},
|
||||
actions: {
|
||||
view: "Ver artículo",
|
||||
edit: "Editar artículo",
|
||||
delete: "Eliminar artículo",
|
||||
},
|
||||
},
|
||||
detail: {
|
||||
notFound: "Artículo no encontrado",
|
||||
labels: {
|
||||
category: "Categoría",
|
||||
stock: "Stock",
|
||||
},
|
||||
},
|
||||
new: {
|
||||
title: "Nuevo artículo",
|
||||
},
|
||||
edit: {
|
||||
title: "Editar artículo",
|
||||
notFound: "Artículo no encontrado",
|
||||
hasAssetsWarning: "Este artículo ya tiene activos asignados.",
|
||||
},
|
||||
form: {
|
||||
nameLabel: "Nombre",
|
||||
namePlaceholder: "Nombre del artículo",
|
||||
categoryLabel: "Categoría",
|
||||
categoryPlaceholder: "Selecciona una categoría",
|
||||
stockLabel: "Stock",
|
||||
stockPlaceholder: "0",
|
||||
createSubmit: "Crear artículo",
|
||||
updateSubmit: "Actualizar artículo",
|
||||
},
|
||||
delete: {
|
||||
label: "Eliminar artículo",
|
||||
pending: "Eliminando...",
|
||||
unknownError: "Error desconocido",
|
||||
},
|
||||
actions: {
|
||||
createSuccess: "Artículo creado correctamente",
|
||||
createFailure: "Error al crear el artículo",
|
||||
updateSuccess: "Artículo actualizado correctamente",
|
||||
updateFailure: "Error al actualizar el artículo",
|
||||
deleteSuccess: "Artículo eliminado correctamente",
|
||||
deleteFailure: "Error al eliminar el artículo",
|
||||
duplicateName: "El artículo ya existe",
|
||||
notFound: "Artículo no encontrado",
|
||||
hasAssets: "No se puede eliminar un artículo con activos",
|
||||
hasStock: "No se puede eliminar un artículo con stock",
|
||||
invalidStock: "Stock inválido",
|
||||
negativeStock: "El stock no puede ser negativo",
|
||||
},
|
||||
schema: {
|
||||
nameRequired: "El nombre es obligatorio",
|
||||
categoryRequired: "La categoría es obligatoria",
|
||||
stockRequired: "El stock es obligatorio",
|
||||
itemRequired: "El artículo es obligatorio",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("keeps dashboard home dictionary keys aligned across locales", () => {
|
||||
expect(getDictionary("en").dashboardHome).toEqual({
|
||||
heading: "Dashboard",
|
||||
|
||||
Reference in New Issue
Block a user