- | {movement.type} |
- {movement?.item?.name} |
- {movement?.asset?.serialNumber || "-"}
+ {formatMovementType(
+ movement.type,
+ copy.types,
+ copy.fallback,
+ )}
+ |
+
+ {movement?.item?.name || copy.fallback.missingValue}
+ |
+
+ {movement?.asset?.serialNumber ||
+ copy.fallback.missingValue}
|
{movement.quantity} |
- {movement?.recipient?.firstName || "-"}{" "}
- {movement?.recipient?.lastName || "-"}
+ {movement?.recipient
+ ? `${movement.recipient.firstName} ${movement.recipient.lastName}`
+ : copy.fallback.missingValue}
|
{formatDate(movement.createdAt)} |
diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts
index f6ab73d..d224073 100644
--- a/src/i18n/dictionaries/en.ts
+++ b/src/i18n/dictionaries/en.ts
@@ -255,6 +255,39 @@ export const en = {
invalidUpdateStatus: "Invalid status",
},
},
+ movements: {
+ list: {
+ title: "Movements",
+ empty: "No movements found",
+ columns: {
+ type: "Type",
+ item: "Item",
+ serialNumber: "Serial Number",
+ quantity: "Quantity",
+ recipient: "Recipient",
+ date: "Date",
+ },
+ },
+ snippet: {
+ title: "Movements",
+ labels: {
+ type: "Type",
+ quantity: "Quantity",
+ },
+ },
+ types: {
+ IN: "In",
+ OUT: "Out",
+ ASSIGNMENT: "Assignment",
+ RETURN: "Return",
+ ADJUSTMENT: "Adjustment",
+ DELETED: "Deleted",
+ },
+ fallback: {
+ missingValue: "-",
+ unknownType: "Unknown movement type",
+ },
+ },
},
login: {
title: "Sign In",
diff --git a/src/i18n/dictionaries/es.ts b/src/i18n/dictionaries/es.ts
index ee5c548..d00eb41 100644
--- a/src/i18n/dictionaries/es.ts
+++ b/src/i18n/dictionaries/es.ts
@@ -259,6 +259,39 @@ export const es = {
invalidUpdateStatus: "Estado inválido",
},
},
+ movements: {
+ list: {
+ title: "Movimientos",
+ empty: "No se encontraron movimientos.",
+ columns: {
+ type: "Tipo",
+ item: "Artículo",
+ serialNumber: "Número de serie",
+ quantity: "Cantidad",
+ recipient: "Destinatario",
+ date: "Fecha",
+ },
+ },
+ snippet: {
+ title: "Movimientos",
+ labels: {
+ type: "Tipo",
+ quantity: "Cantidad",
+ },
+ },
+ types: {
+ IN: "Entrada",
+ OUT: "Salida",
+ ASSIGNMENT: "Asignación",
+ RETURN: "Devolución",
+ ADJUSTMENT: "Ajuste",
+ DELETED: "Eliminación",
+ },
+ fallback: {
+ missingValue: "-",
+ unknownType: "Tipo de movimiento desconocido",
+ },
+ },
},
login: {
title: "Iniciar sesión",
diff --git a/tests/e2e/movements.spec.ts b/tests/e2e/movements.spec.ts
new file mode 100644
index 0000000..2afbcf0
--- /dev/null
+++ b/tests/e2e/movements.spec.ts
@@ -0,0 +1,43 @@
+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("movements localization", () => {
+ test("renders movement list UI copy in Spanish", async ({
+ baseURL,
+ page,
+ }) => {
+ await signInAsAdmin(page, baseURL)
+ await setLocaleCookie(page, "es", baseURL)
+
+ await page.goto("/movements")
+
+ await expect(page.locator("html")).toHaveAttribute("lang", "es")
+ await expect(
+ page.getByRole("heading", { name: "Movimientos" }),
+ ).toBeVisible()
+ await expect(page.getByText("No se encontraron movimientos.")).toBeVisible()
+ await expect(page.getByText("Tipo")).toHaveCount(0)
+ })
+})
diff --git a/tests/unit/app/movements/movement.copy.test.ts b/tests/unit/app/movements/movement.copy.test.ts
new file mode 100644
index 0000000..6aa9d1c
--- /dev/null
+++ b/tests/unit/app/movements/movement.copy.test.ts
@@ -0,0 +1,32 @@
+import { describe, expect, it } from "vitest"
+
+import { formatMovementType } from "@/app/(dashboard)/movements/movement.copy"
+
+describe("movement copy helpers", () => {
+ const typeCopy = {
+ IN: "Entrada",
+ OUT: "Salida",
+ ASSIGNMENT: "Asignación",
+ RETURN: "Devolución",
+ ADJUSTMENT: "Ajuste",
+ DELETED: "Eliminación",
+ }
+
+ const fallbackCopy = {
+ missingValue: "-",
+ unknownType: "Tipo de movimiento desconocido",
+ }
+
+ it("formats known movement types with localized display labels", () => {
+ expect(formatMovementType("IN", typeCopy, fallbackCopy)).toBe("Entrada")
+ expect(formatMovementType("RETURN", typeCopy, fallbackCopy)).toBe(
+ "Devolución",
+ )
+ })
+
+ it("falls back for unknown movement type values without rewriting the raw value", () => {
+ expect(formatMovementType("LEGACY", typeCopy, fallbackCopy)).toBe(
+ "Tipo de movimiento desconocido",
+ )
+ })
+})
diff --git a/tests/unit/i18n/dictionaries.test.ts b/tests/unit/i18n/dictionaries.test.ts
index 78e1b09..391cd58 100644
--- a/tests/unit/i18n/dictionaries.test.ts
+++ b/tests/unit/i18n/dictionaries.test.ts
@@ -559,6 +559,76 @@ describe("i18n dictionaries", () => {
})
})
+ it("provides localized movement UI copy for English and Spanish", () => {
+ expect(getDictionary("en").inventory.movements).toEqual({
+ list: {
+ title: "Movements",
+ empty: "No movements found",
+ columns: {
+ type: "Type",
+ item: "Item",
+ serialNumber: "Serial Number",
+ quantity: "Quantity",
+ recipient: "Recipient",
+ date: "Date",
+ },
+ },
+ snippet: {
+ title: "Movements",
+ labels: {
+ type: "Type",
+ quantity: "Quantity",
+ },
+ },
+ types: {
+ IN: "In",
+ OUT: "Out",
+ ASSIGNMENT: "Assignment",
+ RETURN: "Return",
+ ADJUSTMENT: "Adjustment",
+ DELETED: "Deleted",
+ },
+ fallback: {
+ missingValue: "-",
+ unknownType: "Unknown movement type",
+ },
+ })
+
+ expect(getDictionary("es").inventory.movements).toEqual({
+ list: {
+ title: "Movimientos",
+ empty: "No se encontraron movimientos.",
+ columns: {
+ type: "Tipo",
+ item: "Artículo",
+ serialNumber: "Número de serie",
+ quantity: "Cantidad",
+ recipient: "Destinatario",
+ date: "Fecha",
+ },
+ },
+ snippet: {
+ title: "Movimientos",
+ labels: {
+ type: "Tipo",
+ quantity: "Cantidad",
+ },
+ },
+ types: {
+ IN: "Entrada",
+ OUT: "Salida",
+ ASSIGNMENT: "Asignación",
+ RETURN: "Devolución",
+ ADJUSTMENT: "Ajuste",
+ DELETED: "Eliminación",
+ },
+ fallback: {
+ missingValue: "-",
+ unknownType: "Tipo de movimiento desconocido",
+ },
+ })
+ })
+
it("keeps dashboard home dictionary keys aligned across locales", () => {
expect(getDictionary("en").dashboardHome).toEqual({
heading: "Dashboard",