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", usernameLabel: "Username", passwordLabel: "Password", submitLabel: "Sign In", }) expect(getDictionary("es").login).toEqual({ title: "Iniciar sesión", usernameLabel: "Usuario", 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", recipients: "Recipients", movements: "Movements", assignments: "Assignments", users: "Users", }, navbar: { accountLabel: "My Account", }, addMenu: { add: "Add", import: "Import", category: "Category", item: "Item", asset: "Asset", recipient: "Recipient", 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", recipients: "Destinatarios", movements: "Movimientos", assignments: "Asignaciones", users: "Usuarios", }, navbar: { accountLabel: "Mi cuenta", }, addMenu: { add: "Añadir", import: "Importar", category: "Categoría", item: "Artículo", asset: "Activo", recipient: "Destinatario", 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 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", recipientLabel: "Recipient", recipientPlaceholder: "Select a recipient", 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", recipientRequired: "Recipient 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", recipientLabel: "Destinatario", recipientPlaceholder: "Selecciona un destinatario", 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", recipientRequired: "El destinatario es obligatorio", 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("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", }, recipients: { title: "Total Recipients", 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", }, recipients: { title: "Total de destinatarios", 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) }