- Update Item
+ {formCopy.updateSubmit}
)
diff --git a/src/app/(dashboard)/inventory/items/new/page.tsx b/src/app/(dashboard)/inventory/items/new/page.tsx
index f53aa3b..7b81bdb 100644
--- a/src/app/(dashboard)/inventory/items/new/page.tsx
+++ b/src/app/(dashboard)/inventory/items/new/page.tsx
@@ -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 (
diff --git a/src/app/(dashboard)/inventory/items/page.tsx b/src/app/(dashboard)/inventory/items/page.tsx
index f136690..13ad0a2 100644
--- a/src/app/(dashboard)/inventory/items/page.tsx
+++ b/src/app/(dashboard)/inventory/items/page.tsx
@@ -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 (
{items.length === 0 && currentPage === 1 && (
- No items found.
+ {copy.list.empty}
)}
@@ -44,19 +48,19 @@ export default async function ItemsPage(props: {
|
- Name
+ {copy.list.columns.name}
|
- Category
+ {copy.list.columns.category}
|
- Assets
+ {copy.list.columns.assets}
|
- Stock
+ {copy.list.columns.stock}
|
- Actions
+ {copy.list.columns.actions}
|
@@ -69,17 +73,25 @@ export default async function ItemsPage(props: {
{item.stock} |
- |
diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts
index db8e711..c38f247 100644
--- a/src/i18n/dictionaries/en.ts
+++ b/src/i18n/dictionaries/en.ts
@@ -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",
diff --git a/src/i18n/dictionaries/es.ts b/src/i18n/dictionaries/es.ts
index a73ccd2..d3f6c67 100644
--- a/src/i18n/dictionaries/es.ts
+++ b/src/i18n/dictionaries/es.ts
@@ -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",
diff --git a/tests/e2e/inventory-items.spec.ts b/tests/e2e/inventory-items.spec.ts
new file mode 100644
index 0000000..edd82b9
--- /dev/null
+++ b/tests/e2e/inventory-items.spec.ts
@@ -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()
+ })
+})
diff --git a/tests/unit/i18n/dictionaries.test.ts b/tests/unit/i18n/dictionaries.test.ts
index 24289b3..967e53f 100644
--- a/tests/unit/i18n/dictionaries.test.ts
+++ b/tests/unit/i18n/dictionaries.test.ts
@@ -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",