import { describe, expect, it } from "vitest" import { dictionaries, getDictionary } from "@/i18n/dictionaries" import { SUPPORTED_LOCALES } from "@/i18n/locales" describe("i18n dictionaries", () => { it("provides dictionaries for every supported locale and no extra locales", () => { expect(Object.keys(dictionaries).sort()).toEqual( [...SUPPORTED_LOCALES].sort(), ) }) it("returns localized login copy for English and Spanish", () => { expect(getDictionary("en").login).toEqual({ title: "Sign In", emailLabel: "Email", passwordLabel: "Password", submitLabel: "Sign In", }) expect(getDictionary("es").login).toEqual({ title: "Iniciar sesión", emailLabel: "Correo electrónico", passwordLabel: "Contraseña", submitLabel: "Iniciar sesión", }) }) it("provides localized language switcher copy for English and Spanish", () => { expect(getDictionary("en").common.languageSwitcher).toEqual({ label: "Language", options: { en: "English", es: "Spanish", }, }) expect(getDictionary("es").common.languageSwitcher).toEqual({ label: "Idioma", options: { en: "Inglés", es: "Español", }, }) }) it("provides localized shell and common copy for English and Spanish", () => { expect(getDictionary("en").layout).toEqual({ sidebar: { home: "Home", inventory: "Inventory", items: "Items", categories: "Categories", assets: "Assets", people: "People", movements: "Movements", assignments: "Assignments", users: "Users", }, navbar: { accountLabel: "My Account", }, addMenu: { add: "Add", import: "Import", category: "Category", item: "Item", asset: "Asset", person: "Person", assignment: "Assignment", }, resetDatabase: { idle: "Reset Database", loading: "Resetting...", successToast: "Database reset successfully", errorToast: "Error resetting database", }, logout: { label: "Sign Out", }, }) expect(getDictionary("es").layout).toEqual({ sidebar: { home: "Inicio", inventory: "Inventario", items: "Artículos", categories: "Categorías", assets: "Activos", people: "Personas", movements: "Movimientos", assignments: "Asignaciones", users: "Usuarios", }, navbar: { accountLabel: "Mi cuenta", }, addMenu: { add: "Añadir", import: "Importar", category: "Categoría", item: "Artículo", asset: "Activo", person: "Persona", assignment: "Asignación", }, resetDatabase: { idle: "Reiniciar base de datos", loading: "Reiniciando...", successToast: "Base de datos reiniciada correctamente", errorToast: "Error al reiniciar la base de datos", }, logout: { label: "Cerrar sesión", }, }) expect(getDictionary("en").common.search).toEqual({ placeholder: "Search...", label: "Search", clearLabel: "Clear search", }) expect(getDictionary("es").common.search).toEqual({ placeholder: "Buscar...", label: "Buscar", clearLabel: "Limpiar búsqueda", }) expect(getDictionary("en").common.pagination).toEqual({ summaryPrefix: "Showing page", summarySeparator: "of", previous: "Previous", next: "Next", }) expect(getDictionary("es").common.pagination).toEqual({ summaryPrefix: "Mostrando página", summarySeparator: "de", previous: "Anterior", next: "Siguiente", }) expect(getDictionary("en").common.submitButton).toEqual({ defaultLabel: "Submit", processing: "Processing", success: "Success", }) expect(getDictionary("en").common.forbidden).toEqual({ title: "Access denied", description: "You do not have permission to access this section.", homeLink: "Back to home", }) expect(getDictionary("es").common.submitButton).toEqual({ defaultLabel: "Enviar", processing: "Procesando", success: "Completado", }) expect(getDictionary("es").common.forbidden).toEqual({ title: "Acceso denegado", description: "No tienes permisos para acceder a esta sección.", homeLink: "Volver al inicio", }) }) it("provides localized inventory category copy for English and Spanish", () => { expect(getDictionary("en").inventory.categories).toEqual({ list: { title: "Categories", addLabel: "Add Category", empty: "No categories found.", columns: { name: "Name", items: "Items", actions: "Actions", }, actions: { edit: "Edit category", delete: "Delete category", }, }, new: { title: "New Category", }, edit: { title: "Edit Category", }, form: { nameLabel: "Name", namePlaceholder: "Category name", createSubmit: "Create Category", updateSubmit: "Update Category", }, delete: { label: "Delete category", pending: "Deleting...", unknownError: "Unknown error", }, actions: { createSuccess: "Category created successfully", createFailure: "Failed to create category", updateSuccess: "Category updated successfully", updateFailure: "Failed to update category", deleteSuccess: "Category deleted successfully", deleteFailure: "Failed to delete category", duplicateName: "Category already exists", unchangedName: "Category name unchanged", notFound: "Category not found", hasItems: "Cannot delete category with items", }, schema: { nameRequired: "Name is required and must be at least 3 characters long", idRequired: "ID is required", }, }) expect(getDictionary("es").inventory.categories).toEqual({ list: { title: "Categorías", addLabel: "Agregar categoría", empty: "No se encontraron categorías.", columns: { name: "Nombre", items: "Artículos", actions: "Acciones", }, actions: { edit: "Editar categoría", delete: "Eliminar categoría", }, }, new: { title: "Nueva categoría", }, edit: { title: "Editar categoría", }, form: { nameLabel: "Nombre", namePlaceholder: "Nombre de la categoría", createSubmit: "Crear categoría", updateSubmit: "Actualizar categoría", }, delete: { label: "Eliminar categoría", pending: "Eliminando...", unknownError: "Error desconocido", }, actions: { createSuccess: "Categoría creada correctamente", createFailure: "Error al crear la categoría", updateSuccess: "Categoría actualizada correctamente", updateFailure: "Error al actualizar la categoría", deleteSuccess: "Categoría eliminada correctamente", deleteFailure: "Error al eliminar la categoría", duplicateName: "La categoría ya existe", unchangedName: "El nombre de la categoría no cambió", notFound: "Categoría no encontrada", hasItems: "No se puede eliminar una categoría con artículos", }, schema: { nameRequired: "El nombre es obligatorio y debe tener al menos 3 caracteres", idRequired: "El ID es obligatorio", }, }) }) 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("provides localized assignment copy for English and Spanish", () => { expect(getDictionary("en").inventory.assignments).toEqual({ list: { title: "Assignments", addLabel: "Add Assignment", empty: "No assignments found.", columns: { person: "Person", item: "Item", serialNumber: "Serial Number", quantity: "Quantity", actions: "Actions", }, actions: { edit: "Edit assignment", return: "Return assignment", }, }, new: { title: "New Assignment", }, edit: { title: "Edit Assignment", notFound: "Assignment not found", }, form: { personLabel: "Person", personPlaceholder: "Select a person", itemLabel: "Item", itemPlaceholder: "Select an item", assetLabel: "Asset", assetPlaceholder: "Select an asset", quantityLabel: "Quantity", quantityPlaceholder: "1", createSubmit: "Create Assignment", updateSubmit: "Update Assignment", }, fallback: { missingValue: "N/A", }, actions: { createSuccess: "Assignment created successfully", createFailure: "Error creating assignment", updateSuccess: "Assignment updated successfully", updateFailure: "Error updating assignment", returnSuccess: "Assignment returned successfully", returnFailure: "Error returning assignment", notFound: "Assignment not found", itemNotFound: "Item not found", itemInsufficientStock: "Item does not have enough stock", assetNotFound: "Asset not found", assetItemMismatch: "Asset does not belong to item", assignmentAlreadyReturned: "Assignment already returned", invalidData: "Invalid assignment data", genericFailure: "Error processing assignment", }, schema: { personRequired: "Person is required", itemIdRequired: "Item is required", quantityMinOne: "Quantity must be at least 1", assetIdRequired: "Asset ID is required when item ID is provided", idRequired: "Assignment ID is required", }, }) expect(getDictionary("es").inventory.assignments).toEqual({ list: { title: "Asignaciones", addLabel: "Agregar asignación", empty: "No se encontraron asignaciones.", columns: { person: "Persona", item: "Artículo", serialNumber: "Número de serie", quantity: "Cantidad", actions: "Acciones", }, actions: { edit: "Editar asignación", return: "Devolver asignación", }, }, new: { title: "Nueva asignación", }, edit: { title: "Editar asignación", notFound: "Asignación no encontrada", }, form: { personLabel: "Persona", personPlaceholder: "Selecciona una persona", itemLabel: "Artículo", itemPlaceholder: "Selecciona un artículo", assetLabel: "Activo", assetPlaceholder: "Selecciona un activo", quantityLabel: "Cantidad", quantityPlaceholder: "1", createSubmit: "Crear asignación", updateSubmit: "Actualizar asignación", }, fallback: { missingValue: "No disponible", }, actions: { createSuccess: "Asignación creada correctamente", createFailure: "Error al crear la asignación", updateSuccess: "Asignación actualizada correctamente", updateFailure: "Error al actualizar la asignación", returnSuccess: "Asignación devuelta correctamente", returnFailure: "Error al devolver la asignación", notFound: "Asignación no encontrada", itemNotFound: "Artículo no encontrado", itemInsufficientStock: "El artículo no tiene stock suficiente", assetNotFound: "Activo no encontrado", assetItemMismatch: "El activo no pertenece al artículo", assignmentAlreadyReturned: "La asignación ya fue devuelta", invalidData: "Datos de asignación inválidos", genericFailure: "Error al procesar la asignación", }, schema: { personRequired: "La persona es obligatoria", itemIdRequired: "El artículo es obligatorio", quantityMinOne: "La cantidad debe ser al menos 1", assetIdRequired: "El activo es obligatorio cuando se especifica el artículo", idRequired: "El ID de asignación es obligatorio", }, }) }) it("provides localized inventory asset UI copy for English and Spanish", () => { expect(getDictionary("en").inventory.assets).toEqual({ list: { title: "Assets", addLabel: "Add Asset", empty: "No assets found.", columns: { item: "Item", category: "Category", serialNumber: "Serial Number", status: "Status", actions: "Actions", }, actions: { edit: "Edit asset", }, }, new: { title: "New Asset", }, edit: { title: "Edit Asset", notFound: "Asset not found", }, form: { itemLabel: "Item", itemPlaceholder: "Select an item", serialNumberLabel: "Serial Number", serialNumberPlaceholder: "Serial number", deliveryNoteLabel: "Delivery Note", deliveryNotePlaceholder: "Delivery note", statusLabel: "Status", statusPlaceholder: "Select a status", personLabel: "Person", personPlaceholder: "Select a person", createSubmit: "Create Asset", updateSubmit: "Update Asset", }, status: { AVAILABLE: "Available", ASSIGNED: "Assigned", RESERVED: "Reserved", IN_REPAIR: "In repair", BROKEN: "Broken", STOLEN: "Stolen", DISPOSED: "Disposed", }, fallback: { unknownStatus: "Unknown status", }, actions: { createSuccess: "Asset created successfully", createFailure: "Error creating asset", updateSuccess: "Asset updated successfully", updateFailure: "Error updating asset", duplicateSerialNumber: "This serial number already exists", notFound: "Asset not found", itemNotFound: "Item not found", assignmentAlreadyReturned: "Assignment already returned", previousItemNotFound: "Previous item not found for available asset", insufficientStock: "Item does not have enough stock", personRequired: "Person is required", invalidStatus: "Invalid status", genericFailure: "Error processing asset", }, schema: { itemRequired: "Item is required", serialNumberRequired: "Serial number is required", idRequired: "ID is required", statusRequired: "Status is required", invalidCreateStatus: "Status must be Available or Assigned", invalidUpdateStatus: "Invalid status", }, }) expect(getDictionary("es").inventory.assets).toEqual({ list: { title: "Activos", addLabel: "Agregar activo", empty: "No se encontraron activos.", columns: { item: "Artículo", category: "Categoría", serialNumber: "Número de serie", status: "Estado", actions: "Acciones", }, actions: { edit: "Editar activo", }, }, new: { title: "Nuevo activo", }, edit: { title: "Editar activo", notFound: "Activo no encontrado", }, form: { itemLabel: "Artículo", itemPlaceholder: "Selecciona un artículo", serialNumberLabel: "Número de serie", serialNumberPlaceholder: "Número de serie", deliveryNoteLabel: "Remito", deliveryNotePlaceholder: "Remito", statusLabel: "Estado", statusPlaceholder: "Selecciona un estado", personLabel: "Persona", personPlaceholder: "Selecciona una persona", createSubmit: "Crear activo", updateSubmit: "Actualizar activo", }, status: { AVAILABLE: "Disponible", ASSIGNED: "Asignado", RESERVED: "Reservado", IN_REPAIR: "En reparación", BROKEN: "Roto", STOLEN: "Robado", DISPOSED: "Dado de baja", }, fallback: { unknownStatus: "Estado desconocido", }, actions: { createSuccess: "Activo creado correctamente", createFailure: "Error al crear el activo", updateSuccess: "Activo actualizado correctamente", updateFailure: "Error al actualizar el activo", duplicateSerialNumber: "El número de serie ya existe", notFound: "Activo no encontrado", itemNotFound: "Artículo no encontrado", assignmentAlreadyReturned: "La asignación ya fue devuelta", previousItemNotFound: "Artículo anterior no encontrado para el activo disponible", insufficientStock: "El artículo no tiene stock suficiente", personRequired: "La persona es obligatoria", invalidStatus: "Estado inválido", genericFailure: "Error al procesar el activo", }, schema: { itemRequired: "El artículo es obligatorio", serialNumberRequired: "El número de serie es obligatorio", idRequired: "El activo es obligatorio", statusRequired: "El estado es obligatorio", invalidCreateStatus: "El estado inicial debe ser Disponible o Asignado", invalidUpdateStatus: "Estado inválido", }, }) }) it("provides localized people UI copy for English and Spanish", () => { expect(getDictionary("en").inventory.people).toEqual({ list: { title: "People", addLabel: "Add Person", empty: "No people found.", columns: { name: "Name", email: "Email", phone: "Phone", department: "Department", role: "Role", status: "Status", actions: "Actions", }, actions: { view: "View person", edit: "Edit person", }, }, detail: { notFound: "Person not found", labels: { email: "Email", phone: "Phone", department: "Department", role: "Role", status: "Status", noUser: "No user account", }, }, new: { title: "Add Person", }, edit: { title: "Edit Person", notFound: "Person not found", }, form: { firstNameLabel: "First Name", firstNamePlaceholder: "First name", lastNameLabel: "Last Name", lastNamePlaceholder: "Last name", departmentLabel: "Department", departmentPlaceholder: "Select a department", emailLabel: "Email", emailPlaceholder: "Email", phoneLabel: "Phone", phonePlaceholder: "Phone", roleLabel: "Role", activeLabel: "Active user", newPasswordLabel: "New password", newPasswordPlaceholder: "Leave blank to keep current password", userAccountHeading: "User account", createSubmit: "Create Person", updateSubmit: "Update Person", }, fallback: { unknownDepartment: "Unknown department", unknownStatus: "Unknown status", }, departments: { IT: "IT", ENGINEERING: "Engineering", LOGISTICS: "Logistics", TRAFFIC: "Traffic", DRIVER: "Driver", ADMINISTRATION: "Administration", SALES: "Sales", OTHER: "Other", }, actions: { createSuccess: "Person created successfully", createFailure: "Failed to create person", updateSuccess: "Person updated successfully", updateFailure: "Failed to update person", duplicateEmail: "Email already exists", notFound: "Person not found", }, schema: { firstNameRequired: "First name is required", lastNameRequired: "Last name is required", departmentRequired: "Department is required", emailInvalid: "Email format is invalid", idRequired: "ID is required", userIdInvalid: "User ID must be a valid UUID", }, }) expect(getDictionary("es").inventory.people).toEqual({ list: { title: "Personas", addLabel: "Agregar persona", empty: "No se encontraron personas.", columns: { name: "Nombre", email: "Correo electrónico", phone: "Teléfono", department: "Departamento", role: "Rol", status: "Estado", actions: "Acciones", }, actions: { view: "Ver persona", edit: "Editar persona", }, }, detail: { notFound: "Persona no encontrada", labels: { email: "Correo electrónico", phone: "Teléfono", department: "Departamento", role: "Rol", status: "Estado", noUser: "Sin cuenta de usuario", }, }, new: { title: "Agregar persona", }, edit: { title: "Editar persona", notFound: "Persona no encontrada", }, form: { firstNameLabel: "Nombre", firstNamePlaceholder: "Nombre", lastNameLabel: "Apellido", lastNamePlaceholder: "Apellido", departmentLabel: "Departamento", departmentPlaceholder: "Selecciona un departamento", emailLabel: "Correo electrónico", emailPlaceholder: "Correo electrónico", phoneLabel: "Teléfono", phonePlaceholder: "Teléfono", roleLabel: "Rol", activeLabel: "Usuario activo", newPasswordLabel: "Nueva contraseña", newPasswordPlaceholder: "Déjalo vacío para mantener la contraseña actual", userAccountHeading: "Cuenta de usuario", createSubmit: "Crear persona", updateSubmit: "Actualizar persona", }, fallback: { unknownDepartment: "Departamento desconocido", unknownStatus: "Estado desconocido", }, departments: { IT: "IT", ENGINEERING: "Ingeniería", LOGISTICS: "Logística", TRAFFIC: "Tráfico", DRIVER: "Chofer", ADMINISTRATION: "Administración", SALES: "Ventas", OTHER: "Otro", }, actions: { createSuccess: "Persona creada correctamente", createFailure: "Error al crear la persona", updateSuccess: "Persona actualizada correctamente", updateFailure: "Error al actualizar la persona", duplicateEmail: "El correo electrónico ya existe", notFound: "Persona no encontrada", }, schema: { firstNameRequired: "El nombre es obligatorio", lastNameRequired: "El apellido es obligatorio", departmentRequired: "El departamento es obligatorio", emailInvalid: "El correo electrónico no es válido", idRequired: "El ID es obligatorio", userIdInvalid: "El ID de usuario debe ser un UUID válido", }, }) }) it("does not have inventory.recipients or legacy recipient keys in either locale", () => { expect("recipients" in getDictionary("en").inventory).toBe(false) expect("recipients" in getDictionary("es").inventory).toBe(false) expect("recipients" in getDictionary("en").layout.sidebar).toBe(false) expect("recipient" in getDictionary("en").layout.addMenu).toBe(false) expect("recipients" in getDictionary("es").layout.sidebar).toBe(false) expect("recipient" in getDictionary("es").layout.addMenu).toBe(false) expect("recipients" in getDictionary("en").dashboardHome.cards).toBe(false) expect("recipients" in getDictionary("es").dashboardHome.cards).toBe(false) }) 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", person: "Person", 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", person: "Persona", 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", cards: { items: { title: "Total Items", countLabel: "Total", }, assets: { title: "Total Assets", countLabel: "Total", }, people: { title: "Total People", countLabel: "Total", }, }, }) expect(getDictionary("es").dashboardHome).toEqual({ heading: "Panel de control", cards: { items: { title: "Total de artículos", countLabel: "Total", }, assets: { title: "Total de activos", countLabel: "Total", }, people: { title: "Total de personas", countLabel: "Total", }, }, }) }) it("has exact structural parity between English and Spanish dictionaries", () => { expect(extractKeyPaths(getDictionary("es"))).toEqual( extractKeyPaths(getDictionary("en")), ) }) }) function extractKeyPaths(value: unknown, prefix = ""): string[] { if (!isPlainObject(value)) return [prefix] return Object.keys(value) .sort() .flatMap((key) => extractKeyPaths(value[key], prefix ? `${prefix}.${key}` : key), ) } function isPlainObject(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value) }