Reunir traducciones en ficheros JSON

This commit is contained in:
David Arranz 2026-10-07 18:58:21 +02:00
parent da21f90615
commit 3f01d5ecf1
49 changed files with 3210 additions and 3461 deletions

View File

@ -15,6 +15,7 @@ Aplicación interna para soporte técnico a pequeñas empresas.
- [Perfiles de trabajo](docs/perfiles-de-trabajo.md)
- [Buzones y correo por perfil](docs/correos-en-dispositivos.md)
- [Componentes de interfaz](docs/componentes-de-interfaz.md)
- [Traducciones de la interfaz](docs/traducciones.md)
- [Alcance y siguientes entregas](docs/plan-de-desarrollo.md)
Incluye acceso, sesiones persistentes, gestión de usuarios y compañías clientes con personas, departamentos, asignaciones con historial y sedes, junto con catálogos informáticos, cuentas, grupos, recursos, permisos y necesidades con cobertura calculada. También incluye inventario configurable, puestos con activos exclusivos y compartidos, perfiles de trabajo por persona y dispositivo, buzones independientes y correo POP/IMAP por perfil con periodos de uso e historial. Las configuraciones del entorno y los tickets se incorporarán progresivamente.

View File

@ -2,3 +2,4 @@
- Traducciones: Quiero que audites todo el código para extraer y reunir todas las cadenas de traducciones en ficheros JSON. El modelo a seguir es el módulo "core". En "modules/core/src/common/locales" se pueden ver los fichero "es.json" y "en.json" con las cadenas de cada idioma. Quiero lo mismo para el resto de módulos. Asi es más facil de mantener y de añadir nuevos idiomas ya que todo está localizado en un mismo sitio.
- Quiero añadir una página "Home" a modo de tablero inicial o dashboard inicial. Quiero que en esa página inicial se muestre información resumen para que el usuario, cuando entre, se haga una idea de la situación de un vistazo. Revisa si hay una pagina similar en otras aplicaciones similares del mercado y muestra en una lista sugerencias de la información a presentar. No cambies nada.

View File

@ -6,7 +6,7 @@
"scripts": {
"dev": "vite --host --clearScreen false",
"typecheck": "tsc -p tsconfig.json",
"test": "TSX_TSCONFIG_PATH=../../tsconfig.json node --import tsx --test src/app.test.tsx",
"test": "TSX_TSCONFIG_PATH=../../tsconfig.json node --import tsx --test src/app.test.tsx src/i18n.test.ts",
"build": "vite build",
"preview": "vite preview --host 127.0.0.1"
},

View File

@ -10,13 +10,12 @@ import {
import { useId, useState } from "react";
import { useTranslation } from "react-i18next";
const LANGUAGE_OPTIONS = [
{ value: "es", label: "Español" },
{ value: "en", label: "English" },
];
export function App() {
const { t, i18n } = useTranslation("core");
const languageOptions = ["es", "en"].map((value) => ({
value,
label: t(`languageNames.${value}`),
}));
const [languageError, setLanguageError] = useState(false);
const languageLabelId = useId();
@ -30,7 +29,7 @@ export function App() {
<div className="flex items-center gap-3 text-sm text-muted-foreground">
<span id={languageLabelId}>{t("language")}</span>
<Select
items={LANGUAGE_OPTIONS}
items={languageOptions}
onValueChange={(language) => {
if (language !== "es" && language !== "en") {
return;
@ -49,7 +48,7 @@ export function App() {
<SelectValue />
</SelectTrigger>
<SelectContent>
{LANGUAGE_OPTIONS.map((language) => (
{languageOptions.map((language) => (
<SelectItem key={language.value} value={language.value}>
{language.label}
</SelectItem>

54
apps/web/src/i18n.test.ts Normal file
View File

@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { createAppI18n } from "./i18n";
function translationLeaves(value: unknown, prefix = ""): Record<string, string> {
assert.ok(value !== null && typeof value === "object" && !Array.isArray(value));
return Object.fromEntries(
Object.entries(value).flatMap(([key, content]) => {
const path = prefix ? `${prefix}.${key}` : key;
if (typeof content === "string") {
assert.ok(content.trim(), `Traducción vacía: ${path}`);
return [[path, content]];
}
return Object.entries(translationLeaves(content, path));
})
);
}
function parameters(value: string) {
return [...value.matchAll(/{{\s*([^}]+?)\s*}}/g)].map((match) => match[1]).sort();
}
test("todos los módulos cargan claves completas y parámetros equivalentes en es y en", async () => {
const { instance, initialization } = createAppI18n();
await initialization;
for (const namespace of ["core", "identity", "companies", "access", "assets", "resources"]) {
const spanish = translationLeaves(instance.getResourceBundle("es", namespace));
const english = translationLeaves(instance.getResourceBundle("en", namespace));
assert.deepEqual(Object.keys(english).sort(), Object.keys(spanish).sort(), namespace);
for (const [key, value] of Object.entries(spanish)) {
const translated = english[key];
assert.equal(typeof translated, "string");
assert.ok(translated);
assert.deepEqual(parameters(translated), parameters(value), `${namespace}:${key}`);
for (const language of ["es", "en"]) {
assert.equal(
instance.exists(key, { ns: namespace, lng: language, fallbackLng: false }),
true
);
}
}
}
});
test("las traducciones se resuelven en inglés y el idioma desconocido utiliza castellano", async () => {
const { instance, initialization } = createAppI18n("en");
await initialization;
assert.equal(instance.t("language"), "Language");
assert.equal(instance.t("brands.title", { ns: "assets" }), "Brands");
await instance.changeLanguage("fr");
assert.equal(instance.resolvedLanguage, "es");
assert.equal(instance.t("brands.title", { ns: "assets" }), "Marcas");
});

39
docs/traducciones.md Normal file
View File

@ -0,0 +1,39 @@
# Traducciones de la interfaz
Las cadenas de interfaz se mantienen en JSON, con un fichero por idioma y módulo. Los componentes utilizan las claves existentes mediante i18next; no incorporan diccionarios de textos en TypeScript.
## Ubicación y espacios de traducción
| Módulo | Ficheros | Espacio de traducción |
| --- | --- | --- |
| Core | `modules/core/src/common/locales/es.json`, `en.json` | `core` |
| Identidad | `modules/identity/src/common/locales/es.json`, `en.json` | `identity` |
| Compañías | `modules/companies/src/common/locales/es.json`, `en.json` | `companies`, `access` |
| Activos | `modules/assets/src/common/locales/es.json`, `en.json` | `assets` |
| Recursos | `modules/resources/src/common/locales/es.json`, `en.json` | `resources` |
| Biblioteca visual | `packages/shadcn-ui/src/locales/es.json`, `en.json` | Textos propios de componentes genéricos |
Compañías conserva sus dos espacios existentes. Cada JSON contiene las propiedades raíz `companies` y `access`: la segunda reúne cuentas, accesos y ayuda contextual. Esta organización evita cambiar las claves utilizadas por las pantallas. Los demás módulos conservan su estructura de claves directa, igual que Core.
Los archivos `web/i18n.ts` solo exportan el contenido JSON. El adaptador de accesos también comprueba que la ayuda cubra todos los tipos previstos. `apps/web/src/i18n.ts` registra los módulos y mantiene castellano como idioma de reserva.
La biblioteca visual conserva sus textos en su propio paquete, sin depender de módulos de negocio ni de i18next. `entityListLabels(locale)` selecciona los textos y construye el rango de resultados. Los diálogos aceptan `closeLabel` para recibir el texto traducido del consumidor; el valor predeterminado procede del JSON inglés. Los formularios de Metis Desk utilizan sus acciones y etiquetas ya traducidas.
## Añadir o modificar textos
1. Elegir el módulo propietario del texto y una clave en inglés que describa su finalidad.
2. Añadir la misma clave en `es.json` y `en.json`, dentro de la sección correspondiente.
3. Utilizar `t("clave")` en el espacio del módulo. Conservar los parámetros `{{nombre}}` idénticos en ambos idiomas.
4. Ejecutar las pruebas y la comprobación de tipos. Las pruebas de la aplicación comprueban todas las claves cargadas, textos no vacíos y equivalencia de parámetros entre idiomas.
No duplicar textos de componentes genéricos en los módulos. Los textos específicos de una entidad pertenecen al módulo consumidor.
## Incorporar un idioma
Crear un JSON junto a los existentes en cada módulo y en la biblioteca visual, conservando las claves y parámetros. Exportar las traducciones desde los adaptadores del módulo, registrarlas en `apps/web/src/i18n.ts` y ampliar `supportedLngs`, el selector de `apps/web/src/app.tsx` y los nombres de idioma de Core. Ampliar también la selección de idioma de `entityListLabels` y las pruebas de paridad. La carga sigue siendo explícita, sin nuevas dependencias.
## Alcance de la auditoría
Se consolidaron 1.350 claves por idioma que estaban distribuidas en Identidad, Compañías, Accesos, Activos y Recursos, incluyendo formularios, estados, errores de interfaz y ayuda contextual. Core ya utilizaba JSON; ahora también contiene los nombres del selector de idioma. Se extrajeron los textos comunes de tablas y el cierre de diálogos de la biblioteca visual.
Se revisaron los textos JSX, etiquetas, placeholders y diccionarios de los módulos y la aplicación. Se mantienen como datos o elementos técnicos los nombres propios, las siglas y protocolos, los símbolos de interfaz, las claves de traducción, los identificadores, las rutas y los datos iniciales de catálogos editables. Los mensajes internos de diagnóstico y los datos de pruebas no son traducciones de interfaz y conservan su ubicación. No se modifican datos persistidos ni valores de negocio.

View File

@ -0,0 +1,273 @@
{
"brands": {
"title": "Brands",
"introduction": "Manage the brands available for assets across all companies.",
"entityLabel": "brands",
"new": "Add brand",
"edit": "Edit brand",
"active": "Active",
"inactive": "Inactive",
"name": "Brand",
"all": "All",
"search": "Search by brand...",
"loading": "Loading brands…",
"loadError": "Brands could not be loaded.",
"empty": "No brands match the search.",
"noBrand": "Unspecified",
"legacy": "Previous value",
"quickNotice": "The brand remains in the global catalog if you later cancel the asset.",
"help": "A brand identifies an asset manufacturer. It is shared across all companies and does not define commercial models.",
"examples": "Examples: LG, HP, Microsoft, Kyocera, Zebra or Clónico for computers assembled from different manufacturers.",
"guidance": "Add brands and change their availability. Built-in names are protected; custom brands can be renamed. Deactivation preserves assets and their history.",
"errors": {
"INVALID_REQUEST": "Enter a valid brand name.",
"NAME_EXISTS": "A brand already has this name, even if inactive.",
"BUILTIN_PROTECTED": "The name of a built-in brand is protected.",
"BRAND_NOT_FOUND": "This brand is no longer available.",
"INTERNAL_ERROR": "The brand could not be saved.",
"FORBIDDEN": "Your session does not allow this operation. Sign in again.",
"UNAUTHENTICATED": "Sign in again."
}
},
"support": {
"title": "Remote technical assistance",
"new": "Add method",
"edit": "Edit method",
"tool": "Tool",
"choose": "Choose a tool",
"chooseState": "Choose a status",
"newTool": "New tool",
"tools": "Assistance tools",
"editTool": "Edit tool",
"toolName": "Name",
"active": "Active",
"builtin": "Built-in",
"destination": "Identifier or destination (optional)",
"requiresVpn": "Requires VPN",
"preferred": "Preferred method",
"state": "Status",
"pending": "Pending configuration",
"available": "Available",
"retired": "Retired",
"notes": "Notes (optional)",
"empty": "No assistance methods recorded for this asset.",
"showRetired": "Include retired methods",
"history": "History",
"loading": "Loading remote assistance…",
"loadError": "Remote assistance could not be loaded.",
"help": "What is this method for?",
"description": "Records how a technician can provide remote support for this asset.",
"guidance": "You can record several methods. Only one available method can be preferred. Recording a method does not establish a connection or authorize remote work.",
"examples": "AnyDesk: 123 456 789; Supremo Control: its identifier; Remote Desktop: PORT-ANA-01, with VPN if required.",
"examplesLabel": "Examples and guidance",
"scope": "Do not enter passwords or access keys in the identifier or notes.",
"toolHelp": "What is a tool for?",
"toolDescription": "Global assistance tool catalog shared across companies.",
"toolGuidance": "Add tools you use. Deactivation prevents selection in new methods and preserves existing records. Built-in names are protected.",
"toolExamples": "AnyDesk, Supremo Control, Remote Desktop, TeamViewer or RustDesk.",
"review": "Review: the asset is retired or the tool is inactive.",
"errors": {
"INVALID_REQUEST": "Check the fields. Only an available method can be preferred.",
"ASSET_NOT_FOUND": "The asset does not belong to this company or is unavailable.",
"ASSET_RETIRED": "Only retired methods can be saved for a retired asset.",
"TOOL_UNAVAILABLE": "Select an active tool.",
"CONNECTION_NOT_FOUND": "This method is unavailable.",
"PREFERRED_EXISTS": "A preferred method already exists. Uncheck it before choosing another.",
"CONNECTION_EXISTS": "This method already exists with the same identifier or destination.",
"TOOL_NOT_FOUND": "This tool is unavailable.",
"TOOL_EXISTS": "A tool with this name already exists.",
"BUILTIN_PROTECTED": "Built-in tool names cannot be changed.",
"INTERNAL_ERROR": "The operation could not be completed."
}
},
"assetPanel": "Selected asset details",
"closeAssetPanel": "Close details panel",
"showAssetPanel": "Show details panel",
"inventory": "Assets",
"chooseValue": "Choose an option",
"relatedNotice": "This item is saved separately and remains available if you later cancel the main form.",
"newAsset": "New asset",
"editAsset": "Edit asset",
"viewAsset": "Details and history",
"template": "Template",
"site": "Site",
"person": "Assigned person",
"status": "Status",
"notes": "Notes",
"available": "Available",
"in_use": "In use",
"maintenance": "Maintenance",
"retired": "Retired",
"noAssets": "This company has no assets yet.",
"noAssignment": "Unassigned",
"noValue": "Not specified",
"characteristics": "Characteristics",
"history": "History",
"recordedBy": "Recorded by {{name}}",
"snapshotNotice": "The template is fixed when the asset is created. Its characteristics remain unchanged when the catalog changes.",
"newSite": "New site",
"newPerson": "New person",
"assetDescription": "An asset is a specific IT item belonging to this company. Its template defines the characteristics you can record.",
"assetExamples": "Ana's laptop, the reception monitor or the main site's switch.",
"assetGuidance": "Only the name, template and characteristics required by your template are mandatory. Complete other data later. Unassign the person before retiring an asset; its history is retained.",
"assetScope": "The asset belongs to this company. Site and person are optional and must belong to it.",
"saveAssetError": "The asset could not be saved.",
"inventoryErrors": {
"BRAND_UNAVAILABLE": "This brand is unavailable for new assignments.",
"SELF_RELATIONSHIP": "An asset cannot link to itself.",
"RELATIONSHIP_EXISTS": "This relationship already exists between these assets.",
"RELATIONSHIP_NOT_FOUND": "Relationship not found in this company.",
"RELATION_TYPE_UNAVAILABLE": "The relationship type must be active for this company.",
"INVALID_MEMBERS": "Select distinct valid assets.",
"WORKSTATION_NOT_FOUND": "Workstation not found in this company.",
"ASSET_IN_WORKSTATION": "An asset belongs to another active workstation. Remove it there before assigning it.",
"INVALID_REQUEST": "Check the form data.",
"INVALID_CHARACTERISTICS": "A characteristic does not match its field or type.",
"REQUIRED_CHARACTERISTICS": "Complete the template's required characteristics.",
"RETIRED_ASSIGNED": "Unassign the person before retiring the asset.",
"TEMPLATE_FIXED": "An existing asset's template cannot change.",
"TEMPLATE_UNAVAILABLE": "The template must be active and available to this company.",
"SITE_NOT_FOUND": "The site does not belong to this company.",
"PERSON_UNAVAILABLE": "The person must be active and belong to this company.",
"COMPANY_NOT_FOUND": "Company not found.",
"ASSET_NOT_FOUND": "Asset not found in this company.",
"INTERNAL_ERROR": "The operation could not be completed.",
"FORBIDDEN": "Sign in again.",
"UNAUTHENTICATED": "Sign in again."
},
"workstations": "Workstations",
"newWorkstation": "New workstation",
"addSharedWorkstationAsset": "Add a shared resource",
"exclusiveWorkstationAssets": "Exclusive assets",
"sharedWorkstationAssets": "Shared resources",
"noSharedWorkstationAssets": "No shared resources.",
"editWorkstation": "Edit workstation",
"workstationPerson": "Workstation assignee",
"workstationHistory": "Workstation history",
"workstationAssets": "Workstation assets",
"noWorkstations": "This company has no workstations yet.",
"noWorkstationAssets": "This workstation has no assets yet.",
"addWorkstationAsset": "Add an exclusive asset",
"workstationOccupied": "{{name}} · used in another workstation",
"removeWorkstationAsset": "Remove {{name}} from workstation",
"workstationCount": "{{count}} assets",
"workstationCount_one": "{{count}} asset",
"workstationCount_other": "{{count}} assets",
"workstationDescription": "A workstation groups assets a person uses regularly. Person and site are optional and it can be created without assets.",
"workstationExamples": "Ana's workstation: laptop, two monitors and a printer. One person can have several workstations.",
"workstationGuidance": "Exclusive assets can belong to one active workstation. Shared resources can be used in several workstations unless they are reserved as exclusive. Deactivation preserves composition and releases its assets; reactivation checks availability. Workstation assignment does not change individual asset assignments.",
"workstationScope": "All elements must belong to this company. Person, site and composition changes retain history.",
"workstationInAsset": "Workstation",
"noWorkstation": "No active workstation",
"relations": "Relationships",
"newRelation": "New relationship type",
"editRelation": "Edit relationship type",
"inverseName": "Name shown from the other asset",
"symmetric": "Same name in both directions",
"directional": "Directional relationship",
"relationDescription": "A relationship type describes how two assets in the same company are linked.",
"relationExamples": "Laptop → Connected to → Monitor. Virtual machine → Hosted on → Server; from the server it reads «Hosts».",
"relationGuidance": "Disable types without losing existing links. Direction is fixed after creation. Relationships retain the names they had when created.",
"linkAsset": "Link asset",
"relationshipType": "Relationship type",
"targetAsset": "Other asset",
"chooseRelation": "Select a relationship",
"chooseAsset": "Select an asset",
"noRelationships": "This asset has no current relationships.",
"endedRelationships": "Ended relationships",
"endRelationship": "End relationship",
"endRelationshipConfirm": "The relationship history will be retained.",
"relationshipHelp": "Link devices that work together or depend on others. Only assets in this company are shown.",
"relationshipExample": "For example: this laptop is connected to an external monitor.",
"relationshipGuidance": "Select the type and then the other asset. Ending a relationship does not delete assets.",
"quickRelationNotice": "The type is saved for this company even if you later cancel the relationship.",
"relationCreated": "Created by {{name}} · {{date}}",
"relationEnded": "Ended by {{name}} · {{date}}",
"yes": "Yes",
"no": "No",
"title": "Asset catalog",
"companyTitle": "Asset catalog",
"introduction": "Define the characteristics and templates used when registering assets.",
"companyIntroduction": "Use global definitions and add those needed by this company.",
"fields": "Fields",
"templates": "Templates",
"newField": "New field",
"editField": "Edit field",
"newTemplate": "New template",
"editTemplate": "Edit template",
"name": "Name",
"description": "Description (optional)",
"type": "Data type",
"unit": "Unit (optional)",
"choices": "List values",
"choicesHelp": "Enter one value per line. Existing values are retained; you can add others.",
"text": "Text",
"number": "Number",
"date": "Date",
"boolean": "Yes / No",
"choice": "List of values",
"active": "Active",
"inactive": "Inactive",
"activate": "Activate",
"deactivate": "Deactivate",
"inherited": "Global configuration",
"overridden": "Customized for this company",
"reset": "Use global configuration",
"builtin": "Built in",
"global": "Global",
"own": "Company",
"custom": "Custom",
"origin": "Origin",
"actions": "Actions",
"edit": "Edit",
"copy": "Create copy",
"copyName": "{{name}} (copy)",
"fieldCount": "{{count}} fields",
"search": "Search",
"empty": "No results.",
"loading": "Loading catalog…",
"retry": "Retry",
"save": "Save",
"saving": "Saving…",
"cancel": "Cancel",
"selectField": "Add a field to the template",
"choose": "Select a field",
"addField": "New field",
"required": "Required",
"chooseType": "Select a type",
"view": "View",
"close": "Close",
"viewDefinition": "{{name}}",
"optional": "Optional",
"remove": "Remove field: {{name}}",
"noFields": "Add at least one field. All fields are initially optional.",
"protected": "Built-in definitions are protected. You can change their availability or create a template copy.",
"helpTitle": "What is it for?",
"examplesLabel": "Show examples and guidance",
"fieldDescription": "A field describes an asset characteristic. You can reuse it in several templates.",
"fieldExamples": "Examples: Processor (text), Screen size (number), Warranty expiry (date), PoE (yes/no) or Battery status (list).",
"fieldGuidance": "Choose a useful unit, such as GB. Never store passwords or secrets. The data type is fixed after creation.",
"templateDescription": "A template groups the fields displayed for an asset type. It is not a specific device or a commercial brand or model.",
"templateExamples": "Monitor: screen size, resolution and connections. Laptop: processor, RAM and storage.",
"templateGuidance": "Characteristics are optional by default. Require only information that is always known. Removing a template field does not delete its catalog definition.",
"globalHelp": "Global scope: available to all companies.",
"companyHelp": "Company scope: additions and availability changes affect this company only.",
"disabledField": "Inactive field: retained in this template.",
"disabledFieldsNotice": "This template contains inactive fields. You can activate them in the catalog before using it.",
"quickNotice": "The field remains saved if you later cancel the template.",
"errors": {
"RELATION_DIRECTION_FIXED": "Relationship direction cannot be changed.",
"INVALID_REQUEST": "Check the form data.",
"INVALID_CHOICES": "A list needs unique non-empty values. Other data types do not accept list values.",
"INVALID_FIELDS": "The template needs distinct valid fields.",
"NAME_EXISTS": "A definition of this type already has this name.",
"FIELD_UNAVAILABLE": "Only active global or company fields can be added.",
"FIELD_TYPE_FIXED": "The data type cannot change and existing list values cannot be removed.",
"BUILTIN_PROTECTED": "Built-in definitions cannot be edited.",
"DEFINITION_NOT_FOUND": "This definition is unavailable in this scope.",
"COMPANY_NOT_FOUND": "Company not found.",
"INTERNAL_ERROR": "The operation could not be completed.",
"FORBIDDEN": "Your session does not allow this operation. Sign in again.",
"UNAUTHENTICATED": "Sign in again."
}
}

View File

@ -0,0 +1,273 @@
{
"brands": {
"title": "Marcas",
"introduction": "Gestiona las marcas disponibles para los activos de todas las compañías.",
"entityLabel": "marcas",
"new": "Añadir marca",
"edit": "Editar marca",
"active": "Activa",
"inactive": "Inactiva",
"name": "Marca",
"all": "Todas",
"search": "Buscar por marca...",
"loading": "Cargando marcas…",
"loadError": "No se han podido cargar las marcas.",
"empty": "No hay marcas que coincidan con la búsqueda.",
"noBrand": "Sin especificar",
"legacy": "Valor anterior",
"quickNotice": "La marca se guardará en el catálogo global aunque después canceles el activo.",
"help": "Una marca identifica al fabricante de un activo. Es común a todas las compañías y no define sus modelos comerciales.",
"examples": "Ejemplos: LG, HP, Microsoft, Kyocera, Zebra o Clónico para equipos montados con componentes de distintos fabricantes.",
"guidance": "Puedes añadir marcas y cambiar su disponibilidad. Las marcas de serie conservan su nombre; las personalizadas se pueden renombrar. Desactivar no borra los activos ni su historial.",
"errors": {
"INVALID_REQUEST": "Indica un nombre de marca válido.",
"NAME_EXISTS": "Ya existe una marca con ese nombre, incluso si está desactivada.",
"BUILTIN_PROTECTED": "El nombre de una marca de serie está protegido.",
"BRAND_NOT_FOUND": "La marca ya no está disponible.",
"INTERNAL_ERROR": "No se ha podido guardar la marca.",
"FORBIDDEN": "La sesión no permite esta operación. Vuelve a iniciar sesión.",
"UNAUTHENTICATED": "Vuelve a iniciar sesión."
}
},
"support": {
"title": "Asistencia técnica remota",
"new": "Añadir método",
"edit": "Editar método",
"tool": "Herramienta",
"choose": "Selecciona una herramienta",
"chooseState": "Selecciona un estado",
"newTool": "Nueva herramienta",
"tools": "Herramientas de asistencia",
"editTool": "Editar herramienta",
"toolName": "Nombre",
"active": "Activa",
"builtin": "De serie",
"destination": "Identificador o destino (opcional)",
"requiresVpn": "Requiere VPN",
"preferred": "Método preferido",
"state": "Estado",
"pending": "Pendiente de configurar",
"available": "Disponible",
"retired": "Retirado",
"notes": "Notas (opcional)",
"empty": "No hay métodos de asistencia registrados para este activo.",
"showRetired": "Mostrar métodos retirados",
"history": "Historial",
"loading": "Cargando asistencia remota…",
"loadError": "No se pudo cargar la asistencia remota.",
"help": "¿Para qué sirve este método?",
"description": "Registra cómo un técnico puede prestar soporte remoto a este activo.",
"guidance": "Puedes registrar varios métodos. Solo uno disponible puede ser el preferido. Documentar un método no establece una conexión ni concede permisos de teletrabajo.",
"examples": "AnyDesk: 123 456 789; Supremo Control: su identificador; Escritorio remoto: PORT-ANA-01, con VPN si es necesaria.",
"examplesLabel": "Ejemplos y recomendaciones",
"scope": "No introduzcas contraseñas ni claves de acceso en el identificador o las notas.",
"toolHelp": "¿Para qué sirve una herramienta?",
"toolDescription": "Catálogo global de herramientas de asistencia, común a todas las compañías.",
"toolGuidance": "Añade las herramientas que utilicéis. Desactivar impide seleccionarlas en nuevos métodos y conserva los existentes. Los nombres de serie están protegidos.",
"toolExamples": "AnyDesk, Supremo Control, Escritorio remoto, TeamViewer o RustDesk.",
"review": "Revisar: el activo está retirado o la herramienta está desactivada.",
"errors": {
"INVALID_REQUEST": "Revisa los campos. Solo un método disponible puede marcarse como preferido.",
"ASSET_NOT_FOUND": "El activo no pertenece a esta compañía o ya no está disponible.",
"ASSET_RETIRED": "En un activo retirado solo se pueden guardar métodos retirados.",
"TOOL_UNAVAILABLE": "Selecciona una herramienta activa.",
"CONNECTION_NOT_FOUND": "El método ya no está disponible.",
"PREFERRED_EXISTS": "Ya hay un método preferido. Desmárcalo antes de elegir otro.",
"CONNECTION_EXISTS": "Ya existe este método con el mismo identificador o destino.",
"TOOL_NOT_FOUND": "La herramienta ya no está disponible.",
"TOOL_EXISTS": "Ya existe una herramienta con ese nombre.",
"BUILTIN_PROTECTED": "El nombre de una herramienta de serie no se puede modificar.",
"INTERNAL_ERROR": "No se pudo completar la operación."
}
},
"assetPanel": "Ficha del activo seleccionado",
"closeAssetPanel": "Cerrar ficha lateral",
"showAssetPanel": "Mostrar ficha lateral",
"inventory": "Activos",
"chooseValue": "Selecciona una opción",
"relatedNotice": "El elemento se guarda por separado y se conserva aunque después canceles el formulario principal.",
"newAsset": "Nuevo activo",
"editAsset": "Editar activo",
"viewAsset": "Ficha e historial",
"template": "Plantilla",
"site": "Sede",
"person": "Persona asignada",
"status": "Estado",
"notes": "Notas",
"available": "Disponible",
"in_use": "En uso",
"maintenance": "En mantenimiento",
"retired": "Retirado",
"noAssets": "Esta compañía todavía no tiene activos.",
"noAssignment": "Sin asignar",
"noValue": "Sin indicar",
"characteristics": "Características",
"history": "Historial",
"recordedBy": "Registrado por {{name}}",
"snapshotNotice": "La plantilla queda fijada al crear el activo. Sus características se conservan aunque después cambie el catálogo.",
"newSite": "Nueva sede",
"newPerson": "Nueva persona",
"assetDescription": "Un activo es un elemento informático concreto de esta compañía. La plantilla indica qué características puedes registrar.",
"assetExamples": "Portátil de Ana, monitor de recepción o switch de la sede central.",
"assetGuidance": "Solo son obligatorios el nombre, la plantilla y los campos exigidos por tu plantilla. Puedes completar el resto más adelante. Retirar un activo requiere dejarlo sin persona asignada; su historial se conserva.",
"assetScope": "El activo pertenece a esta compañía. La sede y la persona son opcionales y deben pertenecer a ella.",
"saveAssetError": "No se pudo guardar el activo.",
"inventoryErrors": {
"BRAND_UNAVAILABLE": "La marca no está disponible para nuevas asignaciones.",
"SELF_RELATIONSHIP": "Un activo no puede relacionarse consigo mismo.",
"RELATIONSHIP_EXISTS": "Ya existe esa relación entre estos activos.",
"RELATIONSHIP_NOT_FOUND": "La relación no existe en esta compañía.",
"RELATION_TYPE_UNAVAILABLE": "El tipo de relación debe estar activo en esta compañía.",
"INVALID_MEMBERS": "Selecciona activos distintos y válidos.",
"WORKSTATION_NOT_FOUND": "El puesto no existe en esta compañía.",
"ASSET_IN_WORKSTATION": "Un activo ya pertenece a otro puesto activo. Retíralo de allí antes de asignarlo.",
"INVALID_REQUEST": "Revisa los datos del formulario.",
"INVALID_CHARACTERISTICS": "Alguna característica no corresponde a su campo o tipo.",
"REQUIRED_CHARACTERISTICS": "Completa las características obligatorias de la plantilla.",
"RETIRED_ASSIGNED": "Para retirar el activo, deja la persona asignada en «Sin asignar».",
"TEMPLATE_FIXED": "No se puede cambiar la plantilla de un activo existente.",
"TEMPLATE_UNAVAILABLE": "La plantilla debe estar activa y disponible para esta compañía.",
"SITE_NOT_FOUND": "La sede no pertenece a esta compañía.",
"PERSON_UNAVAILABLE": "La persona debe estar activa y pertenecer a esta compañía.",
"COMPANY_NOT_FOUND": "Compañía no encontrada.",
"ASSET_NOT_FOUND": "Activo no encontrado en esta compañía.",
"INTERNAL_ERROR": "No se pudo completar la operación.",
"FORBIDDEN": "Vuelve a iniciar sesión.",
"UNAUTHENTICATED": "Vuelve a iniciar sesión."
},
"workstations": "Puestos de trabajo",
"newWorkstation": "Nuevo puesto",
"addSharedWorkstationAsset": "Añadir un recurso compartido",
"exclusiveWorkstationAssets": "Activos exclusivos",
"sharedWorkstationAssets": "Recursos compartidos",
"noSharedWorkstationAssets": "Sin recursos compartidos.",
"editWorkstation": "Editar puesto",
"workstationPerson": "Persona del puesto",
"workstationHistory": "Historial del puesto",
"workstationAssets": "Activos del puesto",
"noWorkstations": "Esta compañía todavía no tiene puestos de trabajo.",
"noWorkstationAssets": "Este puesto todavía no tiene activos.",
"addWorkstationAsset": "Añadir un activo exclusivo",
"workstationOccupied": "{{name}} · ocupado en otro puesto",
"removeWorkstationAsset": "Retirar {{name}} del puesto",
"workstationCount": "{{count}} activos",
"workstationCount_one": "{{count}} activo",
"workstationCount_other": "{{count}} activos",
"workstationDescription": "Un puesto reúne los activos que una persona utiliza habitualmente. Puede quedar sin persona o sede y puede crearse sin activos.",
"workstationExamples": "Puesto de Ana: portátil, dos monitores y una impresora. Una persona puede tener varios puestos.",
"workstationGuidance": "Los activos exclusivos solo pueden estar en un puesto activo. Los recursos compartidos pueden utilizarse en varios puestos, siempre que no estén reservados como exclusivos. Desactivar conserva la composición y libera sus activos; reactivar comprueba su disponibilidad. El puesto no cambia las asignaciones individuales de los activos.",
"workstationScope": "Todos los elementos deben pertenecer a esta compañía. Los cambios de persona, sede y composición conservan historial.",
"workstationInAsset": "Puesto de trabajo",
"noWorkstation": "Sin puesto activo",
"relations": "Relaciones",
"newRelation": "Nuevo tipo de relación",
"editRelation": "Editar tipo de relación",
"inverseName": "Nombre visto desde el otro activo",
"symmetric": "Mismo nombre en ambos sentidos",
"directional": "Relación con dirección",
"relationDescription": "Un tipo de relación describe cómo se vinculan dos activos de una misma compañía.",
"relationExamples": "Portátil → Conectado a → Monitor. Máquina virtual → Alojado en → Servidor; desde el servidor se lee «Aloja».",
"relationGuidance": "Puedes desactivar tipos sin perder los vínculos existentes. La dirección queda fija tras el alta. Las relaciones conservan los nombres que tenían al crearse.",
"linkAsset": "Relacionar activo",
"relationshipType": "Tipo de relación",
"targetAsset": "Otro activo",
"chooseRelation": "Selecciona una relación",
"chooseAsset": "Selecciona un activo",
"noRelationships": "Este activo no tiene relaciones vigentes.",
"endedRelationships": "Relaciones retiradas",
"endRelationship": "Retirar relación",
"endRelationshipConfirm": "Se conservará el historial de esta relación.",
"relationshipHelp": "Vincula equipos que trabajan juntos o dependen de otros. Solo se muestran activos de esta compañía.",
"relationshipExample": "Por ejemplo: este portátil está conectado a un monitor externo.",
"relationshipGuidance": "Selecciona primero el tipo y después el otro activo. Retirar una relación no elimina los activos.",
"quickRelationNotice": "El tipo se guardará para esta compañía aunque después canceles la relación.",
"relationCreated": "Creada por {{name}} · {{date}}",
"relationEnded": "Retirada por {{name}} · {{date}}",
"yes": "Sí",
"no": "No",
"title": "Catálogo de activos",
"companyTitle": "Catálogo de activos",
"introduction": "Define las características y plantillas que utilizarás al registrar activos.",
"companyIntroduction": "Utiliza las definiciones globales y añade las que necesite esta compañía.",
"fields": "Campos",
"templates": "Plantillas",
"newField": "Nuevo campo",
"editField": "Editar campo",
"newTemplate": "Nueva plantilla",
"editTemplate": "Editar plantilla",
"name": "Nombre",
"description": "Descripción (opcional)",
"type": "Tipo de dato",
"unit": "Unidad (opcional)",
"choices": "Valores de la lista",
"choicesHelp": "Escribe un valor por línea. Los valores existentes se conservan; puedes añadir otros.",
"text": "Texto",
"number": "Número",
"date": "Fecha",
"boolean": "Sí / No",
"choice": "Lista de valores",
"active": "Activo",
"inactive": "Inactivo",
"activate": "Activar",
"deactivate": "Desactivar",
"inherited": "Configuración global",
"overridden": "Personalizado en esta compañía",
"reset": "Usar configuración global",
"builtin": "De serie",
"global": "Global",
"own": "De la compañía",
"custom": "Personalizado",
"origin": "Origen",
"actions": "Acciones",
"edit": "Editar",
"copy": "Crear copia",
"copyName": "{{name}} (copia)",
"fieldCount": "{{count}} campos",
"search": "Buscar",
"empty": "No hay resultados.",
"loading": "Cargando catálogo…",
"retry": "Reintentar",
"save": "Guardar",
"saving": "Guardando…",
"cancel": "Cancelar",
"selectField": "Añadir un campo a la plantilla",
"choose": "Selecciona un campo",
"addField": "Nuevo campo",
"required": "Obligatorio",
"chooseType": "Selecciona un tipo",
"view": "Ver",
"close": "Cerrar",
"viewDefinition": "{{name}}",
"optional": "Opcional",
"remove": "Retirar campo: {{name}}",
"noFields": "Añade al menos un campo. Todos son opcionales inicialmente.",
"protected": "Las definiciones de serie están protegidas. Puedes cambiar su disponibilidad o crear una copia de una plantilla.",
"helpTitle": "¿Qué es y para qué sirve?",
"examplesLabel": "Ver ejemplos y recomendaciones",
"fieldDescription": "Un campo describe una característica que podrás rellenar en un activo. Puedes reutilizarlo en varias plantillas.",
"fieldExamples": "Ejemplos: Procesador (texto), Pulgadas (número), Fin de garantía (fecha), PoE (sí/no) o Estado de batería (lista).",
"fieldGuidance": "Elige una unidad si ayuda, por ejemplo GB. No guardes contraseñas ni secretos. El tipo de dato queda fijo después del alta.",
"templateDescription": "Una plantilla reúne los campos que se mostrarán para un tipo de activo. No es un equipo concreto ni una marca o modelo comercial.",
"templateExamples": "Monitor: pulgadas, resolución y conexiones. Portátil: procesador, memoria RAM y almacenamiento.",
"templateGuidance": "Las características son opcionales por defecto. Marca como obligatorio solo lo que siempre se conozca. Retirar un campo de una plantilla no elimina el campo del catálogo.",
"globalHelp": "Ámbito global: disponible para todas las compañías.",
"companyHelp": "Ámbito de compañía: estas adiciones y cambios de disponibilidad solo afectan a esta compañía.",
"disabledField": "Campo inactivo: se conserva en esta plantilla.",
"disabledFieldsNotice": "Esta plantilla contiene campos inactivos. Puedes activarlos en el catálogo antes de utilizarla.",
"quickNotice": "El campo se guardará aunque después canceles la plantilla.",
"errors": {
"RELATION_DIRECTION_FIXED": "La dirección de una relación no se puede cambiar.",
"INVALID_REQUEST": "Revisa los datos del formulario.",
"INVALID_CHOICES": "Una lista necesita valores únicos y no vacíos. Los demás tipos no admiten valores de lista.",
"INVALID_FIELDS": "La plantilla necesita campos distintos y válidos.",
"NAME_EXISTS": "Ya existe una definición de este tipo con ese nombre.",
"FIELD_UNAVAILABLE": "Solo puedes añadir campos activos del catálogo global o de esta compañía.",
"FIELD_TYPE_FIXED": "El tipo de dato no se puede cambiar ni se pueden eliminar valores existentes de una lista.",
"BUILTIN_PROTECTED": "Las definiciones de serie no se pueden modificar.",
"DEFINITION_NOT_FOUND": "La definición no está disponible en este ámbito.",
"COMPANY_NOT_FOUND": "No se ha encontrado la compañía.",
"INTERNAL_ERROR": "No se ha podido completar la operación.",
"FORBIDDEN": "La sesión no permite esta operación. Vuelve a iniciar sesión.",
"UNAUTHENTICATED": "Vuelve a iniciar sesión."
}
}

View File

@ -1,64 +0,0 @@
export const brandsSpanish = {
title: "Marcas",
introduction: "Gestiona las marcas disponibles para los activos de todas las compañías.",
entityLabel: "marcas",
new: "Añadir marca",
edit: "Editar marca",
active: "Activa",
inactive: "Inactiva",
name: "Marca",
all: "Todas",
search: "Buscar por marca...",
loading: "Cargando marcas…",
loadError: "No se han podido cargar las marcas.",
empty: "No hay marcas que coincidan con la búsqueda.",
noBrand: "Sin especificar",
legacy: "Valor anterior",
quickNotice: "La marca se guardará en el catálogo global aunque después canceles el activo.",
help: "Una marca identifica al fabricante de un activo. Es común a todas las compañías y no define sus modelos comerciales.",
examples:
"Ejemplos: LG, HP, Microsoft, Kyocera, Zebra o Clónico para equipos montados con componentes de distintos fabricantes.",
guidance:
"Puedes añadir marcas y cambiar su disponibilidad. Las marcas de serie conservan su nombre; las personalizadas se pueden renombrar. Desactivar no borra los activos ni su historial.",
errors: {
INVALID_REQUEST: "Indica un nombre de marca válido.",
NAME_EXISTS: "Ya existe una marca con ese nombre, incluso si está desactivada.",
BUILTIN_PROTECTED: "El nombre de una marca de serie está protegido.",
BRAND_NOT_FOUND: "La marca ya no está disponible.",
INTERNAL_ERROR: "No se ha podido guardar la marca.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const brandsEnglish = {
title: "Brands",
introduction: "Manage the brands available for assets across all companies.",
entityLabel: "brands",
new: "Add brand",
edit: "Edit brand",
active: "Active",
inactive: "Inactive",
name: "Brand",
all: "All",
search: "Search by brand...",
loading: "Loading brands…",
loadError: "Brands could not be loaded.",
empty: "No brands match the search.",
noBrand: "Unspecified",
legacy: "Previous value",
quickNotice: "The brand remains in the global catalog if you later cancel the asset.",
help: "A brand identifies an asset manufacturer. It is shared across all companies and does not define commercial models.",
examples:
"Examples: LG, HP, Microsoft, Kyocera, Zebra or Clónico for computers assembled from different manufacturers.",
guidance:
"Add brands and change their availability. Built-in names are protected; custom brands can be renamed. Deactivation preserves assets and their history.",
errors: {
INVALID_REQUEST: "Enter a valid brand name.",
NAME_EXISTS: "A brand already has this name, even if inactive.",
BUILTIN_PROTECTED: "The name of a built-in brand is protected.",
BRAND_NOT_FOUND: "This brand is no longer available.",
INTERNAL_ERROR: "The brand could not be saved.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,214 +1,2 @@
import { brandsEnglish, brandsSpanish } from "./brand-i18n";
import { inventoryEnglish, inventorySpanish } from "./inventory-i18n";
import { relationshipsEnglish, relationshipsSpanish } from "./relationship-i18n";
import { supportEnglish, supportSpanish } from "./support-i18n";
import { workstationsEnglish, workstationsSpanish } from "./workstation-i18n";
export const assetsSpanish = {
brands: brandsSpanish,
support: supportSpanish,
...inventorySpanish,
...workstationsSpanish,
...relationshipsSpanish,
yes: "Sí",
no: "No",
title: "Catálogo de activos",
companyTitle: "Catálogo de activos",
introduction: "Define las características y plantillas que utilizarás al registrar activos.",
companyIntroduction: "Utiliza las definiciones globales y añade las que necesite esta compañía.",
fields: "Campos",
templates: "Plantillas",
newField: "Nuevo campo",
editField: "Editar campo",
newTemplate: "Nueva plantilla",
editTemplate: "Editar plantilla",
name: "Nombre",
description: "Descripción (opcional)",
type: "Tipo de dato",
unit: "Unidad (opcional)",
choices: "Valores de la lista",
choicesHelp:
"Escribe un valor por línea. Los valores existentes se conservan; puedes añadir otros.",
text: "Texto",
number: "Número",
date: "Fecha",
boolean: "Sí / No",
choice: "Lista de valores",
active: "Activo",
inactive: "Inactivo",
activate: "Activar",
deactivate: "Desactivar",
inherited: "Configuración global",
overridden: "Personalizado en esta compañía",
reset: "Usar configuración global",
builtin: "De serie",
global: "Global",
own: "De la compañía",
custom: "Personalizado",
origin: "Origen",
actions: "Acciones",
edit: "Editar",
copy: "Crear copia",
copyName: "{{name}} (copia)",
fieldCount: "{{count}} campos",
search: "Buscar",
empty: "No hay resultados.",
loading: "Cargando catálogo…",
retry: "Reintentar",
save: "Guardar",
saving: "Guardando…",
cancel: "Cancelar",
selectField: "Añadir un campo a la plantilla",
choose: "Selecciona un campo",
addField: "Nuevo campo",
required: "Obligatorio",
chooseType: "Selecciona un tipo",
view: "Ver",
close: "Cerrar",
viewDefinition: "{{name}}",
optional: "Opcional",
remove: "Retirar campo: {{name}}",
noFields: "Añade al menos un campo. Todos son opcionales inicialmente.",
protected:
"Las definiciones de serie están protegidas. Puedes cambiar su disponibilidad o crear una copia de una plantilla.",
helpTitle: "¿Qué es y para qué sirve?",
examplesLabel: "Ver ejemplos y recomendaciones",
fieldDescription:
"Un campo describe una característica que podrás rellenar en un activo. Puedes reutilizarlo en varias plantillas.",
fieldExamples:
"Ejemplos: Procesador (texto), Pulgadas (número), Fin de garantía (fecha), PoE (sí/no) o Estado de batería (lista).",
fieldGuidance:
"Elige una unidad si ayuda, por ejemplo GB. No guardes contraseñas ni secretos. El tipo de dato queda fijo después del alta.",
templateDescription:
"Una plantilla reúne los campos que se mostrarán para un tipo de activo. No es un equipo concreto ni una marca o modelo comercial.",
templateExamples:
"Monitor: pulgadas, resolución y conexiones. Portátil: procesador, memoria RAM y almacenamiento.",
templateGuidance:
"Las características son opcionales por defecto. Marca como obligatorio solo lo que siempre se conozca. Retirar un campo de una plantilla no elimina el campo del catálogo.",
globalHelp: "Ámbito global: disponible para todas las compañías.",
companyHelp:
"Ámbito de compañía: estas adiciones y cambios de disponibilidad solo afectan a esta compañía.",
disabledField: "Campo inactivo: se conserva en esta plantilla.",
disabledFieldsNotice:
"Esta plantilla contiene campos inactivos. Puedes activarlos en el catálogo antes de utilizarla.",
quickNotice: "El campo se guardará aunque después canceles la plantilla.",
errors: {
RELATION_DIRECTION_FIXED: "La dirección de una relación no se puede cambiar.",
INVALID_REQUEST: "Revisa los datos del formulario.",
INVALID_CHOICES:
"Una lista necesita valores únicos y no vacíos. Los demás tipos no admiten valores de lista.",
INVALID_FIELDS: "La plantilla necesita campos distintos y válidos.",
NAME_EXISTS: "Ya existe una definición de este tipo con ese nombre.",
FIELD_UNAVAILABLE: "Solo puedes añadir campos activos del catálogo global o de esta compañía.",
FIELD_TYPE_FIXED:
"El tipo de dato no se puede cambiar ni se pueden eliminar valores existentes de una lista.",
BUILTIN_PROTECTED: "Las definiciones de serie no se pueden modificar.",
DEFINITION_NOT_FOUND: "La definición no está disponible en este ámbito.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const assetsEnglish = {
brands: brandsEnglish,
support: supportEnglish,
...inventoryEnglish,
...workstationsEnglish,
...relationshipsEnglish,
yes: "Yes",
no: "No",
title: "Asset catalog",
companyTitle: "Asset catalog",
introduction: "Define the characteristics and templates used when registering assets.",
companyIntroduction: "Use global definitions and add those needed by this company.",
fields: "Fields",
templates: "Templates",
newField: "New field",
editField: "Edit field",
newTemplate: "New template",
editTemplate: "Edit template",
name: "Name",
description: "Description (optional)",
type: "Data type",
unit: "Unit (optional)",
choices: "List values",
choicesHelp: "Enter one value per line. Existing values are retained; you can add others.",
text: "Text",
number: "Number",
date: "Date",
boolean: "Yes / No",
choice: "List of values",
active: "Active",
inactive: "Inactive",
activate: "Activate",
deactivate: "Deactivate",
inherited: "Global configuration",
overridden: "Customized for this company",
reset: "Use global configuration",
builtin: "Built in",
global: "Global",
own: "Company",
custom: "Custom",
origin: "Origin",
actions: "Actions",
edit: "Edit",
copy: "Create copy",
copyName: "{{name}} (copy)",
fieldCount: "{{count}} fields",
search: "Search",
empty: "No results.",
loading: "Loading catalog…",
retry: "Retry",
save: "Save",
saving: "Saving…",
cancel: "Cancel",
selectField: "Add a field to the template",
choose: "Select a field",
addField: "New field",
required: "Required",
chooseType: "Select a type",
view: "View",
close: "Close",
viewDefinition: "{{name}}",
optional: "Optional",
remove: "Remove field: {{name}}",
noFields: "Add at least one field. All fields are initially optional.",
protected:
"Built-in definitions are protected. You can change their availability or create a template copy.",
helpTitle: "What is it for?",
examplesLabel: "Show examples and guidance",
fieldDescription:
"A field describes an asset characteristic. You can reuse it in several templates.",
fieldExamples:
"Examples: Processor (text), Screen size (number), Warranty expiry (date), PoE (yes/no) or Battery status (list).",
fieldGuidance:
"Choose a useful unit, such as GB. Never store passwords or secrets. The data type is fixed after creation.",
templateDescription:
"A template groups the fields displayed for an asset type. It is not a specific device or a commercial brand or model.",
templateExamples:
"Monitor: screen size, resolution and connections. Laptop: processor, RAM and storage.",
templateGuidance:
"Characteristics are optional by default. Require only information that is always known. Removing a template field does not delete its catalog definition.",
globalHelp: "Global scope: available to all companies.",
companyHelp: "Company scope: additions and availability changes affect this company only.",
disabledField: "Inactive field: retained in this template.",
disabledFieldsNotice:
"This template contains inactive fields. You can activate them in the catalog before using it.",
quickNotice: "The field remains saved if you later cancel the template.",
errors: {
RELATION_DIRECTION_FIXED: "Relationship direction cannot be changed.",
INVALID_REQUEST: "Check the form data.",
INVALID_CHOICES:
"A list needs unique non-empty values. Other data types do not accept list values.",
INVALID_FIELDS: "The template needs distinct valid fields.",
NAME_EXISTS: "A definition of this type already has this name.",
FIELD_UNAVAILABLE: "Only active global or company fields can be added.",
FIELD_TYPE_FIXED: "The data type cannot change and existing list values cannot be removed.",
BUILTIN_PROTECTED: "Built-in definitions cannot be edited.",
DEFINITION_NOT_FOUND: "This definition is unavailable in this scope.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};
export { default as assetsEnglish } from "../common/locales/en.json";
export { default as assetsSpanish } from "../common/locales/es.json";

View File

@ -1,128 +0,0 @@
export const inventorySpanish = {
assetPanel: "Ficha del activo seleccionado",
closeAssetPanel: "Cerrar ficha lateral",
showAssetPanel: "Mostrar ficha lateral",
inventory: "Activos",
chooseValue: "Selecciona una opción",
relatedNotice:
"El elemento se guarda por separado y se conserva aunque después canceles el formulario principal.",
newAsset: "Nuevo activo",
editAsset: "Editar activo",
viewAsset: "Ficha e historial",
template: "Plantilla",
site: "Sede",
person: "Persona asignada",
status: "Estado",
notes: "Notas",
available: "Disponible",
in_use: "En uso",
maintenance: "En mantenimiento",
retired: "Retirado",
noAssets: "Esta compañía todavía no tiene activos.",
noAssignment: "Sin asignar",
noValue: "Sin indicar",
characteristics: "Características",
history: "Historial",
recordedBy: "Registrado por {{name}}",
snapshotNotice:
"La plantilla queda fijada al crear el activo. Sus características se conservan aunque después cambie el catálogo.",
newSite: "Nueva sede",
newPerson: "Nueva persona",
assetDescription:
"Un activo es un elemento informático concreto de esta compañía. La plantilla indica qué características puedes registrar.",
assetExamples: "Portátil de Ana, monitor de recepción o switch de la sede central.",
assetGuidance:
"Solo son obligatorios el nombre, la plantilla y los campos exigidos por tu plantilla. Puedes completar el resto más adelante. Retirar un activo requiere dejarlo sin persona asignada; su historial se conserva.",
assetScope:
"El activo pertenece a esta compañía. La sede y la persona son opcionales y deben pertenecer a ella.",
saveAssetError: "No se pudo guardar el activo.",
inventoryErrors: {
BRAND_UNAVAILABLE: "La marca no está disponible para nuevas asignaciones.",
SELF_RELATIONSHIP: "Un activo no puede relacionarse consigo mismo.",
RELATIONSHIP_EXISTS: "Ya existe esa relación entre estos activos.",
RELATIONSHIP_NOT_FOUND: "La relación no existe en esta compañía.",
RELATION_TYPE_UNAVAILABLE: "El tipo de relación debe estar activo en esta compañía.",
INVALID_MEMBERS: "Selecciona activos distintos y válidos.",
WORKSTATION_NOT_FOUND: "El puesto no existe en esta compañía.",
ASSET_IN_WORKSTATION:
"Un activo ya pertenece a otro puesto activo. Retíralo de allí antes de asignarlo.",
INVALID_REQUEST: "Revisa los datos del formulario.",
INVALID_CHARACTERISTICS: "Alguna característica no corresponde a su campo o tipo.",
REQUIRED_CHARACTERISTICS: "Completa las características obligatorias de la plantilla.",
RETIRED_ASSIGNED: "Para retirar el activo, deja la persona asignada en «Sin asignar».",
TEMPLATE_FIXED: "No se puede cambiar la plantilla de un activo existente.",
TEMPLATE_UNAVAILABLE: "La plantilla debe estar activa y disponible para esta compañía.",
SITE_NOT_FOUND: "La sede no pertenece a esta compañía.",
PERSON_UNAVAILABLE: "La persona debe estar activa y pertenecer a esta compañía.",
COMPANY_NOT_FOUND: "Compañía no encontrada.",
ASSET_NOT_FOUND: "Activo no encontrado en esta compañía.",
INTERNAL_ERROR: "No se pudo completar la operación.",
FORBIDDEN: "Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const inventoryEnglish = {
assetPanel: "Selected asset details",
closeAssetPanel: "Close details panel",
showAssetPanel: "Show details panel",
inventory: "Assets",
chooseValue: "Choose an option",
relatedNotice:
"This item is saved separately and remains available if you later cancel the main form.",
newAsset: "New asset",
editAsset: "Edit asset",
viewAsset: "Details and history",
template: "Template",
site: "Site",
person: "Assigned person",
status: "Status",
notes: "Notes",
available: "Available",
in_use: "In use",
maintenance: "Maintenance",
retired: "Retired",
noAssets: "This company has no assets yet.",
noAssignment: "Unassigned",
noValue: "Not specified",
characteristics: "Characteristics",
history: "History",
recordedBy: "Recorded by {{name}}",
snapshotNotice:
"The template is fixed when the asset is created. Its characteristics remain unchanged when the catalog changes.",
newSite: "New site",
newPerson: "New person",
assetDescription:
"An asset is a specific IT item belonging to this company. Its template defines the characteristics you can record.",
assetExamples: "Ana's laptop, the reception monitor or the main site's switch.",
assetGuidance:
"Only the name, template and characteristics required by your template are mandatory. Complete other data later. Unassign the person before retiring an asset; its history is retained.",
assetScope:
"The asset belongs to this company. Site and person are optional and must belong to it.",
saveAssetError: "The asset could not be saved.",
inventoryErrors: {
BRAND_UNAVAILABLE: "This brand is unavailable for new assignments.",
SELF_RELATIONSHIP: "An asset cannot link to itself.",
RELATIONSHIP_EXISTS: "This relationship already exists between these assets.",
RELATIONSHIP_NOT_FOUND: "Relationship not found in this company.",
RELATION_TYPE_UNAVAILABLE: "The relationship type must be active for this company.",
INVALID_MEMBERS: "Select distinct valid assets.",
WORKSTATION_NOT_FOUND: "Workstation not found in this company.",
ASSET_IN_WORKSTATION:
"An asset belongs to another active workstation. Remove it there before assigning it.",
INVALID_REQUEST: "Check the form data.",
INVALID_CHARACTERISTICS: "A characteristic does not match its field or type.",
REQUIRED_CHARACTERISTICS: "Complete the template's required characteristics.",
RETIRED_ASSIGNED: "Unassign the person before retiring the asset.",
TEMPLATE_FIXED: "An existing asset's template cannot change.",
TEMPLATE_UNAVAILABLE: "The template must be active and available to this company.",
SITE_NOT_FOUND: "The site does not belong to this company.",
PERSON_UNAVAILABLE: "The person must be active and belong to this company.",
COMPANY_NOT_FOUND: "Company not found.",
ASSET_NOT_FOUND: "Asset not found in this company.",
INTERNAL_ERROR: "The operation could not be completed.",
FORBIDDEN: "Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,64 +0,0 @@
export const relationshipsSpanish = {
relations: "Relaciones",
newRelation: "Nuevo tipo de relación",
editRelation: "Editar tipo de relación",
inverseName: "Nombre visto desde el otro activo",
symmetric: "Mismo nombre en ambos sentidos",
directional: "Relación con dirección",
relationDescription:
"Un tipo de relación describe cómo se vinculan dos activos de una misma compañía.",
relationExamples:
"Portátil → Conectado a → Monitor. Máquina virtual → Alojado en → Servidor; desde el servidor se lee «Aloja».",
relationGuidance:
"Puedes desactivar tipos sin perder los vínculos existentes. La dirección queda fija tras el alta. Las relaciones conservan los nombres que tenían al crearse.",
linkAsset: "Relacionar activo",
relationshipType: "Tipo de relación",
targetAsset: "Otro activo",
chooseRelation: "Selecciona una relación",
chooseAsset: "Selecciona un activo",
noRelationships: "Este activo no tiene relaciones vigentes.",
endedRelationships: "Relaciones retiradas",
endRelationship: "Retirar relación",
endRelationshipConfirm: "Se conservará el historial de esta relación.",
relationshipHelp:
"Vincula equipos que trabajan juntos o dependen de otros. Solo se muestran activos de esta compañía.",
relationshipExample: "Por ejemplo: este portátil está conectado a un monitor externo.",
relationshipGuidance:
"Selecciona primero el tipo y después el otro activo. Retirar una relación no elimina los activos.",
quickRelationNotice:
"El tipo se guardará para esta compañía aunque después canceles la relación.",
relationCreated: "Creada por {{name}} · {{date}}",
relationEnded: "Retirada por {{name}} · {{date}}",
};
export const relationshipsEnglish = {
relations: "Relationships",
newRelation: "New relationship type",
editRelation: "Edit relationship type",
inverseName: "Name shown from the other asset",
symmetric: "Same name in both directions",
directional: "Directional relationship",
relationDescription:
"A relationship type describes how two assets in the same company are linked.",
relationExamples:
"Laptop → Connected to → Monitor. Virtual machine → Hosted on → Server; from the server it reads «Hosts».",
relationGuidance:
"Disable types without losing existing links. Direction is fixed after creation. Relationships retain the names they had when created.",
linkAsset: "Link asset",
relationshipType: "Relationship type",
targetAsset: "Other asset",
chooseRelation: "Select a relationship",
chooseAsset: "Select an asset",
noRelationships: "This asset has no current relationships.",
endedRelationships: "Ended relationships",
endRelationship: "End relationship",
endRelationshipConfirm: "The relationship history will be retained.",
relationshipHelp:
"Link devices that work together or depend on others. Only assets in this company are shown.",
relationshipExample: "For example: this laptop is connected to an external monitor.",
relationshipGuidance:
"Select the type and then the other asset. Ending a relationship does not delete assets.",
quickRelationNotice:
"The type is saved for this company even if you later cancel the relationship.",
relationCreated: "Created by {{name}} · {{date}}",
relationEnded: "Ended by {{name}} · {{date}}",
};

View File

@ -1,108 +0,0 @@
export const supportSpanish = {
title: "Asistencia técnica remota",
new: "Añadir método",
edit: "Editar método",
tool: "Herramienta",
choose: "Selecciona una herramienta",
chooseState: "Selecciona un estado",
newTool: "Nueva herramienta",
tools: "Herramientas de asistencia",
editTool: "Editar herramienta",
toolName: "Nombre",
active: "Activa",
builtin: "De serie",
destination: "Identificador o destino (opcional)",
requiresVpn: "Requiere VPN",
preferred: "Método preferido",
state: "Estado",
pending: "Pendiente de configurar",
available: "Disponible",
retired: "Retirado",
notes: "Notas (opcional)",
empty: "No hay métodos de asistencia registrados para este activo.",
showRetired: "Mostrar métodos retirados",
history: "Historial",
loading: "Cargando asistencia remota…",
loadError: "No se pudo cargar la asistencia remota.",
help: "¿Para qué sirve este método?",
description: "Registra cómo un técnico puede prestar soporte remoto a este activo.",
guidance:
"Puedes registrar varios métodos. Solo uno disponible puede ser el preferido. Documentar un método no establece una conexión ni concede permisos de teletrabajo.",
examples:
"AnyDesk: 123 456 789; Supremo Control: su identificador; Escritorio remoto: PORT-ANA-01, con VPN si es necesaria.",
examplesLabel: "Ejemplos y recomendaciones",
scope: "No introduzcas contraseñas ni claves de acceso en el identificador o las notas.",
toolHelp: "¿Para qué sirve una herramienta?",
toolDescription: "Catálogo global de herramientas de asistencia, común a todas las compañías.",
toolGuidance:
"Añade las herramientas que utilicéis. Desactivar impide seleccionarlas en nuevos métodos y conserva los existentes. Los nombres de serie están protegidos.",
toolExamples: "AnyDesk, Supremo Control, Escritorio remoto, TeamViewer o RustDesk.",
review: "Revisar: el activo está retirado o la herramienta está desactivada.",
errors: {
INVALID_REQUEST: "Revisa los campos. Solo un método disponible puede marcarse como preferido.",
ASSET_NOT_FOUND: "El activo no pertenece a esta compañía o ya no está disponible.",
ASSET_RETIRED: "En un activo retirado solo se pueden guardar métodos retirados.",
TOOL_UNAVAILABLE: "Selecciona una herramienta activa.",
CONNECTION_NOT_FOUND: "El método ya no está disponible.",
PREFERRED_EXISTS: "Ya hay un método preferido. Desmárcalo antes de elegir otro.",
CONNECTION_EXISTS: "Ya existe este método con el mismo identificador o destino.",
TOOL_NOT_FOUND: "La herramienta ya no está disponible.",
TOOL_EXISTS: "Ya existe una herramienta con ese nombre.",
BUILTIN_PROTECTED: "El nombre de una herramienta de serie no se puede modificar.",
INTERNAL_ERROR: "No se pudo completar la operación.",
},
};
export const supportEnglish = {
title: "Remote technical assistance",
new: "Add method",
edit: "Edit method",
tool: "Tool",
choose: "Choose a tool",
chooseState: "Choose a status",
newTool: "New tool",
tools: "Assistance tools",
editTool: "Edit tool",
toolName: "Name",
active: "Active",
builtin: "Built-in",
destination: "Identifier or destination (optional)",
requiresVpn: "Requires VPN",
preferred: "Preferred method",
state: "Status",
pending: "Pending configuration",
available: "Available",
retired: "Retired",
notes: "Notes (optional)",
empty: "No assistance methods recorded for this asset.",
showRetired: "Include retired methods",
history: "History",
loading: "Loading remote assistance…",
loadError: "Remote assistance could not be loaded.",
help: "What is this method for?",
description: "Records how a technician can provide remote support for this asset.",
guidance:
"You can record several methods. Only one available method can be preferred. Recording a method does not establish a connection or authorize remote work.",
examples:
"AnyDesk: 123 456 789; Supremo Control: its identifier; Remote Desktop: PORT-ANA-01, with VPN if required.",
examplesLabel: "Examples and guidance",
scope: "Do not enter passwords or access keys in the identifier or notes.",
toolHelp: "What is a tool for?",
toolDescription: "Global assistance tool catalog shared across companies.",
toolGuidance:
"Add tools you use. Deactivation prevents selection in new methods and preserves existing records. Built-in names are protected.",
toolExamples: "AnyDesk, Supremo Control, Remote Desktop, TeamViewer or RustDesk.",
review: "Review: the asset is retired or the tool is inactive.",
errors: {
INVALID_REQUEST: "Check the fields. Only an available method can be preferred.",
ASSET_NOT_FOUND: "The asset does not belong to this company or is unavailable.",
ASSET_RETIRED: "Only retired methods can be saved for a retired asset.",
TOOL_UNAVAILABLE: "Select an active tool.",
CONNECTION_NOT_FOUND: "This method is unavailable.",
PREFERRED_EXISTS: "A preferred method already exists. Uncheck it before choosing another.",
CONNECTION_EXISTS: "This method already exists with the same identifier or destination.",
TOOL_NOT_FOUND: "This tool is unavailable.",
TOOL_EXISTS: "A tool with this name already exists.",
BUILTIN_PROTECTED: "Built-in tool names cannot be changed.",
INTERNAL_ERROR: "The operation could not be completed.",
},
};

View File

@ -1,60 +0,0 @@
export const workstationsSpanish = {
workstations: "Puestos de trabajo",
newWorkstation: "Nuevo puesto",
addSharedWorkstationAsset: "Añadir un recurso compartido",
exclusiveWorkstationAssets: "Activos exclusivos",
sharedWorkstationAssets: "Recursos compartidos",
noSharedWorkstationAssets: "Sin recursos compartidos.",
editWorkstation: "Editar puesto",
workstationPerson: "Persona del puesto",
workstationHistory: "Historial del puesto",
workstationAssets: "Activos del puesto",
noWorkstations: "Esta compañía todavía no tiene puestos de trabajo.",
noWorkstationAssets: "Este puesto todavía no tiene activos.",
addWorkstationAsset: "Añadir un activo exclusivo",
workstationOccupied: "{{name}} · ocupado en otro puesto",
removeWorkstationAsset: "Retirar {{name}} del puesto",
workstationCount: "{{count}} activos",
workstationCount_one: "{{count}} activo",
workstationCount_other: "{{count}} activos",
workstationDescription:
"Un puesto reúne los activos que una persona utiliza habitualmente. Puede quedar sin persona o sede y puede crearse sin activos.",
workstationExamples:
"Puesto de Ana: portátil, dos monitores y una impresora. Una persona puede tener varios puestos.",
workstationGuidance:
"Los activos exclusivos solo pueden estar en un puesto activo. Los recursos compartidos pueden utilizarse en varios puestos, siempre que no estén reservados como exclusivos. Desactivar conserva la composición y libera sus activos; reactivar comprueba su disponibilidad. El puesto no cambia las asignaciones individuales de los activos.",
workstationScope:
"Todos los elementos deben pertenecer a esta compañía. Los cambios de persona, sede y composición conservan historial.",
workstationInAsset: "Puesto de trabajo",
noWorkstation: "Sin puesto activo",
};
export const workstationsEnglish = {
workstations: "Workstations",
newWorkstation: "New workstation",
addSharedWorkstationAsset: "Add a shared resource",
exclusiveWorkstationAssets: "Exclusive assets",
sharedWorkstationAssets: "Shared resources",
noSharedWorkstationAssets: "No shared resources.",
editWorkstation: "Edit workstation",
workstationPerson: "Workstation assignee",
workstationHistory: "Workstation history",
workstationAssets: "Workstation assets",
noWorkstations: "This company has no workstations yet.",
noWorkstationAssets: "This workstation has no assets yet.",
addWorkstationAsset: "Add an exclusive asset",
workstationOccupied: "{{name}} · used in another workstation",
removeWorkstationAsset: "Remove {{name}} from workstation",
workstationCount: "{{count}} assets",
workstationCount_one: "{{count}} asset",
workstationCount_other: "{{count}} assets",
workstationDescription:
"A workstation groups assets a person uses regularly. Person and site are optional and it can be created without assets.",
workstationExamples:
"Ana's workstation: laptop, two monitors and a printer. One person can have several workstations.",
workstationGuidance:
"Exclusive assets can belong to one active workstation. Shared resources can be used in several workstations unless they are reserved as exclusive. Deactivation preserves composition and releases its assets; reactivation checks availability. Workstation assignment does not change individual asset assignments.",
workstationScope:
"All elements must belong to this company. Person, site and composition changes retain history.",
workstationInAsset: "Workstation",
noWorkstation: "No active workstation",
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ test("cuentas ofrecen caducidad opcional y campos sin contraseña", async () =>
test("la ayuda explica todos los elementos en ambos idiomas y distingue el ámbito", async () => {
const { EntityHelp } = await import("./entity-help");
const { helpSpanish, helpEnglish } = await import("./help-i18n");
const { accessSpanish: helpSpanish, accessEnglish: helpEnglish } = await import("./i18n");
for (const [language, content] of [
["es", helpSpanish],
["en", helpEnglish],

View File

@ -1,7 +1,7 @@
import { ContextHelp } from "@repo/shadcn-ui/components/context-help";
import { useTranslation } from "react-i18next";
import type { HelpKind } from "./help-i18n";
import type { HelpKind } from "./i18n";
export function EntityHelp({ kind, scope }: { kind: HelpKind; scope?: string }) {
const { t } = useTranslation("access");

View File

@ -1,272 +0,0 @@
import type { AccessKind } from "../../common";
export type HelpKind = AccessKind | "company" | "person" | "department" | "site" | "assignment";
export const helpSpanish = {
helpTitle: "¿Qué es y para qué sirve?",
helpExamples: "Ver ejemplos y recomendaciones",
helpButton: "Ayuda",
helpClose: "Cerrar",
helpGlobalScope:
"Ámbito global: estará disponible para todas las compañías. Aquí solo se comparte la definición; las cuentas y los accesos pertenecen a cada compañía.",
helpCompanyScope:
"Ámbito de compañía: este elemento solo estará disponible en esta compañía. También puedes utilizar los elementos del catálogo global.",
helpContent: {
system: {
description:
"Un sistema identifica el servicio o plataforma donde existen cuentas y accesos. Sirve para organizar cuentas, entornos y grupos relacionados.",
examples: "Dominio; Microsoft 365; ERP; Brevo; Zoom; Canva.",
guidance:
"Crea el sistema antes de registrar sus cuentas. No representa una cuenta concreta ni guarda credenciales.",
},
resource_type: {
description:
"Un tipo de recurso clasifica los elementos a los que se puede acceder. Permite definir qué permisos tienen sentido para cada clase.",
examples: "Carpeta compartida; aplicación; impresora.",
guidance:
"Define primero el tipo y después sus permisos. Una carpeta concreta se registra como recurso.",
},
permission: {
description:
"Un permiso define una acción posible sobre un tipo de recurso. Después puedes concederlo a una cuenta o a un grupo.",
examples: "Lectura o modificación para carpetas; uso para impresoras.",
guidance:
"Selecciona el tipo de recurso al que corresponde. Registrar el permiso no lo concede a nadie.",
},
template: {
description:
"Una plantilla de grupo facilita crear grupos similares en distintas compañías, usando un nombre y una descripción comunes.",
examples: "Administración; Dirección; Soporte.",
guidance:
"Copia valores iniciales al crear un grupo. Cada compañía conserva sus miembros y accesos; los cambios posteriores en la plantilla no se propagan.",
},
environment: {
description: "Un entorno distingue instalaciones o ámbitos dentro de un mismo sistema.",
examples: "ERP → Producción y Pruebas; Dominio → Oficina.",
guidance:
"Usa un entorno cuando necesites diferenciar cuentas o grupos del mismo sistema. Pertenece a esta compañía.",
},
account: {
description:
"Una cuenta es la identidad utilizada para iniciar sesión en un sistema de la compañía. Puede ser personal, compartida o técnica.",
examples:
"ana.garcia en Dominio; administracion@example.com en Microsoft 365; svc_backup como cuenta técnica.",
guidance:
"Después vincula las personas correspondientes. La caducidad es opcional. No se almacenan contraseñas ni se crea acceso a Metis Desk.",
},
account_person: {
description:
"Esta relación indica quién utiliza una cuenta o quién es responsable de ella, conservando sus periodos e historial.",
examples:
"Ana como titular de ana.garcia; Ana y Luis autorizados a usar administracion@example.com.",
guidance:
"Una cuenta personal tiene un titular; una compartida admite varias personas; una técnica puede tener un responsable. Configuración refleja el trabajo técnico realizado.",
},
group: {
description:
"Un grupo reúne cuentas del mismo sistema para gestionar accesos conjuntamente. Pertenece a una compañía.",
examples: "Grupo Administración del dominio; grupo Ventas del ERP.",
guidance:
"Puedes partir de una plantilla global. Añade cuentas mediante pertenencias y concede permisos al grupo. No hay grupos dentro de otros grupos.",
},
membership: {
description:
"Una pertenencia vincula una cuenta con un grupo durante un periodo. Sirve para justificar los accesos obtenidos mediante ese grupo.",
examples: "ana.garcia pertenece al grupo Administración desde su incorporación.",
guidance:
"Indica si está pendiente de configurar o ya configurada. Cerrar el periodo puede dejar pendiente retirar el acceso real.",
},
resource: {
description: "Un recurso es un elemento concreto al que una cuenta o un grupo puede acceder.",
examples:
"Carpeta Administración con referencia \\\\SERVIDOR\\Administracion; aplicación ERP; impresora Recepción.",
guidance:
"Selecciona su tipo. Después registra las concesiones de permisos. Esta ficha no configura el recurso real.",
},
grant: {
description:
"Una concesión registra un permiso otorgado a una cuenta o a un grupo sobre un recurso concreto.",
examples: "Grupo Administración → modificación → carpeta Administración.",
guidance:
"Elige cuenta o grupo como destinatario. La autorización y su configuración técnica se registran por separado.",
},
need: {
description:
"Una necesidad describe qué acceso requiere una persona para trabajar. Sirve para comprobar si sus cuentas y grupos lo cubren.",
examples: "Ana necesita lectura y modificación sobre la carpeta Administración.",
guidance:
"Selecciona los permisos requeridos y después vincula su cobertura. Registrar la necesidad no concede acceso.",
},
coverage: {
description:
"Una cobertura vincula una necesidad con la vía que proporciona sus permisos: una concesión directa o a través de un grupo.",
examples:
"La necesidad de Ana se cubre mediante ana.garcia → grupo Administración → permiso de modificación.",
guidance:
"Se comprueba que la vía sea vigente y esté configurada. Si faltan permisos o hay accesos pendientes, la necesidad sigue requiriendo revisión.",
},
company: {
description:
"Una compañía reúne las personas, sedes, cuentas y recursos que gestionas para una organización.",
examples: "Talleres Rivera; Asesoría Norte.",
guidance:
"Activa indica si la compañía está en uso. Su información se conserva cuando se desactiva.",
},
person: {
description:
"Una persona pertenece a una compañía y puede ser contacto, utilizar cuentas y estar asignada a departamentos.",
examples: "Ana García; puesto general: Responsable de administración.",
guidance:
"Esta ficha no da acceso a Metis Desk. Una persona puede pertenecer a varios departamentos con un puesto diferente en cada uno.",
},
department: {
description: "Un departamento organiza las personas dentro de una compañía.",
examples: "Administración; Ventas; Soporte.",
guidance:
"Las pertenencias se añaden desde la ficha de la persona. No se conceden permisos informáticos por pertenecer a un departamento.",
},
site: {
description: "Una sede representa una ubicación de la compañía.",
examples: "Oficina central; Almacén; dirección: Calle Mayor 12, 28013 Madrid.",
guidance: "La dirección es opcional y admite varias líneas.",
},
assignment: {
description:
"Una asignación registra la pertenencia de una persona a un departamento, su puesto y su periodo.",
examples: "Ana → Administración → Contable; también pertenece a Compras como Responsable.",
guidance:
"El puesto en el departamento puede ser diferente del puesto general. Puedes indicar cuál es su departamento principal y conservar el historial.",
},
} satisfies Record<HelpKind, { description: string; examples: string; guidance: string }>,
};
export const helpEnglish = {
helpTitle: "What is it and what is it for?",
helpExamples: "Show examples and recommendations",
helpButton: "Help",
helpClose: "Close",
helpGlobalScope:
"Global scope: available to all companies. Only the definition is shared; accounts and access belong to each company.",
helpCompanyScope:
"Company scope: this element is only available in this company. You can also use global catalogue elements.",
helpContent: {
system: {
description:
"A system identifies the service or platform where accounts and access exist. It organizes related accounts, environments and groups.",
examples: "Domain; Microsoft 365; ERP; Brevo; Zoom; Canva.",
guidance:
"Create the system before registering accounts. It is not an individual account and does not store credentials.",
},
resource_type: {
description:
"A resource type classifies elements people can access and determines which permissions apply.",
examples: "Shared folder; application; printer.",
guidance:
"Define the type and its permissions first. Register an individual folder as a resource.",
},
permission: {
description:
"A permission defines an action available for a resource type. You can then grant it to an account or group.",
examples: "Read or modify for folders; use for printers.",
guidance: "Select the relevant resource type. Defining a permission does not grant access.",
},
template: {
description:
"A group template helps create similar groups across companies using a common name and description.",
examples: "Administration; Management; Support.",
guidance:
"It copies initial values. Each company keeps its own members and access; later template changes do not propagate.",
},
environment: {
description: "An environment distinguishes installations or scopes within the same system.",
examples: "ERP → Production and Test; Domain → Office.",
guidance:
"Use it to distinguish accounts or groups in the same system. It belongs to this company.",
},
account: {
description:
"An account is the identity used to sign into a company system. It can be personal, shared or technical.",
examples:
"ana.garcia in Domain; administration@example.com in Microsoft 365; svc_backup as a technical account.",
guidance:
"Link the relevant people afterwards. Expiry is optional. Passwords are not stored and this does not grant access to Metis Desk.",
},
account_person: {
description:
"This relationship records who uses an account or is responsible for it, keeping periods and history.",
examples: "Ana owns ana.garcia; Ana and Luis can use administration@example.com.",
guidance:
"Personal accounts have one owner; shared accounts allow several people; technical accounts may have a responsible person. Configuration records technical work.",
},
group: {
description:
"A group collects accounts from the same system to manage access together. It belongs to one company.",
examples: "Administration domain group; Sales ERP group.",
guidance:
"You can start from a global template. Add accounts through memberships and grant permissions to the group. Nested groups are not supported.",
},
membership: {
description:
"A membership links an account to a group for a period and explains access obtained through that group.",
examples: "ana.garcia joins the Administration group on arrival.",
guidance:
"Record whether configuration is pending or complete. Ending the period may leave actual access pending removal.",
},
resource: {
description: "A resource is a specific element an account or group can access.",
examples:
"Administration folder at \\\\SERVER\\Administration; ERP application; Reception printer.",
guidance:
"Select its type, then record permission grants. This record does not configure the actual resource.",
},
grant: {
description:
"A grant records a permission given to an account or group on a specific resource.",
examples: "Administration group → modify → Administration folder.",
guidance:
"Choose an account or group as the recipient. Authorization and technical configuration are recorded separately.",
},
need: {
description:
"A need describes the access a person requires for work and helps check whether accounts and groups provide it.",
examples: "Ana needs read and modify access to the Administration folder.",
guidance:
"Select required permissions, then link coverage. Recording a need does not grant access.",
},
coverage: {
description:
"Coverage links a need to the access path providing its permissions: directly or through a group.",
examples:
"Ana's need is covered through ana.garcia → Administration group → modify permission.",
guidance:
"The path must be current and configured. Missing permissions or pending access mean the need still requires review.",
},
company: {
description:
"A company brings together the people, sites, accounts and resources managed for an organization.",
examples: "Rivera Workshops; North Consulting.",
guidance: "Active indicates whether it is in use. Its information is kept when deactivated.",
},
person: {
description:
"A person belongs to a company and may be a contact, use accounts and belong to departments.",
examples: "Ana García; general position: Administration manager.",
guidance:
"This record does not grant access to Metis Desk. A person can belong to several departments with a different position in each.",
},
department: {
description: "A department organizes people within a company.",
examples: "Administration; Sales; Support.",
guidance:
"Add memberships from the person's record. Department membership does not grant computer access.",
},
site: {
description: "A site represents a company location.",
examples: "Head office; Warehouse; address: 12 Main Street, London.",
guidance: "The address is optional and supports multiple lines.",
},
assignment: {
description: "An assignment records a person's department membership, position and period.",
examples: "Ana → Administration → Accountant; also Purchasing → Manager.",
guidance:
"Department positions can differ from the general position. You can mark a primary department and keep history.",
},
} satisfies Record<HelpKind, { description: string; examples: string; guidance: string }>,
};

View File

@ -1,286 +1,15 @@
import { helpEnglish, helpSpanish } from "./help-i18n";
import type { AccessKind } from "../../common";
import english from "../../common/locales/en.json";
import spanish from "../../common/locales/es.json";
export type HelpKind = AccessKind | "company" | "person" | "department" | "site" | "assignment";
type HelpContent = Record<HelpKind, { description: string; examples: string; guidance: string }>;
export const accessSpanish = {
...helpSpanish,
quickAddPerson: "Nueva persona",
quickAddEntry: "Añadir: {{name}}",
quickGlobalNotice:
"Se guardará en el catálogo global y estará disponible para todas las compañías, aunque canceles el formulario principal.",
quickExisting:
"Ya existe un elemento activo con este nombre. Puedes seleccionarlo sin crear otro.",
quickUseExisting: "Usar existente",
quickAddSystem: "Añadir sistema",
quickAddEnvironment: "Añadir entorno",
quickSaveSelect: "Guardar y seleccionar",
quickEnvironmentContext: "Sistema: {{name}}",
quickCreateNotice:
"Se guardará en el catálogo de esta compañía, aunque después canceles el formulario principal.",
title: "Cuentas y accesos",
catalogTitle: "Catálogos informáticos",
catalogHelp:
"Definiciones comunes para todas las compañías. Las compañías pueden añadir sus propios elementos.",
workspaceHelp: "Cuentas, grupos, recursos y necesidades de esta compañía.",
sections: "Secciones de cuentas y accesos",
newSystem: "Nuevo sistema",
newResourceType: "Nuevo tipo de recurso",
newPermission: "Nuevo permiso",
newTemplate: "Nueva plantilla de grupo",
newEnvironment: "Nuevo entorno",
newAccount: "Nueva cuenta",
newAccountPerson: "Vincular persona",
newGroup: "Nuevo grupo",
newMembership: "Añadir cuenta a grupo",
newResource: "Nuevo recurso",
newGrant: "Conceder permiso",
newNeed: "Nueva necesidad",
newCoverage: "Vincular cobertura",
name: "Nombre",
active: "Activo",
inactive: "Inactivo",
state: "Estado",
actions: "Acciones",
edit: "Editar",
history: "Historial",
global: "Global",
search: "Buscar",
empty: "Todavía no hay elementos en esta sección.",
choose: "Selecciona una opción",
optional: " (opcional)",
save: "Guardar",
saving: "Guardando…",
cancel: "Cancelar",
close: "Cerrar",
back: "Volver al listado",
loading: "Cargando…",
retry: "Reintentar",
systemId: "Sistema",
environmentId: "Entorno",
resourceTypeId: "Tipo de recurso",
description: "Descripción",
login: "Identificador de acceso",
usage: "Uso",
expiresOn: "Fecha de caducidad",
notes: "Observaciones",
accountId: "Cuenta",
personId: "Persona",
groupId: "Grupo",
identifier: "Identificador en el sistema",
templateId: "Plantilla global",
resourceId: "Recurso",
permissionId: "Permiso",
permissionIds: "Permisos necesarios",
reference: "Ubicación o referencia",
needId: "Necesidad",
grantId: "Permiso concedido",
accountPersonId: "Relación de la persona con la cuenta",
membershipId: "Pertenencia a grupo",
startDate: "Fecha de inicio",
endDate: "Fecha de fin",
configuration: "Configuración",
personal: "Personal",
shared: "Compartida",
technical: "Técnica",
pending: "Pendiente de configurar",
configured: "Configurada",
removal_pending: "Pendiente de retirar",
removed: "Retirada",
never_configured: "Cerrada sin configurar",
open: "Abierta",
covered: "Cubierta",
notCovered: "Sin cubrir",
missingPermissions: "Permisos pendientes",
review: "Revisión",
actor: "Registrado por",
yes: "Sí",
no: "No",
noPermissions: "Define primero permisos para el tipo de recurso seleccionado.",
invalid: "Revisa los campos obligatorios y sus formatos.",
accountHelp:
"Estas cuentas no permiten entrar en Metis Desk. Sistema, entorno y uso quedan fijos desde el alta. Las contraseñas no se almacenan aquí.",
coverageHelp:
"Vincula la necesidad con un permiso y la relación de la persona con la cuenta. Si el permiso viene de un grupo, selecciona también su pertenencia.",
templateHelp:
"La plantilla facilita el alta de grupos. Sus cambios no modifican grupos existentes.",
formHelp: "Los cambios conservan el historial. Sin fecha de fin, la relación permanece abierta.",
configurationHelp:
"Al cerrar una relación configurada quedará pendiente su retirada. Guarda primero el cierre y después confirma la retirada cuando se haya realizado.",
kinds: {
system: "Sistemas",
resource_type: "Tipos de recurso",
permission: "Permisos disponibles",
template: "Plantillas de grupos",
environment: "Entornos",
account: "Cuentas",
account_person: "Personas y cuentas",
group: "Grupos",
membership: "Miembros de grupos",
resource: "Recursos",
grant: "Permisos concedidos",
need: "Necesidades",
coverage: "Cobertura",
},
reviews: {
incomplete: "Pendiente de completar",
expired: "Caducada: revisar",
inactive_person: "Persona inactiva: revisar",
inactive_target: "Destino inactivo o caducado: revisar",
unjustified: "Acceso sin necesidad cubierta: revisar",
uncovered: "Necesidad pendiente de cubrir",
pending_configuration: "Configuración pendiente",
pending_removal: "Retirada pendiente",
inactive_catalog: "Elemento de catálogo inactivo: revisar",
},
errors: {
INVALID_REQUEST: "Revisa los datos y el estado de configuración.",
ACCESS_NOT_FOUND: "No se encuentra el elemento.",
COMPANY_NOT_FOUND: "No se encuentra la compañía.",
ACCESS_CONFLICT: "Ya existe un elemento equivalente o se solapan los periodos.",
INACTIVE_TARGET: "El elemento o persona seleccionado está inactivo o caducado.",
IMMUTABLE_FIELDS: "No se pueden cambiar las relaciones o campos fijos de este elemento.",
SCOPE_MISMATCH: "Los elementos no pertenecen al ámbito, sistema o entorno compatibles.",
UNAUTHENTICATED: "Inicia sesión para continuar.",
FORBIDDEN: "No se puede realizar esta operación.",
INTERNAL_ERROR: "No se ha podido completar la operación.",
},
};
export const accessEnglish = {
...helpEnglish,
quickAddPerson: "New person",
quickAddEntry: "Add: {{name}}",
quickGlobalNotice:
"It will be saved in the global catalogue for all companies even if you cancel the main form.",
quickExisting:
"An active entry with this name already exists. You can select it without creating another.",
quickUseExisting: "Use existing",
quickAddSystem: "Add system",
quickAddEnvironment: "Add environment",
quickSaveSelect: "Save and select",
quickEnvironmentContext: "System: {{name}}",
quickCreateNotice:
"It will be saved in this company's catalogue even if you later cancel the main form.",
title: "Accounts and access",
catalogTitle: "IT catalogs",
catalogHelp: "Shared definitions for all companies. Companies can add their own entries.",
workspaceHelp: "Accounts, groups, resources and needs for this company.",
sections: "Account and access sections",
newSystem: "New system",
newResourceType: "New resource type",
newPermission: "New permission",
newTemplate: "New group template",
newEnvironment: "New environment",
newAccount: "New account",
newAccountPerson: "Link person",
newGroup: "New group",
newMembership: "Add account to group",
newResource: "New resource",
newGrant: "Grant permission",
newNeed: "New need",
newCoverage: "Link coverage",
name: "Name",
active: "Active",
inactive: "Inactive",
state: "State",
actions: "Actions",
edit: "Edit",
history: "History",
global: "Global",
search: "Search",
empty: "No entries in this section yet.",
choose: "Choose an option",
optional: " (optional)",
save: "Save",
saving: "Saving…",
cancel: "Cancel",
close: "Close",
back: "Back to list",
loading: "Loading…",
retry: "Retry",
systemId: "System",
environmentId: "Environment",
resourceTypeId: "Resource type",
description: "Description",
login: "Login identifier",
usage: "Usage",
expiresOn: "Expiration date",
notes: "Notes",
accountId: "Account",
personId: "Person",
groupId: "Group",
identifier: "System identifier",
templateId: "Global template",
resourceId: "Resource",
permissionId: "Permission",
permissionIds: "Required permissions",
reference: "Location or reference",
needId: "Need",
grantId: "Permission grant",
accountPersonId: "Person's account relationship",
membershipId: "Group membership",
startDate: "Start date",
endDate: "End date",
configuration: "Configuration",
personal: "Personal",
shared: "Shared",
technical: "Technical",
pending: "Pending configuration",
configured: "Configured",
removal_pending: "Pending removal",
removed: "Removed",
never_configured: "Closed without configuration",
open: "Open",
covered: "Covered",
notCovered: "Not covered",
missingPermissions: "Missing permissions",
review: "Review",
actor: "Recorded by",
yes: "Yes",
no: "No",
noPermissions: "Define permissions for the selected resource type first.",
invalid: "Check required fields and formats.",
accountHelp:
"These accounts do not sign in to Metis Desk. System, environment and usage are fixed at creation. Passwords are not stored here.",
coverageHelp:
"Link a need to a grant and the person's account relationship. For a group grant, also select the account's membership.",
templateHelp: "Templates help create groups. Changes do not update existing groups.",
formHelp: "Changes retain their history. Without an end date, a relationship remains open.",
configurationHelp:
"Closing a configured relationship leaves removal pending. Save the closure first, then confirm removal once completed.",
kinds: {
system: "Systems",
resource_type: "Resource types",
permission: "Available permissions",
template: "Group templates",
environment: "Environments",
account: "Accounts",
account_person: "People and accounts",
group: "Groups",
membership: "Group members",
resource: "Resources",
grant: "Permission grants",
need: "Needs",
coverage: "Coverage",
},
reviews: {
incomplete: "Incomplete",
expired: "Expired: review",
inactive_person: "Inactive person: review",
inactive_target: "Inactive or expired target: review",
unjustified: "Access without covered need: review",
uncovered: "Need not covered",
pending_configuration: "Configuration pending",
pending_removal: "Removal pending",
inactive_catalog: "Inactive catalog entry: review",
},
errors: {
INVALID_REQUEST: "Check values and configuration state.",
ACCESS_NOT_FOUND: "Entry not found.",
COMPANY_NOT_FOUND: "Company not found.",
ACCESS_CONFLICT: "A matching entry exists or periods overlap.",
INACTIVE_TARGET: "Selected entry or person is inactive or expired.",
IMMUTABLE_FIELDS: "Fixed fields and relationships cannot be changed.",
SCOPE_MISMATCH: "Entries do not have compatible scope, system or environment.",
UNAUTHENTICATED: "Sign in to continue.",
FORBIDDEN: "This action is not allowed.",
INTERNAL_ERROR: "The action could not be completed.",
},
...english.access,
helpContent: english.access.helpContent satisfies HelpContent,
};
export const accessSpanish = {
...spanish.access,
helpContent: spanish.access.helpContent satisfies HelpContent,
};

View File

@ -1,83 +0,0 @@
export const applicationsSpanish = {
title: "Aplicaciones del perfil",
new: "Configurar aplicación",
edit: "Editar configuración de aplicación",
resource: "Aplicación",
newResource: "Nueva aplicación",
delivery: "Forma de uso",
installed: "Instalada en el equipo",
web: "Acceso web",
version: "Versión (opcional)",
versionValue: "Versión: {{version}}",
search: "Buscar aplicación, perfil, persona o equipo…",
empty: "Todavía no hay aplicaciones registradas en estos perfiles.",
review:
"Revisar: hay una retirada pendiente o el recurso, perfil, persona o equipo ya no está disponible.",
fixed:
"El perfil y la aplicación quedan fijos. Para otro destino, cierra esta configuración y crea una nueva.",
help: "¿Para qué sirve esta configuración?",
description:
"Registra qué aplicaciones necesita una persona en el perfil de su equipo y si están listas para usar.",
scope:
"La aplicación es un recurso de esta compañía. Su proveedor y URL se gestionan en Recursos; la versión y el estado corresponden a este perfil.",
examples:
"Thunderbird instalado por petición de Ana; Zoom corporativo instalado; ERP utilizado desde el navegador.",
guidance:
"Pendiente de configurar indica una necesidad; Configurado indica disponibilidad comprobada. No instala programas ni concede permisos. No exige una licencia o cuenta informática. Las altas rápidas guardadas se conservan aunque canceles este formulario.",
errors: {
INVALID_REQUEST: "Revisa el formulario. La versión solo corresponde a aplicaciones instaladas.",
APPLICATION_REFERENCE_NOT_FOUND:
"Selecciona un perfil y un recurso de una plantilla de aplicación de esta compañía.",
APPLICATION_REFERENCE_UNAVAILABLE:
"El perfil, la persona, el equipo o la aplicación no están disponibles para configurar.",
APPLICATION_TARGET_IMMUTABLE: "El perfil y la aplicación no se pueden cambiar.",
APPLICATION_ALREADY_OPEN: "Esta aplicación ya tiene una configuración abierta en el perfil.",
INVALID_APPLICATION_TRANSITION: "El cambio de estado no está permitido.",
APPLICATION_CONFIGURATION_NOT_FOUND: "No se ha encontrado la configuración.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const applicationsEnglish = {
title: "Profile applications",
new: "Configure application",
edit: "Edit application configuration",
resource: "Application",
newResource: "New application",
delivery: "Usage",
installed: "Installed on device",
web: "Web access",
version: "Version (optional)",
versionValue: "Version: {{version}}",
search: "Search application, profile, person or device…",
empty: "No applications have been registered for these profiles yet.",
review: "Review: removal is pending or the resource, profile, person or device is unavailable.",
fixed:
"The profile and application are fixed. For another target, close this configuration and create a new one.",
help: "What is this configuration for?",
description:
"Record the applications a person needs in their device profile and whether they are ready to use.",
scope:
"The application is a company resource. Manage its provider and URL in Resources; version and state belong to this profile.",
examples:
"Thunderbird installed at Ana's request; installed corporate Zoom; ERP accessed in a browser.",
guidance:
"Pending means a need; Configured means verified availability. This does not install software or grant permissions. No license or account is required. Saved quick additions remain if you cancel this form.",
errors: {
INVALID_REQUEST: "Check the form. Version applies only to installed applications.",
APPLICATION_REFERENCE_NOT_FOUND:
"Select a company profile and a resource from an application template.",
APPLICATION_REFERENCE_UNAVAILABLE:
"The profile, person, device or application is unavailable for configuration.",
APPLICATION_TARGET_IMMUTABLE: "The profile and application cannot change.",
APPLICATION_ALREADY_OPEN: "This application already has an open configuration in the profile.",
INVALID_APPLICATION_TRANSITION: "This state change is not allowed.",
APPLICATION_CONFIGURATION_NOT_FOUND: "Configuration not found.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed. Try again.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,82 +0,0 @@
export const certificatesSpanish = {
title: "Certificados del perfil",
new: "Configurar certificado",
edit: "Editar configuración de certificado",
resource: "Certificado digital",
newResource: "Nuevo certificado digital",
location: "Almacén o ubicación (opcional)",
expiryValue: "Caducidad: {{date}}",
expired: "Caducado",
locationValue: "Ubicación: {{location}}",
search: "Buscar certificado, perfil, persona o equipo…",
empty: "Todavía no hay certificados registrados en estos perfiles.",
review:
"Revisar: hay una retirada pendiente, el certificado ha caducado o el recurso, perfil, persona o equipo ya no está disponible.",
fixed:
"El perfil y el certificado quedan fijos. Para otro destino, cierra esta configuración y crea una nueva.",
help: "¿Para qué sirve esta configuración?",
description:
"Registra qué certificados necesita una persona en el perfil de su equipo y si están listos para usar.",
scope:
"El certificado es un recurso de la compañía. Titular, emisor y caducidad se gestionan en Recursos; la ubicación y el estado corresponden a este perfil.",
examples:
"Almacén de Windows del usuario, almacén de Firefox o tarjeta criptográfica. Un certificado puede utilizarse en varios perfiles.",
guidance:
"Pendiente de configurar indica una necesidad; Configurado indica disponibilidad comprobada. La ubicación puede dejarse vacía. Este registro no instala, autoriza ni retira certificados reales. No adjuntes certificados, claves privadas, PIN o contraseñas. Las altas rápidas guardadas se conservan aunque canceles este formulario.",
errors: {
INVALID_REQUEST: "Revisa los datos del formulario.",
CERTIFICATE_REFERENCE_NOT_FOUND:
"Selecciona un perfil y un recurso de una plantilla de certificado digital de esta compañía.",
CERTIFICATE_REFERENCE_UNAVAILABLE:
"El certificado ha caducado o el perfil, persona, equipo o recurso no están disponibles para configurar.",
CERTIFICATE_TARGET_IMMUTABLE: "El perfil y el certificado no se pueden cambiar.",
CERTIFICATE_ALREADY_OPEN: "Este certificado ya tiene una configuración abierta en el perfil.",
INVALID_CERTIFICATE_TRANSITION: "El cambio de estado no está permitido.",
CERTIFICATE_CONFIGURATION_NOT_FOUND: "No se ha encontrado la configuración.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const certificatesEnglish = {
title: "Profile certificates",
new: "Configure certificate",
edit: "Edit certificate configuration",
resource: "Digital certificate",
newResource: "New digital certificate",
location: "Store or location (optional)",
expiryValue: "Expires: {{date}}",
expired: "Expired",
locationValue: "Location: {{location}}",
search: "Search certificate, profile, person or device…",
empty: "No certificates have been registered for these profiles yet.",
review:
"Review: removal is pending, the certificate has expired, or the resource, profile, person or device is unavailable.",
fixed:
"The profile and certificate are fixed. For another target, close this configuration and create a new one.",
help: "What is this configuration for?",
description:
"Record the certificates a person needs in their device profile and whether they are ready to use.",
scope:
"The certificate is a company resource. Manage holder, issuer and expiry in Resources; location and state belong to this profile.",
examples:
"Windows user store, Firefox store or smart card. A certificate may be used in several profiles.",
guidance:
"Pending means a need; Configured means verified availability. Location may be left empty. This record does not install, authorize or remove real certificates. Do not attach certificates, private keys, PINs or passwords. Saved quick additions remain if you cancel this form.",
errors: {
INVALID_REQUEST: "Check the form data.",
CERTIFICATE_REFERENCE_NOT_FOUND:
"Select a company profile and a resource from a digital certificate template.",
CERTIFICATE_REFERENCE_UNAVAILABLE:
"The certificate has expired or the profile, person, device or resource is unavailable for configuration.",
CERTIFICATE_TARGET_IMMUTABLE: "The profile and certificate cannot change.",
CERTIFICATE_ALREADY_OPEN: "This certificate already has an open configuration in the profile.",
INVALID_CERTIFICATE_TRANSITION: "This state change is not allowed.",
CERTIFICATE_CONFIGURATION_NOT_FOUND: "Configuration not found.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed. Try again.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,38 +0,0 @@
export const foldersSpanish={
title:"Carpetas del perfil",new:"Configurar carpeta",edit:"Editar configuración de carpeta",empty:"Todavía no hay carpetas configuradas en perfiles.",
search:"Buscar carpeta, ruta o perfil",need:"Necesidad de acceso",newNeed:"Nueva necesidad de acceso",path:"Ruta compartida",letter:"Letra de unidad (opcional)",noLetter:"Acceso por ruta, sin letra",currentPath:"Ruta actual del recurso",useCurrentPath:"Utilizar la ruta actual",
fixed:"Perfil y necesidad quedan fijados desde el alta.",permissions:"Permisos necesarios",covered:"Permisos con cobertura registrada",uncovered:"Hay permisos pendientes de cubrir o revisar.",
review:"Revisar: cambió la ruta, terminó la necesidad de acceso o alguno de sus elementos ya no está disponible.",
help:"¿Para qué sirve esta configuración?",description:"Registra cómo accede una persona a una carpeta compartida desde un perfil concreto. La necesidad de acceso aporta el recurso y sus permisos; esta ficha documenta la configuración del equipo.",
guidance:"La referencia del recurso debe contener una ruta UNC de servidor y carpeta compartida. La letra es opcional y no puede repetirse entre configuraciones abiertas del mismo perfil. Una carpeta mapeada no demuestra que se hayan concedido sus permisos.",
examples:"General en SOLETE01, mapeada como G:; Administración como H:; Pedidos como P:. También puedes acceder directamente por la ruta, sin letra de unidad.",
scope:"Los cambios conservan ruta, letra, estado, fecha y técnico. Retirar documenta lo comprobado; Metis Desk no cambia permisos ni desmonta unidades en los equipos.",
errors:{
INVALID_REQUEST:"Revisa los campos del formulario.",INVALID_FOLDER_PATH:"Introduce una ruta UNC válida en la referencia del recurso.",INVALID_DRIVE_LETTER:"La letra debe ser una sola letra, de A a Z.",
FOLDER_SCOPE_MISMATCH:"El perfil y la necesidad deben pertenecer a la compañía y a la misma persona.",FOLDER_TARGET_IMMUTABLE:"Para cambiar perfil o necesidad, crea otra configuración.",
FOLDER_UNAVAILABLE:"El perfil, el recurso y la necesidad deben estar vigentes. La ruta debe coincidir con la referencia actual del recurso.",
FOLDER_MAPPING_EXISTS:"Ya hay una configuración abierta de esta carpeta en el perfil.",DRIVE_LETTER_IN_USE:"Esta letra ya está reservada por otra carpeta de este perfil.",
INVALID_FOLDER_TRANSITION:"El estado no permite ese cambio. Una carpeta configurada pasa primero a pendiente de retirar.",
FOLDER_MAPPING_NOT_FOUND:"La configuración no está disponible.",COMPANY_NOT_FOUND:"La compañía no está disponible.",INTERNAL_ERROR:"No se ha podido completar la operación. Inténtalo de nuevo.",
UNAUTHENTICATED:"La sesión ha caducado.",FORBIDDEN:"Recarga la página para autorizar la operación."
},
};
export const foldersEnglish={
title:"Profile folders",new:"Configure folder",edit:"Edit folder configuration",empty:"No folders configured in profiles yet.",
search:"Search folder, path or profile",need:"Access requirement",newNeed:"New access requirement",path:"Shared path",letter:"Drive letter (optional)",noLetter:"Direct path access, no drive letter",currentPath:"Current resource path",useCurrentPath:"Use current path",
fixed:"Profile and access requirement are fixed after creation.",permissions:"Required permissions",covered:"Permissions have recorded coverage",uncovered:"Some permissions need coverage or review.",
review:"Review: the path changed, the access requirement ended or a referenced item is unavailable.",
help:"What is this configuration for?",description:"Records how a person accesses a shared folder from a specific profile. The access requirement defines the resource and permissions; this record documents device configuration.",
guidance:"The resource reference must contain a UNC server and share path. The drive letter is optional and cannot repeat among open configurations of the same profile. A mapped folder does not prove permissions were granted.",
examples:"General on SOLETE01 mapped as G:; Administration as H:; Orders as P:. Direct path access without a drive letter is also supported.",
scope:"Changes retain path, letter, status, date and technician. Removal records verified work; Metis Desk does not change permissions or unmount device drives.",
errors:{
INVALID_REQUEST:"Check the form fields.",INVALID_FOLDER_PATH:"Enter a valid UNC path in the resource reference.",INVALID_DRIVE_LETTER:"Use a single letter from A to Z.",
FOLDER_SCOPE_MISMATCH:"Profile and access requirement must belong to the company and the same person.",FOLDER_TARGET_IMMUTABLE:"Create another configuration to change the profile or requirement.",
FOLDER_UNAVAILABLE:"Profile, resource and requirement must be current. The path must match the resource's current reference.",
FOLDER_MAPPING_EXISTS:"An open folder configuration already exists for this profile.",DRIVE_LETTER_IN_USE:"Another folder in this profile already reserves this letter.",
INVALID_FOLDER_TRANSITION:"Invalid status change. A configured folder must first be marked for removal.",
FOLDER_MAPPING_NOT_FOUND:"The configuration is unavailable.",COMPANY_NOT_FOUND:"The company is unavailable.",INTERNAL_ERROR:"Unable to complete the operation. Try again.",
UNAUTHENTICATED:"Your session expired.",FORBIDDEN:"Reload the page to authorize the operation."
},
};

View File

@ -1,271 +1,5 @@
import { applicationsEnglish, applicationsSpanish } from "./applications/application-i18n";
import { certificatesEnglish, certificatesSpanish } from "./certificates/certificate-i18n";
import { foldersEnglish, foldersSpanish } from "./folders/folder-i18n";
import {
interventionTemplatesEnglish,
interventionTemplatesSpanish,
} from "./intervention-templates/template-i18n";
import { interventionsEnglish, interventionsSpanish } from "./interventions/intervention-i18n";
import { mailEnglish, mailSpanish } from "./mail/mail-i18n";
import { mailboxEnglish, mailboxSpanish } from "./mail/mailbox-i18n";
import { printersEnglish, printersSpanish } from "./printers/printer-i18n";
import { processesEnglish, processesSpanish } from "./processes/process-i18n";
import { environmentEnglish, environmentSpanish } from "./profiles/environment-i18n";
import { profilesEnglish, profilesSpanish } from "./profiles/profile-i18n";
import { remoteEnglish, remoteSpanish } from "./remote/remote-i18n";
import { teleworkEnglish, teleworkSpanish } from "./telework/telework-i18n";
import { ticketsEnglish, ticketsSpanish } from "./tickets/ticket-i18n";
import english from "../common/locales/en.json";
import spanish from "../common/locales/es.json";
export const companiesSpanish = {
interventionTemplates: interventionTemplatesSpanish,
interventions: interventionsSpanish,
tickets: ticketsSpanish,
processes: processesSpanish,
remote: remoteSpanish,
telework: teleworkSpanish,
environment: environmentSpanish,
certificates: certificatesSpanish,
applications: applicationsSpanish,
printers: printersSpanish,
mail: mailSpanish,
mailbox: mailboxSpanish,
folders: foldersSpanish,
profiles: profilesSpanish,
companySpaces: "Espacios de compañías",
globalCatalogs: "Catálogos globales",
supportWorkspace: "Tu espacio de soporte",
workspaceSections: "Secciones del espacio",
people: "Personas",
newPerson: "Nueva persona",
editPerson: "Editar persona",
backToPeople: "Volver a personas",
generalPosition: "Puesto general (opcional)",
emailOptional: "Email (opcional)",
phoneOptional: "Teléfono (opcional)",
activePerson: "Persona activa",
inactivePerson: "Persona inactiva",
isContact: "Contacto de la compañía",
personHelp:
"Esta persona no tiene acceso a Metis Desk. Sus cuentas informáticas se gestionarán por separado.",
deactivationHelp:
"Se conservarán sus asignaciones e historial. Revisa las pertenencias que continúen abiertas.",
inactiveMembershipWarning:
"Esta persona está inactiva y conserva asignaciones a departamentos pendientes de revisar.",
contactsOnly: "Solo contactos",
searchPerson: "Buscar persona",
noPeople: "Todavía no hay personas en esta compañía.",
departments: "Departamentos",
department: "Departamento",
newDepartment: "Nuevo departamento",
editDepartment: "Editar departamento",
activeDepartment: "Departamento activo",
inactiveDepartment: "Departamento inactivo",
noDepartments: "Todavía no hay departamentos.",
departmentDeactivationHelp:
"Al desactivarlo se conservan las asignaciones existentes, pero no se admiten nuevas.",
departmentAssignments: "Asignaciones a departamentos",
addAssignment: "Asignar departamento",
editAssignment: "Editar asignación",
noAssignments: "Esta persona todavía no tiene departamentos asignados.",
chooseDepartment: "Selecciona un departamento",
departmentPosition: "Puesto en el departamento (opcional)",
startDate: "Fecha de inicio",
endDate: "Fecha de fin (opcional)",
primary: "Principal",
primaryDepartment: "Departamento principal",
currentAssignment: "Abierta",
assignmentHelp:
"Sin fecha de fin, la asignación sigue abierta. Para registrar un cambio de puesto con un nuevo periodo, cierra esta asignación y crea la siguiente; puedes usar el mismo día. Las ediciones conservan su historial.",
history: "Historial",
assignmentHistory: "Historial de la asignación",
createDepartmentFirst: "Puedes añadir un departamento desde el formulario de asignación.",
yes: "Sí",
no: "No",
title: "Compañías",
help: "Gestiona las empresas clientes, sus personas de contacto y sus sedes.",
name: "Nombre",
status: "Estado",
actions: "Acciones",
active: "Activa",
inactive: "Inactiva",
activeCompany: "Compañía activa",
inactiveCompany: "Compañía inactiva",
activeContact: "Contacto activo",
newCompany: "Nueva compañía",
editCompany: "Editar compañía",
newContact: "Nuevo contacto",
editContact: "Editar contacto",
newSite: "Nueva sede",
editSite: "Editar sede",
contacts: "Contactos",
sites: "Sedes",
position: "Puesto",
email: "Email",
phone: "Teléfono",
addressOptional: "Dirección (opcional)",
addressHelp: "Puedes introducir varias líneas, hasta 10.000 caracteres.",
noAddress: "Sin dirección",
empty: "Todavía no hay compañías. Crea la primera para añadir contactos y sedes.",
noContacts: "Todavía no hay contactos en esta compañía.",
noSites: "Todavía no hay sedes en esta compañía.",
search: "Buscar compañía",
searchPlaceholder: "Nombre de la compañía…",
noResults: "No se han encontrado compañías.",
companySections: "Información de la compañía",
open: "Abrir ficha",
edit: "Editar",
back: "Volver a compañías",
close: "Cerrar edición",
cancel: "Cancelar",
save: "Guardar",
saving: "Guardando…",
saved: "Cambios guardados.",
loading: "Cargando compañías…",
retry: "Reintentar",
invalidForm: "Revisa los campos del formulario.",
errors: {
PERSON_NOT_FOUND: "La persona no pertenece a esta compañía o no está disponible.",
DEPARTMENT_NOT_FOUND: "El departamento no pertenece a esta compañía o no está disponible.",
DEPARTMENT_NAME_EXISTS: "Ya existe un departamento con ese nombre en esta compañía.",
ASSIGNMENT_NOT_FOUND: "La asignación no pertenece a esta persona o no está disponible.",
ASSIGNMENT_CONFLICT:
"Las fechas se solapan con otra asignación o ya existe un departamento principal. Revisa las asignaciones abiertas.",
INACTIVE_ASSIGNMENT_TARGET:
"Activa la persona y el departamento antes de crear o reabrir una asignación.",
INVALID_REQUEST: "Revisa los datos enviados. El teléfono debe ser un número válido.",
COMPANY_NOT_FOUND: "La compañía no está disponible.",
CONTACT_NOT_FOUND: "El contacto no pertenece a esta compañía o no está disponible.",
SITE_NOT_FOUND: "La sede no pertenece a esta compañía o no está disponible.",
UNAUTHENTICATED: "La sesión ha caducado. Vuelve a entrar.",
FORBIDDEN: "No se ha podido autorizar la operación. Recarga la página.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
},
};
export const companiesEnglish = {
interventionTemplates: interventionTemplatesEnglish,
interventions: interventionsEnglish,
tickets: ticketsEnglish,
processes: processesEnglish,
remote: remoteEnglish,
telework: teleworkEnglish,
environment: environmentEnglish,
certificates: certificatesEnglish,
applications: applicationsEnglish,
printers: printersEnglish,
mail: mailEnglish,
mailbox: mailboxEnglish,
folders: foldersEnglish,
profiles: profilesEnglish,
companySpaces: "Company spaces",
globalCatalogs: "Global catalogs",
supportWorkspace: "Your support workspace",
workspaceSections: "Workspace sections",
people: "People",
newPerson: "New person",
editPerson: "Edit person",
backToPeople: "Back to people",
generalPosition: "General job title (optional)",
emailOptional: "Email (optional)",
phoneOptional: "Phone (optional)",
activePerson: "Active person",
inactivePerson: "Inactive person",
isContact: "Company contact",
personHelp: "This person cannot access Metis Desk. Their IT accounts will be managed separately.",
deactivationHelp: "Assignments and history will be retained. Review any open memberships.",
inactiveMembershipWarning:
"This person is inactive and has department assignments that need review.",
contactsOnly: "Contacts only",
searchPerson: "Find person",
noPeople: "No people in this company yet.",
departments: "Departments",
department: "Department",
newDepartment: "New department",
editDepartment: "Edit department",
activeDepartment: "Active department",
inactiveDepartment: "Inactive department",
noDepartments: "No departments yet.",
departmentDeactivationHelp: "Deactivation retains existing assignments but prevents new ones.",
departmentAssignments: "Department assignments",
addAssignment: "Assign department",
editAssignment: "Edit assignment",
noAssignments: "This person has no department assignments yet.",
chooseDepartment: "Choose a department",
departmentPosition: "Department job title (optional)",
startDate: "Start date",
endDate: "End date (optional)",
primary: "Primary",
primaryDepartment: "Primary department",
currentAssignment: "Open",
assignmentHelp:
"Without an end date, the assignment remains open. To record a new job period, close this assignment and create the next one; both may use the same day. Edits retain history.",
history: "History",
assignmentHistory: "Assignment history",
createDepartmentFirst: "You can add a department from the assignment form.",
yes: "Yes",
no: "No",
title: "Companies",
help: "Manage client companies, their contact people and their sites.",
name: "Name",
status: "Status",
actions: "Actions",
active: "Active",
inactive: "Inactive",
activeCompany: "Active company",
inactiveCompany: "Inactive company",
activeContact: "Active contact",
newCompany: "New company",
editCompany: "Edit company",
newContact: "New contact",
editContact: "Edit contact",
newSite: "New site",
editSite: "Edit site",
contacts: "Contacts",
sites: "Sites",
position: "Job title",
email: "Email",
phone: "Phone",
addressOptional: "Address (optional)",
addressHelp: "Enter multiple lines, up to 10,000 characters.",
noAddress: "No address",
empty: "No companies yet. Create the first one to add contacts and sites.",
noContacts: "No contacts in this company yet.",
noSites: "No sites in this company yet.",
search: "Find company",
searchPlaceholder: "Company name…",
noResults: "No companies found.",
companySections: "Company information",
open: "Open details",
edit: "Edit",
back: "Back to companies",
close: "Close editor",
cancel: "Cancel",
save: "Save",
saving: "Saving…",
saved: "Changes saved.",
loading: "Loading companies…",
retry: "Try again",
invalidForm: "Review the form fields.",
errors: {
PERSON_NOT_FOUND: "The person does not belong to this company or is unavailable.",
DEPARTMENT_NOT_FOUND: "The department does not belong to this company or is unavailable.",
DEPARTMENT_NAME_EXISTS: "A department with this name already exists in this company.",
ASSIGNMENT_NOT_FOUND: "The assignment does not belong to this person or is unavailable.",
ASSIGNMENT_CONFLICT:
"Dates overlap with another assignment or a primary department already exists. Review current assignments.",
INACTIVE_ASSIGNMENT_TARGET:
"Activate the person and department before creating or reopening an assignment.",
INVALID_REQUEST: "Review the submitted data. Enter a valid phone number.",
COMPANY_NOT_FOUND: "The company is not available.",
CONTACT_NOT_FOUND: "The contact does not belong to this company or is not available.",
SITE_NOT_FOUND: "The site does not belong to this company or is not available.",
UNAUTHENTICATED: "Your session expired. Sign in again.",
FORBIDDEN: "The operation could not be authorized. Reload the page.",
INTERNAL_ERROR: "The operation could not be completed. Try again.",
},
};
export const companiesEnglish = english.companies;
export const companiesSpanish = spanish.companies;

View File

@ -1,88 +0,0 @@
export const interventionTemplatesSpanish = {
title: "Plantillas de intervención",
new: "Nueva plantilla de intervención",
edit: "Editar plantilla de intervención",
description: "Descripción (opcional)",
steps: "Pasos",
addStep: "Añadir paso",
removeStep: "Quitar paso",
stepTitle: "Nombre del paso",
instructions: "Instrucciones (opcional)",
required: "Comprobación obligatoria antes de finalizar",
up: "Subir paso",
down: "Bajar paso",
global: "Global",
company: "De esta compañía",
copy: "Copiar plantilla",
copyOf: "Copia de",
filter: "Buscar plantilla",
showInactive: "Mostrar inactivas",
empty: "No hay plantillas que coincidan.",
scopeGlobal: "Estas plantillas son comunes a todas las compañías.",
scopeCompany:
"Consulta las plantillas globales y crea variantes propias de esta compañía. Las globales se editan desde Catálogos globales.",
help: "¿Para qué sirve una plantilla de intervención?",
helpDescription:
"Guarda un procedimiento repetible como una lista ordenada de pasos. Una intervención sencilla podrá registrarse sin plantilla.",
helpScope:
"La plantilla es un procedimiento: no contiene personas, cuentas ni datos concretos de una compañía.",
examples: "Sustitución de equipo, configuración de correo, baja de persona o revisión periódica.",
guidance:
"Añade instrucciones solo cuando ayuden al técnico. Marca como obligatorias únicamente las comprobaciones esenciales. Desactivar conserva el historial.",
errors: {
INVALID_REQUEST: "Revisa el nombre y los pasos. Debe haber entre 1 y 100 pasos con nombre.",
TEMPLATE_CHANGED:
"Otra persona ha modificado la plantilla. Cierra y vuelve a abrir el formulario para cargar su última versión.",
TEMPLATE_NAME_EXISTS: "Ya existe una plantilla con ese nombre en este ámbito.",
REQUEST_KEY_REUSED:
"La petición ya se utilizó con otros datos. Cierra y vuelve a abrir el formulario.",
TEMPLATE_NOT_FOUND: "La plantilla no está disponible en este ámbito.",
COMPANY_NOT_FOUND: "La compañía no está disponible.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
FORBIDDEN: "Recarga la página para autorizar la operación.",
UNAUTHENTICATED: "La sesión ha caducado. Vuelve a entrar.",
},
};
export const interventionTemplatesEnglish = {
title: "Intervention templates",
new: "New intervention template",
edit: "Edit intervention template",
description: "Description (optional)",
steps: "Steps",
addStep: "Add step",
removeStep: "Remove step",
stepTitle: "Step name",
instructions: "Instructions (optional)",
required: "Required check before completion",
up: "Move step up",
down: "Move step down",
global: "Global",
company: "This company",
copy: "Copy template",
copyOf: "Copy of",
filter: "Find template",
showInactive: "Show inactive",
empty: "No matching templates.",
scopeGlobal: "These templates are shared by all companies.",
scopeCompany:
"Browse global templates and create company variants. Edit global templates in Global catalogs.",
help: "What is an intervention template for?",
helpDescription:
"Save a repeatable procedure as an ordered list of steps. Simple interventions can be recorded without a template.",
helpScope: "A template is a procedure: it contains no people, accounts or specific company data.",
examples: "Device replacement, email setup, employee departure or periodic checks.",
guidance:
"Add instructions when useful. Make only essential checks required. Deactivation retains history.",
errors: {
INVALID_REQUEST: "Review the name and steps. Include between 1 and 100 named steps.",
TEMPLATE_CHANGED:
"Someone else changed this template. Close and reopen the form to load the latest version.",
TEMPLATE_NAME_EXISTS: "A template with this name already exists in this scope.",
REQUEST_KEY_REUSED: "This request was already used with other data. Close and reopen the form.",
TEMPLATE_NOT_FOUND: "This template is unavailable in this scope.",
COMPANY_NOT_FOUND: "The company is unavailable.",
INTERNAL_ERROR: "The operation failed. Try again.",
FORBIDDEN: "Reload the page to authorize the operation.",
UNAUTHENTICATED: "Your session expired. Sign in again.",
},
};

View File

@ -1,171 +0,0 @@
export const interventionsSpanish = {
resetFilters: "Limpiar filtros",
title: "Intervenciones",
global: "Todas las intervenciones",
new: "Nueva intervención",
edit: "Editar intervención",
details: "Ficha de intervención",
subject: "Asunto",
state: "Estado",
ticket: "Ticket (opcional)",
internal: "Intervención interna, sin ticket",
fromTicket: "Crear intervención",
assignee: "Técnico responsable",
unassigned: "Sin asignar",
modality: "Modalidad",
states: {
pending: "Pendiente",
in_progress: "En curso",
finished: "Finalizada",
cancelled: "Cancelada",
},
modalities: { remote: "Remota", onsite: "Presencial", other: "Otra" },
plannedDate: "Fecha prevista (opcional)",
startedAt: "Inicio real (opcional)",
endedAt: "Fin real (opcional)",
minutes: "Tiempo empleado (minutos, opcional)",
unknownTime: "Sin registrar",
timeHelp: "El tiempo empleado es manual y puede diferir del intervalo entre inicio y fin.",
diagnosis: "Diagnóstico (opcional)",
workPerformed: "Trabajo realizado",
resolution: "Resultado / resolución (opcional)",
template: "Plantilla (opcional)",
noTemplate: "Sin plantilla",
apply: "Aplicar plantilla",
newTemplate: "Nueva plantilla",
templateNotice:
"Aplicar sustituye los pasos actuales. La copia se puede adaptar y conserva la versión original de la plantilla.",
source: "Plantilla de origen",
steps: "Pasos",
progress: "{{done}} de {{total}} pasos hechos",
addStep: "Añadir paso",
removeStep: "Quitar paso",
notes: "Observaciones / motivo de no aplicación",
stepStates: {
pending: "Pendiente",
in_progress: "En curso",
done: "Hecho",
not_applicable: "No aplica",
},
search: "Buscar intervención",
allCompanies: "Todas las compañías",
allStates: "Todos los estados",
allAssignees: "Todos los técnicos",
dateFrom: "Prevista desde",
dateTo: "Prevista hasta",
empty: "No hay intervenciones que coincidan.",
loading: "Cargando intervenciones…",
history: "Historial de la intervención",
completed: "Completado por {{actor}} · {{date}}",
help: "¿Para qué sirve una intervención?",
helpDescription:
"Registra el trabajo técnico realizado o previsto para una compañía, con ticket o por iniciativa interna.",
scope:
"Puede ser una actuación sencilla sin pasos. Las plantillas copian un procedimiento; no configuran equipos ni retiran accesos automáticamente.",
examples: "Reconfigurar un correo, resolver un atasco de impresora o sustituir un portátil.",
guidance:
"Para finalizar, indica técnico y trabajo realizado. Los pasos obligatorios deben estar hechos o no aplicar con un motivo. Finalizar no cierra el ticket.",
errors: {
INVALID_REQUEST:
"Revisa los datos. En curso o finalizada requiere técnico. Para finalizar indica el trabajo y completa los pasos obligatorios (o justifica que no aplican). Las fechas deben estar ordenadas y los minutos ser positivos o cero.",
INTERVENTION_NOT_FOUND: "La intervención no está disponible en esta compañía.",
INTERVENTION_CHANGED:
"Otra persona ha modificado la intervención. Cierra y vuelve a abrir el formulario.",
TICKET_NOT_FOUND: "El ticket no pertenece a esta compañía.",
TEMPLATE_UNAVAILABLE: "La plantilla no está activa o no está disponible para esta compañía.",
TEMPLATE_CHANGED: "La plantilla cambió. Recarga sus opciones y vuelve a aplicarla.",
TARGET_UNAVAILABLE: "Algún elemento afectado no pertenece a la compañía o está inactivo.",
ASSIGNEE_UNAVAILABLE: "El técnico no está disponible para una nueva asignación.",
REQUEST_KEY_REUSED:
"Esta petición ya se utilizó con otros datos. Cierra y vuelve a abrir el formulario.",
COMPANY_NOT_FOUND: "La compañía no está disponible.",
UNAUTHENTICATED: "La sesión ha caducado. Vuelve a entrar.",
FORBIDDEN: "Recarga la página para autorizar la operación.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
},
};
export const interventionsEnglish = {
resetFilters: "Clear filters",
title: "Interventions",
global: "All interventions",
new: "New intervention",
edit: "Edit intervention",
details: "Intervention details",
subject: "Subject",
state: "State",
ticket: "Ticket (optional)",
internal: "Internal intervention, no ticket",
fromTicket: "Create intervention",
assignee: "Responsible technician",
unassigned: "Unassigned",
modality: "Modality",
states: {
pending: "Pending",
in_progress: "In progress",
finished: "Finished",
cancelled: "Cancelled",
},
modalities: { remote: "Remote", onsite: "On site", other: "Other" },
plannedDate: "Planned date (optional)",
startedAt: "Actual start (optional)",
endedAt: "Actual end (optional)",
minutes: "Time spent (minutes, optional)",
unknownTime: "Not recorded",
timeHelp: "Time spent is recorded manually and may differ from elapsed time.",
diagnosis: "Diagnosis (optional)",
workPerformed: "Work performed",
resolution: "Result / resolution (optional)",
template: "Template (optional)",
noTemplate: "No template",
apply: "Apply template",
newTemplate: "New template",
templateNotice:
"Applying replaces current steps. The copy can be adapted and retains the original template version.",
source: "Source template",
steps: "Steps",
progress: "{{done}} of {{total}} steps done",
addStep: "Add step",
removeStep: "Remove step",
notes: "Notes / reason for not applying",
stepStates: {
pending: "Pending",
in_progress: "In progress",
done: "Done",
not_applicable: "Not applicable",
},
search: "Find intervention",
allCompanies: "All companies",
allStates: "All states",
allAssignees: "All technicians",
dateFrom: "Planned from",
dateTo: "Planned until",
empty: "No matching interventions.",
loading: "Loading interventions…",
history: "Intervention history",
completed: "Completed by {{actor}} · {{date}}",
help: "What is an intervention for?",
helpDescription:
"Record technical work planned or performed for a company, linked to a ticket or initiated internally.",
scope:
"Simple work can have no steps. Templates copy a procedure; they do not configure devices or remove access automatically.",
examples: "Reconfigure email, clear a printer jam or replace a laptop.",
guidance:
"To finish, enter a technician and work performed. Required steps must be done or marked not applicable with a reason. Finishing does not close the ticket.",
errors: {
INVALID_REQUEST:
"Review the data. In progress or finished requires a technician. To finish, describe the work and complete required checks (or explain why they do not apply). Dates must be ordered and minutes nonnegative integers.",
INTERVENTION_NOT_FOUND: "The intervention is unavailable in this company.",
INTERVENTION_CHANGED: "Someone changed this intervention. Close and reopen the form.",
TICKET_NOT_FOUND: "The ticket does not belong to this company.",
TEMPLATE_UNAVAILABLE: "The template is inactive or unavailable for this company.",
TEMPLATE_CHANGED: "The template changed. Refresh the options and apply it again.",
TARGET_UNAVAILABLE: "An affected item is inactive or does not belong to the company.",
ASSIGNEE_UNAVAILABLE: "The technician is unavailable for a new assignment.",
REQUEST_KEY_REUSED:
"This request was already used with different data. Close and reopen the form.",
COMPANY_NOT_FOUND: "The company is unavailable.",
UNAUTHENTICATED: "Your session expired. Sign in again.",
FORBIDDEN: "Reload the page to authorize the operation.",
INTERNAL_ERROR: "The operation failed. Try again.",
},
};

View File

@ -1,147 +0,0 @@
export const mailSpanish = {
title: "Correos en dispositivos",
new: "Configurar correo",
profile: "Perfil de usuario",
legacy: "Registro anterior pendiente de vincular a buzón y perfil.",
legacyLink:
"Selecciona el perfil de esta persona en este dispositivo y un buzón con la misma dirección. Se conservará el historial anterior:",
edit: "Editar configuración",
empty: "Todavía no hay correos registrados en dispositivos.",
search: "Buscar correo, persona o dispositivo…",
person: "Persona",
account: "Cuenta informática",
asset: "Dispositivo",
choose: "Selecciona un valor",
address: "Dirección de correo",
method: "Método de acceso",
state: "Estado",
notes: "Notas (opcional)",
pop: "POP",
imap: "IMAP",
exchange: "Exchange",
other: "Otro",
pending: "Pendiente de configurar",
configured: "Configurado",
removal_pending: "Pendiente de retirar",
removed: "Retirado",
never_configured: "No llegó a configurarse",
review: "Revisar uso: buzón, perfil, persona, dispositivo o periodo de uso no vigente.",
noAuthorization:
"Autoriza esta cuenta para la persona antes de guardar una configuración activa.",
authorize: "Autorizar cuenta para esta persona",
newAccount: "Nueva cuenta",
newAsset: "Nuevo dispositivo",
newPerson: "Nueva persona",
history: "Historial",
help: "¿Para qué sirve esta configuración?",
description:
"Registra qué correo está configurado en cada dispositivo de una persona y cómo se accede a él.",
examplesLabel: "Ejemplos y recomendaciones",
examples:
"Ana: ana@empresa.es por IMAP en su portátil y por Exchange en su móvil; ventas@empresa.es también en el portátil.",
guidance:
"El uso del buzón por la persona se registra por separado y no exige una cuenta informática. Al retirar un correo, confirma primero que se ha eliminado del dispositivo. No introduzcas contraseñas.",
scope:
"Puedes registrar varios buzones por persona y varios perfiles por buzón. Los cambios conservan el historial.",
fixed:
"Buzón y perfil quedan fijados. Para cambiar alguno, retira esta configuración y crea otra.",
errors: {
MAIL_LEGACY_TARGET_MISMATCH:
"La vinculación debe conservar la persona, dispositivo y dirección del registro anterior.",
MAILBOX_EXISTS: "Ya existe un buzón con esta dirección.",
MAILBOX_SCOPE_MISMATCH: "Todos los elementos deben pertenecer a esta compañía.",
MAILBOX_UNAVAILABLE: "El buzón y la persona deben estar activos.",
MAILBOX_USE_OVERLAP: "Ya existe un periodo de uso que coincide con estas fechas.",
MAILBOX_KIND_IMMUTABLE: "El tipo de buzón queda fijo desde el alta.",
MAILBOX_USE_TARGET_IMMUTABLE: "La persona, buzón y fecha de inicio quedan fijos.",
MAILBOX_USE_CLOSED: "Este periodo ya está cerrado.",
MAILBOX_NOT_FOUND: "El buzón no está disponible.",
MAILBOX_USE_NOT_FOUND: "La vinculación no está disponible.",
INVALID_PERIOD: "Revisa las fechas del periodo.",
INVALID_REQUEST: "Revisa los campos; introduce una dirección de correo válida.",
MAIL_SCOPE_MISMATCH: "El buzón y el perfil deben pertenecer a esta compañía.",
MAIL_NOT_AUTHORIZED:
"El perfil y el buzón deben estar vigentes y la persona debe tener un periodo de uso vigente del buzón.",
MAIL_CONFIGURATION_EXISTS: "Ya hay una configuración abierta para este buzón y perfil.",
MAIL_TARGET_IMMUTABLE: "Para cambiar buzón o perfil, crea otra configuración.",
INVALID_MAIL_TRANSITION:
"El cambio de estado no es válido. Un correo configurado pasa primero a pendiente de retirar.",
MAIL_CONFIGURATION_NOT_FOUND: "La configuración no está disponible en esta compañía.",
COMPANY_NOT_FOUND: "La compañía no está disponible.",
UNAUTHENTICATED: "La sesión ha caducado. Vuelve a entrar.",
FORBIDDEN: "Recarga la página para autorizar la operación.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
},
};
export const mailEnglish = {
title: "Email on devices",
new: "Configure email",
profile: "User profile",
legacy: "Previous record awaiting mailbox and profile linking.",
legacyLink:
"Select this person’s profile on this device and a mailbox with the same address. Previous history will be retained:",
edit: "Edit configuration",
empty: "No email configurations on devices yet.",
search: "Find email, person or device…",
person: "Person",
account: "IT account",
asset: "Device",
choose: "Choose a value",
address: "Email address",
method: "Access method",
state: "Status",
notes: "Notes (optional)",
pop: "POP",
imap: "IMAP",
exchange: "Exchange",
other: "Other",
pending: "Pending configuration",
configured: "Configured",
removal_pending: "Pending removal",
removed: "Removed",
never_configured: "Never configured",
review: "Review usage: mailbox, profile, person, device or usage period is no longer valid.",
noAuthorization: "Authorize this account for the person before saving an active configuration.",
authorize: "Authorize account for this person",
newAccount: "New account",
newAsset: "New device",
newPerson: "New person",
history: "History",
help: "What is this configuration for?",
description: "Record which email is configured on each person's device and how it is accessed.",
examplesLabel: "Examples and guidance",
examples:
"Ana: ana@company.com via IMAP on her laptop and Exchange on her phone; sales@company.com on the laptop too.",
guidance:
"Mailbox usage by the person is tracked separately and does not require an IT account. Confirm removal from the device before marking it removed. Do not enter passwords.",
scope:
"Multiple mailboxes per person and multiple profiles per mailbox are supported. Changes retain history.",
fixed:
"Mailbox and profile are fixed. To change either, remove this configuration and create another.",
errors: {
MAIL_LEGACY_TARGET_MISMATCH: "Linking must preserve the previous person, device and address.",
MAILBOX_EXISTS: "A mailbox with this address already exists.",
MAILBOX_SCOPE_MISMATCH: "All elements must belong to this company.",
MAILBOX_UNAVAILABLE: "Mailbox and person must be active.",
MAILBOX_USE_OVERLAP: "A usage period already overlaps these dates.",
MAILBOX_KIND_IMMUTABLE: "Mailbox type is fixed after creation.",
MAILBOX_USE_TARGET_IMMUTABLE: "Person, mailbox and start date are fixed.",
MAILBOX_USE_CLOSED: "This period is already closed.",
MAILBOX_NOT_FOUND: "The mailbox is unavailable.",
MAILBOX_USE_NOT_FOUND: "The usage link is unavailable.",
INVALID_PERIOD: "Check the period dates.",
INVALID_REQUEST: "Review the fields; enter a valid email address.",
MAIL_SCOPE_MISMATCH: "Mailbox and profile must belong to this company.",
MAIL_NOT_AUTHORIZED:
"The profile and mailbox must be current, with an active mailbox usage period for the person.",
MAIL_CONFIGURATION_EXISTS: "An open configuration already exists for this mailbox and profile.",
MAIL_TARGET_IMMUTABLE: "Create another configuration to change mailbox or profile.",
INVALID_MAIL_TRANSITION:
"Invalid status change. Configured email must first be marked for removal.",
MAIL_CONFIGURATION_NOT_FOUND: "The configuration is unavailable in this company.",
COMPANY_NOT_FOUND: "The company is unavailable.",
UNAUTHENTICATED: "Your session expired. Sign in again.",
FORBIDDEN: "Reload the page to authorize the operation.",
INTERNAL_ERROR: "Unable to complete the operation. Try again.",
},
};

View File

@ -1,74 +0,0 @@
export const mailboxSpanish = {
title: "Buzones de correo",
titleSingle: "Buzón",
new: "Nuevo buzón",
edit: "Editar buzón",
empty: "Todavía no hay buzones registrados.",
search: "Buscar dirección o contexto",
kind: "Tipo de buzón",
corporate: "Corporativo",
external: "Externo",
context: "Contexto (opcional)",
account: "Cuenta informática relacionada (opcional)",
noAccount: "Sin cuenta relacionada",
active: "Buzón activo",
linkPerson: "Vincular persona",
authorize: "Registrar autorización de uso",
recordUse: "Registrar uso de correo externo",
useMissing: "Registra primero el periodo de uso de este buzón por la persona del perfil.",
start: "Desde",
end: "Hasta (opcional; día de fin excluido)",
openPeriod: "Sin fecha de fin",
closeUse: "Finalizar uso",
help: "¿Qué es un buzón de correo?",
description:
"Un buzón identifica una dirección de correo. Puede utilizarlo más de una persona y configurarse en varios perfiles.",
guidance:
"El dominio no determina a qué compañía pertenece el buzón. El correo externo puede ser personal, de un cliente, proveedor o colaboración. Relacionar una cuenta informática es opcional y no concede acceso al buzón. El tipo queda fijo desde el alta.",
examples:
"ana@empresa.es; administracion@otraempresa.es; correo personal utilizado en el portátil. No introduzcas contraseñas.",
useHelp: "¿Para qué sirve esta vinculación?",
useDescription:
"Para buzones corporativos registra la autorización documentada. Para buzones externos registra su uso por la persona, sin afirmar que la compañía es su propietaria.",
useGuidance:
"El inicio está incluido y el día de fin está excluido. Finalizar conserva el historial y señala para revisión los correos que aún estén configurados. No retira el correo de los equipos automáticamente.",
useExamples:
"Ana utiliza administracion@empresa.es desde su incorporación; Ana deja de utilizarlo al cambiar de departamento.",
};
export const mailboxEnglish = {
title: "Mailboxes",
titleSingle: "Mailbox",
new: "New mailbox",
edit: "Edit mailbox",
empty: "No mailboxes recorded yet.",
search: "Search address or context",
kind: "Mailbox type",
corporate: "Corporate",
external: "External",
context: "Context (optional)",
account: "Related IT account (optional)",
noAccount: "No related account",
active: "Active mailbox",
linkPerson: "Link person",
authorize: "Record usage authorization",
recordUse: "Record external email usage",
useMissing: "First record this mailbox's usage period for the profile's person.",
start: "From",
end: "Until (optional; end day excluded)",
openPeriod: "No end date",
closeUse: "End usage",
help: "What is a mailbox?",
description:
"A mailbox identifies an email address. Several people can use it and it can be configured in several profiles.",
guidance:
"The domain does not determine company ownership. External email may be personal or belong to a client, supplier or collaboration. Linking an IT account is optional and does not grant mailbox access. Type is fixed after creation.",
examples:
"ana@company.com; administration@anothercompany.com; personal email used on a laptop. Do not enter passwords.",
useHelp: "What is this link for?",
useDescription:
"For corporate mailboxes, record documented authorization. For external mailboxes, record personal usage without asserting company ownership.",
useGuidance:
"The start day is included and the end day excluded. Ending usage preserves history and flags outstanding configurations for review. It does not remove email from devices automatically.",
useExamples:
"Ana uses administration@company.com from her start date; she stops using it when changing departments.",
};

View File

@ -1,86 +0,0 @@
export const printersSpanish = {
title: "Impresoras del perfil",
"new": "Configurar impresora",
edit: "Editar configuración de impresora",
printer: "Impresora del inventario",
newAsset: "Nueva impresora en inventario",
connection: "Conexión",
usb: "USB",
network: "Red",
default: "Impresora predeterminada",
notDefault: "No predeterminada",
search: "Buscar impresora, perfil, persona o equipo…",
empty: "Todavía no hay impresoras configuradas en estos perfiles.",
review:
"Revisar esta configuración: hay una retirada pendiente o el perfil, persona o equipo ya no está disponible.",
fixed:
"El perfil y la impresora quedan fijos. Para otro destino, retira esta configuración y crea una nueva.",
help: "¿Para qué sirve esta configuración?",
description:
"Documenta qué impresoras necesita una persona en el perfil de su equipo y cómo se conectan.",
scope:
"La impresora es un activo de la compañía. Configurarla en un perfil no cambia su asignación ni crea una conexión física.",
examples:
"Ejemplo: impresora de administración por red y Zebra de etiquetas por USB en el portátil de Ana.",
guidance:
"Selecciona el activo que represente la impresora, cualquiera que sea su plantilla. Puede haber varias impresoras y una sola predeterminada por perfil. Las altas rápidas se conservan aunque canceles este formulario.",
errors: {
INVALID_REQUEST: "Revisa los datos del formulario.",
PRINTER_REFERENCE_NOT_FOUND:
"Selecciona un perfil y una impresora de esta compañía; la impresora debe ser distinta del equipo del perfil.",
PRINTER_REFERENCE_UNAVAILABLE:
"El perfil, la persona, el equipo o la impresora no están disponibles para configurar.",
PRINTER_TARGET_IMMUTABLE: "El perfil y la impresora no se pueden cambiar.",
PRINTER_ALREADY_OPEN: "Esta impresora ya tiene una configuración abierta en el perfil.",
DEFAULT_PRINTER_EXISTS:
"Ya hay otra impresora predeterminada en este perfil. Desmarca primero la anterior.",
INVALID_PRINTER_TRANSITION: "El cambio de estado no está permitido.",
PRINTER_CONFIGURATION_NOT_FOUND: "No se ha encontrado la configuración de impresora.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const printersEnglish = {
title: "Profile printers",
"new": "Configure printer",
edit: "Edit printer configuration",
printer: "Inventory printer",
newAsset: "New inventory printer",
connection: "Connection",
usb: "USB",
network: "Network",
default: "Default printer",
notDefault: "Not default",
search: "Search printer, profile, person or device…",
empty: "No printers have been configured for these profiles yet.",
review:
"Review this configuration: removal is pending or the profile, person or device is unavailable.",
fixed:
"The profile and printer are fixed. For another target, retire this configuration and create a new one.",
help: "What is this configuration for?",
description: "Document the printers a person needs in their device profile and how they connect.",
scope:
"The printer is a company asset. Configuring it in a profile does not change its assignment or create a physical connection.",
examples: "Example: network administration printer and USB Zebra label printer on Ana's laptop.",
guidance:
"Select the asset representing the printer, regardless of its template. A profile may have several printers and one default. Quick additions remain saved if you cancel this form.",
errors: {
INVALID_REQUEST: "Check the form data.",
PRINTER_REFERENCE_NOT_FOUND:
"Select a profile and printer from this company; the printer must differ from the profile device.",
PRINTER_REFERENCE_UNAVAILABLE:
"The profile, person, device or printer is unavailable for configuration.",
PRINTER_TARGET_IMMUTABLE: "The profile and printer cannot change.",
PRINTER_ALREADY_OPEN: "This printer already has an open configuration in the profile.",
DEFAULT_PRINTER_EXISTS:
"This profile already has another default printer. Uncheck the previous one first.",
INVALID_PRINTER_TRANSITION: "This state change is not allowed.",
PRINTER_CONFIGURATION_NOT_FOUND: "Printer configuration not found.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed. Try again.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,193 +0,0 @@
export const processesSpanish = {
title: "Procesos de personas",
templates: "Plantillas de procesos",
"new": "Nuevo proceso",
template: "Plantilla de proceso",
newTemplate: "Nueva plantilla de proceso",
editTemplate: "Editar plantilla de proceso",
globalTemplates:
"Estas plantillas son comunes a todas las compañías. Los cambios solo afectan a los procesos que se inicien después.",
kind: "Tipo de proceso",
onboarding: "Alta",
change: "Cambio",
offboarding: "Salida",
instructions: "Tareas de la plantilla",
instructionsHelp:
"Una tarea por línea, hasta 100 tareas de 160 caracteres. No repitas una tarea.",
copy: "Crear copia",
copyOf: "Copia de",
history: "Historial",
back: "Volver a procesos",
search: "Buscar proceso, persona o tipo…",
showClosed: "Mostrar también los completados y cancelados",
empty: "Todavía no hay procesos registrados.",
openDetail: "Abrir proceso",
update: "Actualizar seguimiento",
refresh: "Añadir nuevas revisiones",
progress: "{{resolved}} de {{total}} tareas resueltas",
reviewCount: "Se añadirán {{count}} revisiones de los registros actuales de esta persona.",
open: "En curso",
completed: "Completado",
cancelled: "Cancelado",
pending: "Pendiente",
done: "Revisada",
not_applicable: "No procede",
taskNotes: "Resultado o notas de la tarea",
resolutionHelp:
"Revisada significa que has comprobado la tarea. Documenta si se retira, conserva o reasigna el acceso. No cambia cuentas, permisos, configuraciones ni activos.",
stillRecorded: "El elemento sigue en la lista de revisión actual de la persona.",
notInReview: "El elemento ya no aparece en la revisión actual; verifica el resultado real.",
currentReview: "Registros actuales para revisar ({{count}})",
currentReviewHelp:
"Esta lista se consulta de nuevo en el servidor. Puede contener tareas ya revisadas. Las revisiones nuevas se añaden al proceso con el botón de la cabecera; las anteriores conservan su resultado.",
reference: {
profile: "Perfil de usuario",
asset: "Activo asignado",
account: "Relación con cuenta",
membership: "Pertenencia a grupo",
need: "Necesidad de recurso",
mailbox_use: "Uso de correo",
mail: "Correo configurado",
folder: "Carpeta configurada",
printer: "Impresora configurada",
application: "Aplicación configurada",
certificate: "Certificado configurado",
telework: "Autorización de teletrabajo",
remote: "Acceso remoto",
},
help: {
title: "¿Para qué sirve este proceso?",
description: "Organiza las comprobaciones del alta, cambio o salida de una persona.",
scope:
"El proceso pertenece a esta compañía. Copia su plantilla; los cambios posteriores de la plantilla no lo modifican. Para cambio y salida se añaden revisiones de los registros documentados de la persona.",
examples:
"Alta: preparar cuentas y perfil. Cambio: revisar y trasladar configuraciones. Salida: recuperar activos y revisar correo, permisos y acceso desde equipos personales.",
guidance:
"Todas las tareas deben estar revisadas o marcadas No procede para completar el proceso. Los cerrados conservan su historial. Esto documenta el seguimiento; las operaciones sobre cuentas y equipos se realizan en su gestión correspondiente. Las altas rápidas guardadas permanecen si cancelas.",
},
templateHelp: {
title: "¿Para qué sirve esta plantilla?",
description: "Define una lista reutilizable de tareas para altas, cambios o salidas.",
scope:
"Es global y no contiene datos de personas, compañías, cuentas ni equipos concretos. Se copia al iniciar cada proceso.",
examples:
"Comprobar cuentas y permisos. Preparar el perfil. Recuperar el equipo. Revisar acceso desde móviles personales.",
guidance:
"Puedes editar o desactivar plantillas. Desactivar impide iniciar nuevos procesos; los existentes se conservan. Crea una copia si necesitas una variante del procedimiento.",
},
errors: {
INVALID_REQUEST: "Revisa los campos y las tareas.",
PROCESS_TEMPLATE_NOT_FOUND: "No se ha encontrado la plantilla.",
PROCESS_TEMPLATE_INACTIVE: "La plantilla está desactivada.",
PROCESS_TEMPLATE_DUPLICATE: "Ya existe una plantilla con ese nombre.",
PROCESS_PERSON_NOT_FOUND: "Selecciona una persona de esta compañía.",
PROCESS_ALREADY_OPEN: "Esta persona ya tiene un proceso de este tipo en curso.",
PROCESS_NOT_FOUND: "No se ha encontrado el proceso.",
PROCESS_CLOSED: "El proceso cerrado no se puede modificar.",
PROCESS_PENDING_TASKS: "Resuelve las tareas pendientes antes de completar el proceso.",
PROCESS_TASKS_IMMUTABLE: "Las tareas y sus referencias no se pueden sustituir.",
PROCESS_SUBJECT_IMMUTABLE: "La persona y la plantilla inicial no se pueden cambiar.",
PROCESS_CONFLICT:
"Otro usuario ha actualizado el proceso. Cancela el formulario, recarga la ficha y vuelve a editar.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
FORBIDDEN: "Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const processesEnglish = {
title: "People processes",
templates: "Process templates",
"new": "New process",
template: "Process template",
newTemplate: "New process template",
editTemplate: "Edit process template",
globalTemplates:
"These templates are shared across companies. Changes only affect processes started afterwards.",
kind: "Process type",
onboarding: "Onboarding",
change: "Change",
offboarding: "Offboarding",
instructions: "Template tasks",
instructionsHelp: "One task per line, up to 100 tasks of 160 characters. Do not repeat a task.",
copy: "Create copy",
copyOf: "Copy of",
history: "History",
back: "Back to processes",
search: "Search process, person or type…",
showClosed: "Also show completed and cancelled processes",
empty: "No processes recorded yet.",
openDetail: "Open process",
update: "Update progress",
refresh: "Add new reviews",
progress: "{{resolved}} of {{total}} tasks resolved",
reviewCount: "{{count}} reviews of this person's current records will be added.",
open: "In progress",
completed: "Completed",
cancelled: "Cancelled",
pending: "Pending",
done: "Reviewed",
not_applicable: "Not applicable",
taskNotes: "Task outcome or notes",
resolutionHelp:
"Reviewed means you have checked the task. Document whether access is withdrawn, retained or reassigned. It does not change accounts, permissions, configurations or assets.",
stillRecorded: "This item is still in the person's current review list.",
notInReview: "This item no longer appears in the current review; verify the actual outcome.",
currentReview: "Current records to review ({{count}})",
currentReviewHelp:
"This list is read again on the server and may include tasks already reviewed. Use the header button to add new reviews; existing task outcomes are preserved.",
reference: {
profile: "User profile",
asset: "Assigned asset",
account: "Account relationship",
membership: "Group membership",
need: "Resource need",
mailbox_use: "Mail use",
mail: "Mail configuration",
folder: "Folder configuration",
printer: "Printer configuration",
application: "Application configuration",
certificate: "Certificate configuration",
telework: "Telework authorization",
remote: "Remote access",
},
help: {
title: "What is this process for?",
description: "Organize checks when a person joins, changes or leaves.",
scope:
"This process belongs to this company and copies its template. Later template changes do not alter it. Change and offboarding add reviews of the person's documented records.",
examples:
"Onboarding: prepare accounts and profile. Change: review and transfer configurations. Offboarding: recover assets and review mail, permissions and access from personal devices.",
guidance:
"Every task must be reviewed or marked Not applicable before completing the process. Closed processes retain history. This documents progress; account and device actions are performed in their respective sections. Saved quick additions remain if you cancel.",
},
templateHelp: {
title: "What is this template for?",
description: "Define a reusable checklist for onboarding, changes or offboarding.",
scope:
"It is global and contains no specific people, companies, accounts or device data. It is copied when each process starts.",
examples:
"Check accounts and permissions. Prepare the profile. Recover the device. Review access from personal mobile devices.",
guidance:
"Templates can be edited or disabled. Disabling prevents new processes and preserves existing ones. Create a copy for a different procedure.",
},
errors: {
INVALID_REQUEST: "Check the fields and tasks.",
PROCESS_TEMPLATE_NOT_FOUND: "Template not found.",
PROCESS_TEMPLATE_INACTIVE: "The template is disabled.",
PROCESS_TEMPLATE_DUPLICATE: "A template with this name already exists.",
PROCESS_PERSON_NOT_FOUND: "Select a person from this company.",
PROCESS_ALREADY_OPEN: "This person already has a process of this type in progress.",
PROCESS_NOT_FOUND: "Process not found.",
PROCESS_CLOSED: "A closed process cannot change.",
PROCESS_PENDING_TASKS: "Resolve pending tasks before completing the process.",
PROCESS_TASKS_IMMUTABLE: "Tasks and their references cannot be replaced.",
PROCESS_SUBJECT_IMMUTABLE: "The person and original template cannot change.",
PROCESS_CONFLICT:
"Another user updated the process. Cancel this form, reload the record and edit again.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed. Try again.",
FORBIDDEN: "Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,86 +0,0 @@
export const environmentSpanish = {
title: "Entorno actual",
description: "Lo que esta persona tiene y necesita en este equipo.",
back: "Volver al entorno",
manage: "Gestionar",
manageSection: "Gestionar {{section}}",
equipment: "Equipo y puesto de trabajo",
configurations: "Configuraciones del perfil",
archived:
"Perfil archivado: estas configuraciones conservan su último estado registrado y deben revisarse. No representan un entorno vigente.",
personInactive: "La persona está inactiva. Revisa sus configuraciones y accesos.",
reviewProfile:
"Este perfil necesita revisión. Comprueba persona, equipo y cuenta de inicio de sesión.",
device: "Equipo",
workstation: "Puesto de trabajo",
position: "Puesto de la persona",
noStation: "El equipo no pertenece a ningún puesto vigente.",
samePerson: "Otros perfiles de esta persona",
empty: "No hay configuraciones abiertas en este bloque.",
noAttention: "No hay configuraciones pendientes o por revisar en este bloque.",
pending: "Por configurar",
configured: "Configuradas",
removal: "Por retirar",
review: "Por revisar",
onlyAttention: "Mostrar solo pendientes o por revisar",
needsReview: "Requiere revisión",
path: "Ruta",
drive: "Unidad",
default: "Predeterminada",
authorization: "Autorización",
expiry: "Caducidad",
validity: "Vigencia",
shared: "Compartido en el puesto",
exclusive: "Uso exclusivo en el puesto",
retiredAsset: "Activo retirado",
profileNotFound: "El perfil no está disponible en esta compañía.",
loadError:
"No se ha podido cargar el entorno completo. Reintenta para consultar todas sus configuraciones.",
loading: "Cargando el entorno…",
unknown:
"La ausencia de registros no confirma que el equipo esté preparado. Esta ficha muestra información documentada, sin comprobar la configuración real del dispositivo.",
unlinkedMail:
"Hay {{count}} configuraciones antiguas de correo de esta persona y equipo sin vincular al perfil. Revísalas en Correos en dispositivos de la compañía.",
};
export const environmentEnglish = {
title: "Current environment",
description: "What this person has and needs on this device.",
back: "Back to environment",
manage: "Manage",
manageSection: "Manage {{section}}",
equipment: "Device and workstation",
configurations: "Profile configurations",
archived:
"Archived profile: configurations retain their last recorded state and need review. This is not a current environment.",
personInactive: "This person is inactive. Review their configurations and access.",
reviewProfile: "This profile needs review. Check the person, device and sign-in account.",
device: "Device",
workstation: "Workstation",
position: "Person's job title",
noStation: "This device does not belong to an active workstation.",
samePerson: "Other profiles for this person",
empty: "No open configurations in this section.",
noAttention: "No pending configurations or reviews in this section.",
pending: "To configure",
configured: "Configured",
removal: "To remove",
review: "To review",
onlyAttention: "Show only pending or needing review",
needsReview: "Needs review",
path: "Path",
drive: "Drive",
default: "Default",
authorization: "Authorization",
expiry: "Expiry",
validity: "Validity",
shared: "Shared within workstation",
exclusive: "Exclusive within workstation",
retiredAsset: "Retired asset",
profileNotFound: "This profile is unavailable in this company.",
loadError: "Could not load the complete environment. Retry to view all configurations.",
loading: "Loading environment…",
unknown:
"No records does not mean the device is ready. This view shows documented information without inspecting the actual device configuration.",
unlinkedMail:
"{{count}} legacy mail configurations for this person and device are not linked to the profile. Review them in the company Mail on devices section.",
};

View File

@ -1,96 +0,0 @@
export const profilesSpanish = {
title: "Perfiles de usuario",
new: "Nuevo perfil",
edit: "Editar perfil",
name: "Nombre del perfil",
person: "Persona",
asset: "Dispositivo",
account: "Cuenta de inicio de sesión (opcional)",
noAccount: "Sin cuenta registrada",
active: "Perfil vigente",
inactive: "Perfil archivado",
choose: "Selecciona un elemento",
notes: "Notas (opcional)",
search: "Buscar por perfil, persona o dispositivo",
empty: "Esta compañía todavía no tiene perfiles de usuario.",
history: "Ficha e historial",
showArchived: "Mostrar también los perfiles archivados",
review:
"Revisar: la persona, el dispositivo o la cuenta ya no están disponibles para este perfil.",
fixed:
"La persona y el dispositivo identifican este perfil. Para otro dispositivo o persona, crea un perfil nuevo.",
help: "¿Para qué sirve un perfil de usuario?",
description:
"Identifica el entorno de una persona en un dispositivo concreto. Una persona puede tener varios perfiles y un equipo puede tener perfiles de varias personas.",
guidance:
"Elige un ordenador u otro dispositivo que aloje la configuración de la persona. La cuenta de inicio de sesión es opcional y, si se indica, debe estar autorizada para ella. Un perfil vigente no significa que todas sus configuraciones estén listas.",
examplesLabel: "Ejemplos y recomendaciones",
examples:
"Ana en PORT-ANA-01; Ana en su segundo ordenador; Luis en un ordenador compartido. Cada perfil puede tener varios buzones de correo. Programas y recursos se incorporarán después.",
scope:
"Todos los elementos pertenecen a esta compañía. Archivar conserva los datos y el historial; no retira accesos ni borra configuraciones en el equipo.",
unknownConfiguration: "Programas, carpetas, impresoras y certificados: pendientes de incorporar.",
stations: "Puestos que utilizan este dispositivo",
noStation: "Este dispositivo no está incluido en un puesto vigente.",
accountHint:
"Seleccionar la cuenta no la configura en el dispositivo. Su autorización y su configuración se gestionan por separado.",
errors: {
INVALID_REQUEST: "Revisa los campos del formulario.",
PROFILE_REFERENCE_NOT_FOUND:
"La persona, el dispositivo o la cuenta no pertenecen a esta compañía.",
PROFILE_REFERENCE_UNAVAILABLE:
"La persona o el dispositivo no están disponibles, o la cuenta no tiene una autorización vigente para esta persona.",
PROFILE_ALREADY_ACTIVE: "Ya existe un perfil vigente de esta persona en este dispositivo.",
PROFILE_TARGET_IMMUTABLE: "La persona y el dispositivo de un perfil no pueden cambiar.",
PROFILE_NOT_FOUND: "El perfil ya no está disponible.",
COMPANY_NOT_FOUND: "La compañía ya no está disponible.",
INTERNAL_ERROR: "No se pudo completar la operación. Inténtalo de nuevo.",
},
};
export const profilesEnglish = {
title: "User profiles",
new: "New profile",
edit: "Edit profile",
name: "Profile name",
person: "Person",
asset: "Device",
account: "Sign-in account (optional)",
noAccount: "No recorded account",
active: "Current profile",
inactive: "Archived profile",
choose: "Choose an item",
notes: "Notes (optional)",
search: "Search by profile, person or device",
empty: "This company has no user profiles yet.",
history: "Details and history",
showArchived: "Include archived profiles",
review: "Review: the person, device or account is no longer available for this profile.",
fixed:
"Person and device identify this profile. Create another profile for a different person or device.",
help: "What is a user profile for?",
description:
"Identifies a person's environment on a specific device. A person can have several profiles and a device can host profiles for several people.",
guidance:
"Choose a computer or another device hosting the person's configuration. The sign-in account is optional and must be authorized for this person when supplied. A current profile does not mean all its configurations are ready.",
examplesLabel: "Examples and guidance",
examples:
"Ana on PORT-ANA-01; Ana on a second computer; Luis on a shared computer. Each profile can have several mailboxes. Applications and resources will be added later.",
scope:
"All elements belong to this company. Archiving preserves data and history; it does not revoke access or remove device configurations.",
unknownConfiguration: "Applications, folders, printers and certificates: pending implementation.",
stations: "Workstations using this device",
noStation: "This device is not included in a current workstation.",
accountHint:
"Selecting an account does not configure it on the device. Authorization and configuration are tracked separately.",
errors: {
INVALID_REQUEST: "Check the form fields.",
PROFILE_REFERENCE_NOT_FOUND: "The person, device or account does not belong to this company.",
PROFILE_REFERENCE_UNAVAILABLE:
"The person or device is unavailable, or the account has no current authorization for this person.",
PROFILE_ALREADY_ACTIVE: "A current profile already exists for this person on this device.",
PROFILE_TARGET_IMMUTABLE: "A profile's person and device cannot change.",
PROFILE_NOT_FOUND: "This profile is unavailable.",
COMPANY_NOT_FOUND: "This company is unavailable.",
INTERNAL_ERROR: "The operation could not be completed. Please try again.",
},
};

View File

@ -1,116 +0,0 @@
export const remoteSpanish = {
title: "Accesos remotos",
new: "Nuevo acceso remoto",
edit: "Editar acceso remoto",
person: "Persona",
sourceKind: "Equipo de origen",
company_asset: "Activo de la compañía",
personal_computer: "Ordenador personal",
personal_mobile: "Móvil personal",
sourceAsset: "Activo de origen",
targetKind: "Tipo de destino",
network: "Red de compañía / sede",
asset: "Equipo concreto",
resource: "Recurso concreto",
target: "Destino",
site: "Sede (opcional)",
allCompany: "Red de la compañía",
method: "Método o herramienta",
methodExample: "VPN, RDP, AnyDesk…",
requiresVpn: "Necesita una VPN previa",
vpnRequired: "Requiere VPN previa",
vpnNote: "La conexión VPN necesaria debe comprobarse por separado.",
authorizationMissing:
"La persona no tiene autorización de teletrabajo vigente. Revisar el acceso existente.",
review:
"Revisar: retirada pendiente, autorización no vigente o persona, origen o destino no disponibles.",
search: "Buscar persona, origen, destino o herramienta…",
empty: "Todavía no hay accesos remotos registrados.",
showClosed: "Mostrar también los retirados y nunca configurados",
fixed:
"La persona, el origen y el destino quedan fijos. Para otro recorrido, cierra este acceso y crea uno nuevo.",
history: "Historial del acceso",
manage: "Gestionar accesos remotos",
newAsset: "Nuevo activo",
newResource: "Nuevo recurso",
help: "¿Para qué sirve este acceso?",
description:
"Documenta cómo una persona accede desde fuera a la red, a un equipo o a un recurso de la compañía.",
scope:
"Es un acceso de la persona para trabajar. La asistencia del técnico es independiente. Un equipo personal solo se identifica por su tipo; no se inventaría como activo de la compañía.",
examples:
"Portátil corporativo → VPN → red de la sede. Ordenador personal → RDP → PC de oficina, con VPN previa. Móvil personal → acceso a un recurso concreto.",
guidance:
"La herramienta admite nombres propios. Configurado describe el acceso documentado, no concede permiso. Puedes registrar un acceso que siga funcionando aunque la persona ya no esté autorizada; aparecerá para revisión. No se duplican los permisos de carpetas o aplicaciones, ni se establecen o cortan conexiones reales. No guardes contraseñas. Las altas rápidas guardadas permanecen aunque canceles este formulario.",
errors: {
INVALID_REQUEST: "Revisa la persona, el origen, el destino y la herramienta.",
REMOTE_TARGET_IMMUTABLE: "La persona, el origen y el destino no se pueden cambiar.",
REMOTE_REFERENCE_NOT_FOUND: "Selecciona referencias de esta compañía.",
REMOTE_ALREADY_OPEN:
"Ya existe un acceso abierto con este origen, destino y herramienta para la persona.",
INVALID_REMOTE_TRANSITION: "El cambio de estado no está permitido.",
REMOTE_CONNECTION_NOT_FOUND: "No se ha encontrado el acceso.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const remoteEnglish = {
title: "Remote access",
new: "New remote access",
edit: "Edit remote access",
person: "Person",
sourceKind: "Source device",
company_asset: "Company asset",
personal_computer: "Personal computer",
personal_mobile: "Personal mobile",
sourceAsset: "Source asset",
targetKind: "Target type",
network: "Company / site network",
asset: "Specific device",
resource: "Specific resource",
target: "Target",
site: "Site (optional)",
allCompany: "Company network",
method: "Method or tool",
methodExample: "VPN, RDP, AnyDesk…",
requiresVpn: "Requires a prior VPN connection",
vpnRequired: "Prior VPN required",
vpnNote: "The required VPN connection must be checked separately.",
authorizationMissing:
"The person has no current telework authorization. Review the existing access.",
review:
"Review: removal is pending, authorization is not current, or the person, source or target is unavailable.",
search: "Search person, source, target or tool…",
empty: "No remote access has been recorded yet.",
showClosed: "Also show removed and never configured access",
fixed:
"The person, source and target are fixed. For a different route, close this access and create a new one.",
history: "Access history",
manage: "Manage remote access",
newAsset: "New asset",
newResource: "New resource",
help: "What is this access for?",
description:
"Document how a person connects from outside to a company network, device or resource.",
scope:
"This is the person's access for work. Technician assistance is separate. Personal devices are identified only by type and are not company inventory assets.",
examples:
"Company laptop → VPN → site network. Personal computer → RDP → office PC, with prior VPN. Personal mobile → a specific resource.",
guidance:
"Custom tool names are allowed. Configured describes documented access and does not grant permission. You can record access that still works after authorization ends; it will be flagged for review. Folder and application permissions are not duplicated, and real connections are not established or disconnected. Do not store passwords. Saved quick additions remain if this form is cancelled.",
errors: {
INVALID_REQUEST: "Check the person, source, target and tool.",
REMOTE_TARGET_IMMUTABLE: "The person, source and target cannot change.",
REMOTE_REFERENCE_NOT_FOUND: "Select references from this company.",
REMOTE_ALREADY_OPEN:
"An open access with this source, target and tool already exists for the person.",
INVALID_REMOTE_TRANSITION: "This state change is not allowed.",
REMOTE_CONNECTION_NOT_FOUND: "Access not found.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed. Try again.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,97 +0,0 @@
export const teleworkSpanish = {
title: "Teletrabajo",
authorizations: "Autorizaciones de teletrabajo",
new: "Nueva autorización",
edit: "Editar autorización de teletrabajo",
person: "Persona",
active: "Autorización activa",
startsOn: "Válida desde (opcional)",
expiresOn: "Válida hasta (opcional)",
notes: "Notas (opcional)",
authorized: "Autorizada",
scheduled: "Programada",
expired: "Caducada",
revoked: "Revocada",
empty: "Todavía no hay autorizaciones de teletrabajo.",
notRecorded: "No hay autorización de teletrabajo registrada para esta persona.",
search: "Buscar persona…",
review: "Revisar: la persona está inactiva. Esta autorización no es efectiva.",
ineffective:
"La autorización no está vigente. Revisa los accesos y configuraciones remotos existentes.",
effective:
"Autorización vigente para la persona; no demuestra que la conexión remota esté preparada.",
noDate: "Sin límite de fecha",
fixed: "La persona no puede cambiar. Edita esta autorización para renovarla o revocarla.",
history: "Historial de autorización",
manage: "Gestionar teletrabajo",
help: "¿Para qué sirve esta autorización?",
description: "Documenta si una persona puede trabajar fuera de la sede y durante qué periodo.",
scope:
"La autorización pertenece a la persona de esta compañía y es común a sus perfiles. No concede acceso a Metis Desk ni nuevos permisos sobre los recursos.",
examples:
"Ana está autorizada sin fecha de fin; Rubén tiene permiso entre el 1 y el 31 de diciembre; al salir de la compañía se revoca conservando el historial.",
guidance:
"Ambas fechas son opcionales y el último día está incluido. Desactiva la autorización para revocarla. Este registro no configura VPN, no corta conexiones ni retira correos. Una persona nueva guardada permanece aunque canceles este formulario.",
errors: {
INVALID_REQUEST: "Revisa los datos y el orden de las fechas.",
TELEWORK_PERSON_IMMUTABLE: "La persona de la autorización no se puede cambiar.",
TELEWORK_PERSON_NOT_FOUND: "Selecciona una persona de esta compañía.",
TELEWORK_PERSON_INACTIVE:
"No puedes autorizar teletrabajo a una persona inactiva. Puedes revocar su autorización.",
TELEWORK_ALREADY_EXISTS:
"Esta persona ya tiene una autorización. Edítala para renovarla o revocarla.",
TELEWORK_AUTHORIZATION_NOT_FOUND: "No se ha encontrado la autorización.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const teleworkEnglish = {
title: "Telework",
authorizations: "Remote work authorizations",
new: "New authorization",
edit: "Edit telework authorization",
person: "Person",
active: "Active authorization",
startsOn: "Valid from (optional)",
expiresOn: "Valid through (optional)",
notes: "Notes (optional)",
authorized: "Authorized",
scheduled: "Scheduled",
expired: "Expired",
revoked: "Revoked",
empty: "No telework authorizations have been recorded yet.",
notRecorded: "No telework authorization is recorded for this person.",
search: "Search person…",
review: "Review: the person is inactive. This authorization is not effective.",
ineffective: "Authorization is not current. Review existing remote access and configurations.",
effective:
"Current authorization for the person; this does not prove remote connectivity is ready.",
noDate: "No date limit",
fixed: "The person cannot change. Edit this authorization to renew or revoke it.",
history: "Authorization history",
manage: "Manage telework",
help: "What is this authorization for?",
description: "Document whether a person may work away from the site and for which period.",
scope:
"Authorization belongs to the company person and applies across their profiles. It does not grant Metis Desk access or new resource permissions.",
examples:
"Ana is authorized without an end date; Ruben is authorized from December 1 through December 31; on departure, revoke authorization while retaining history.",
guidance:
"Both dates are optional and the final day is included. Deactivate to revoke. This record does not configure VPN, disconnect sessions or remove mail. A saved new person remains if this form is cancelled.",
errors: {
INVALID_REQUEST: "Check the data and date order.",
TELEWORK_PERSON_IMMUTABLE: "The authorization person cannot change.",
TELEWORK_PERSON_NOT_FOUND: "Select a person from this company.",
TELEWORK_PERSON_INACTIVE:
"An inactive person cannot be authorized. You can revoke their authorization.",
TELEWORK_ALREADY_EXISTS:
"This person already has an authorization. Edit it to renew or revoke.",
TELEWORK_AUTHORIZATION_NOT_FOUND: "Authorization not found.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed. Try again.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};

View File

@ -1,148 +0,0 @@
export const ticketsSpanish = {
title: "Tickets",
global: "Todos los tickets",
new: "Nuevo ticket",
edit: "Editar ticket",
details: "Ficha e historial",
company: "Compañía",
subject: "Asunto",
description: "Descripción de la petición (opcional)",
requester: "Solicitante (opcional)",
assignee: "Responsable (opcional)",
channel: "Canal",
state: "Estado",
priority: "Prioridad",
affected: "Elementos afectados",
kind: "Tipo de elemento",
addTarget: "Añadir elemento",
person: "Persona",
asset: "Activo",
resource: "Recurso",
workstation: "Puesto de trabajo",
newTarget: "Crear elemento",
remove: "Quitar elemento",
unassigned: "Sin asignar",
choose: "Selecciona un elemento",
all: "Todos",
allCompanies: "Todas las compañías",
allStates: "Todos los estados",
allPriorities: "Todas las prioridades",
allAssignees: "Todos los responsables",
search: "Buscar por referencia, asunto, compañía o solicitante",
empty: "No hay tickets que coincidan con los filtros.",
loading: "Cargando tickets…",
reference: "Referencia",
created: "Creado",
updated: "Último cambio",
history: "Historial",
help: "¿Para qué sirve un ticket?",
helpDescription:
"Registra una petición o incidencia de una compañía, recibida por email, teléfono o por iniciativa interna.",
guidance:
"La compañía queda fija tras el alta. El solicitante y los elementos afectados deben pertenecer a ella. El responsable es un usuario de Metis Desk. Los cambios conservan el historial.",
examples:
"Ana llama porque no recibe correo; la persona responsable solicita preparar varios puestos; una revisión interna detecta un problema.",
examplesLabel: "Ejemplos y recomendaciones",
scope:
"Los tickets registran la petición. Las intervenciones y el tiempo trabajado se incorporarán en una entrega posterior.",
companyNotice:
"Cambiar la compañía antes de guardar vacía el solicitante y los elementos afectados.",
inactive: "Inactivo",
loadError: "No se pudieron cargar los tickets.",
conflict:
"Este ticket ha cambiado. Cierra el formulario y abre de nuevo la edición para revisar la versión actual.",
channels: { email: "Email", phone: "Teléfono", internal: "Interno", other: "Otro" },
states: {
open: "Abierto",
in_progress: "En curso",
waiting: "En espera",
resolved: "Resuelto",
cancelled: "Cancelado",
},
priorities: { low: "Baja", normal: "Normal", high: "Alta", urgent: "Urgente" },
errors: {
INVALID_REQUEST: "Revisa los campos del formulario.",
COMPANY_NOT_FOUND: "La compañía ya no está disponible.",
TICKET_NOT_FOUND: "El ticket no pertenece a esta compañía o ya no está disponible.",
REQUESTER_UNAVAILABLE: "Selecciona una persona activa de esta compañía.",
ASSIGNEE_UNAVAILABLE: "Selecciona un usuario activo de Metis Desk.",
TARGET_UNAVAILABLE:
"Los elementos afectados deben estar disponibles y pertenecer a esta compañía.",
TICKET_CHANGED:
"Este ticket ha cambiado. Cierra y vuelve a abrir la edición para revisar la versión actual.",
REQUEST_KEY_REUSED: "Esta solicitud ya se utilizó con otros datos.",
INTERNAL_ERROR: "No se pudo completar la operación.",
},
};
export const ticketsEnglish = {
title: "Tickets",
global: "All tickets",
new: "New ticket",
edit: "Edit ticket",
details: "Details and history",
company: "Company",
subject: "Subject",
description: "Request description (optional)",
requester: "Requester (optional)",
assignee: "Assignee (optional)",
channel: "Channel",
state: "Status",
priority: "Priority",
affected: "Affected items",
kind: "Item type",
addTarget: "Add item",
person: "Person",
asset: "Asset",
resource: "Resource",
workstation: "Workstation",
newTarget: "Create item",
remove: "Remove item",
unassigned: "Unassigned",
choose: "Choose an item",
all: "All",
allCompanies: "All companies",
allStates: "All statuses",
allPriorities: "All priorities",
allAssignees: "All assignees",
search: "Search reference, subject, company or requester",
empty: "No tickets match these filters.",
loading: "Loading tickets…",
reference: "Reference",
created: "Created",
updated: "Last change",
history: "History",
help: "What is a ticket for?",
helpDescription:
"Records a company's request or issue received by email, phone or internal initiative.",
guidance:
"The company is fixed after creation. The requester and affected items must belong to it. The assignee is a Metis Desk user. Changes preserve history.",
examples:
"Ana reports missing email; a manager requests preparing several workstations; an internal review finds an issue.",
examplesLabel: "Examples and guidance",
scope:
"Tickets record the request. Interventions and time tracking will be added in a later release.",
companyNotice: "Changing company before saving clears the requester and affected items.",
inactive: "Inactive",
loadError: "Tickets could not be loaded.",
conflict: "This ticket changed. Close the form and reopen editing to review the current version.",
channels: { email: "Email", phone: "Phone", internal: "Internal", other: "Other" },
states: {
open: "Open",
in_progress: "In progress",
waiting: "Waiting",
resolved: "Resolved",
cancelled: "Cancelled",
},
priorities: { low: "Low", normal: "Normal", high: "High", urgent: "Urgent" },
errors: {
INVALID_REQUEST: "Check the form fields.",
COMPANY_NOT_FOUND: "The company is unavailable.",
TICKET_NOT_FOUND: "The ticket does not belong to this company or is unavailable.",
REQUESTER_UNAVAILABLE: "Choose an active person in this company.",
ASSIGNEE_UNAVAILABLE: "Choose an active Metis Desk user.",
TARGET_UNAVAILABLE: "Affected items must be available and belong to this company.",
TICKET_CHANGED: "This ticket changed. Close and reopen editing to review the current version.",
REQUEST_KEY_REUSED: "This request key was already used with other data.",
INTERNAL_ERROR: "The operation could not be completed.",
},
};

View File

@ -12,5 +12,9 @@
"retry": "Check again",
"language": "Language",
"languageError": "The language could not be changed.",
"startupError": "Metis Desk could not start. Reload the page."
"startupError": "Metis Desk could not start. Reload the page.",
"languageNames": {
"es": "Español",
"en": "English"
}
}

View File

@ -12,5 +12,9 @@
"retry": "Volver a comprobar",
"language": "Idioma",
"languageError": "No se ha podido cambiar el idioma.",
"startupError": "No se ha podido iniciar Metis Desk. Recarga la página."
"startupError": "No se ha podido iniciar Metis Desk. Recarga la página.",
"languageNames": {
"es": "Español",
"en": "English"
}
}

View File

@ -0,0 +1,52 @@
{
"loading": "Checking access…",
"retry": "Try again",
"setupTitle": "Set up Metis Desk",
"setupHelp": "Create the initial administrator account. For now, only this account can manage users. Setup is performed once.",
"loginTitle": "Sign in to Metis Desk",
"loginHelp": "Enter your credentials to open your support workspace.",
"name": "Name",
"email": "Email",
"password": "Password",
"passwordHelp": "Use between 8 and 128 characters. A long, memorable passphrase works well.",
"invalidForm": "Review the fields and password length.",
"createAdmin": "Create administrator",
"login": "Sign in",
"saving": "Saving…",
"saved": "Changes saved.",
"users": "Users",
"usersHelp": "Create accounts, update their details and control access. There are no configurable roles.",
"createUser": "New user",
"editUser": "Edit user",
"save": "Save",
"active": "Active",
"inactive": "Inactive",
"adminProtected": "The initial administrator must always remain active.",
"initialAdmin": "Initial administrator",
"status": "Status",
"actions": "Actions",
"edit": "Edit",
"resetPassword": "Reset password",
"changePassword": "My password",
"newPassword": "New password",
"currentPassword": "Current password",
"resetHelp": "This user's existing sessions will be closed. Share the new password through a private channel.",
"close": "Close",
"cancel": "Cancel",
"empty": "No users yet.",
"accountNavigation": "My account",
"workspace": "Home",
"logout": "Sign out",
"errors": {
"INVALID_REQUEST": "The submitted data is invalid.",
"INVALID_CREDENTIALS": "The email or password is incorrect.",
"UNAUTHENTICATED": "Your session has expired. Sign in again.",
"FORBIDDEN": "You cannot perform this action. Reload the page if your session has changed.",
"SETUP_CLOSED": "The initial administrator is already configured. Reload to sign in.",
"EMAIL_EXISTS": "A user with this email already exists.",
"USER_NOT_FOUND": "The user is no longer available.",
"ADMIN_PROTECTED": "The initial administrator account is protected.",
"TOO_MANY_REQUESTS": "Too many attempts. Try again in 15 minutes.",
"INTERNAL_ERROR": "The operation could not be completed. Please try again."
}
}

View File

@ -0,0 +1,52 @@
{
"loading": "Comprobando acceso…",
"retry": "Reintentar",
"setupTitle": "Configura Metis Desk",
"setupHelp": "Crea la cuenta de administrador inicial. Será la única que podrá gestionar usuarios por ahora. Esta configuración solo se realiza una vez.",
"loginTitle": "Accede a Metis Desk",
"loginHelp": "Introduce tus credenciales para abrir tu espacio de soporte.",
"name": "Nombre",
"email": "Correo electrónico",
"password": "Contraseña",
"passwordHelp": "Usa entre 8 y 128 caracteres. Puedes elegir una frase larga y fácil de recordar.",
"invalidForm": "Revisa los campos indicados y la longitud de la contraseña.",
"createAdmin": "Crear administrador",
"login": "Entrar",
"saving": "Guardando…",
"saved": "Cambios guardados.",
"users": "Usuarios",
"usersHelp": "Crea cuentas, actualiza sus datos y controla el acceso. No hay roles configurables.",
"createUser": "Nuevo usuario",
"editUser": "Editar usuario",
"save": "Guardar",
"active": "Activo",
"inactive": "Inactivo",
"adminProtected": "El administrador inicial siempre debe permanecer activo.",
"initialAdmin": "Administrador inicial",
"status": "Estado",
"actions": "Acciones",
"edit": "Editar",
"resetPassword": "Restablecer contraseña",
"changePassword": "Mi contraseña",
"newPassword": "Nueva contraseña",
"currentPassword": "Contraseña actual",
"resetHelp": "Las sesiones abiertas de este usuario se cerrarán. Comunícale la nueva contraseña por un canal privado.",
"close": "Cerrar",
"cancel": "Cancelar",
"empty": "Todavía no hay usuarios.",
"accountNavigation": "Mi cuenta",
"workspace": "Inicio",
"logout": "Salir",
"errors": {
"INVALID_REQUEST": "Los datos enviados no son válidos.",
"INVALID_CREDENTIALS": "El correo o la contraseña no son correctos.",
"UNAUTHENTICATED": "La sesión ha caducado. Vuelve a entrar.",
"FORBIDDEN": "No puedes realizar esta acción. Recarga la página si tu sesión ha cambiado.",
"SETUP_CLOSED": "El administrador inicial ya está configurado. Recarga la página para acceder.",
"EMAIL_EXISTS": "Ya existe un usuario con ese correo.",
"USER_NOT_FOUND": "El usuario ya no está disponible.",
"ADMIN_PROTECTED": "La cuenta de administrador inicial está protegida.",
"TOO_MANY_REQUESTS": "Demasiados intentos. Vuelve a probar dentro de 15 minutos.",
"INTERNAL_ERROR": "No se ha podido completar la operación. Inténtalo de nuevo."
}
}

View File

@ -1,109 +1,2 @@
export const identitySpanish = {
loading: "Comprobando acceso…",
retry: "Reintentar",
setupTitle: "Configura Metis Desk",
setupHelp:
"Crea la cuenta de administrador inicial. Será la única que podrá gestionar usuarios por ahora. Esta configuración solo se realiza una vez.",
loginTitle: "Accede a Metis Desk",
loginHelp: "Introduce tus credenciales para abrir tu espacio de soporte.",
name: "Nombre",
email: "Correo electrónico",
password: "Contraseña",
passwordHelp: "Usa entre 8 y 128 caracteres. Puedes elegir una frase larga y fácil de recordar.",
invalidForm: "Revisa los campos indicados y la longitud de la contraseña.",
createAdmin: "Crear administrador",
login: "Entrar",
saving: "Guardando…",
saved: "Cambios guardados.",
users: "Usuarios",
usersHelp: "Crea cuentas, actualiza sus datos y controla el acceso. No hay roles configurables.",
createUser: "Nuevo usuario",
editUser: "Editar usuario",
save: "Guardar",
active: "Activo",
inactive: "Inactivo",
adminProtected: "El administrador inicial siempre debe permanecer activo.",
initialAdmin: "Administrador inicial",
status: "Estado",
actions: "Acciones",
edit: "Editar",
resetPassword: "Restablecer contraseña",
changePassword: "Mi contraseña",
newPassword: "Nueva contraseña",
currentPassword: "Contraseña actual",
resetHelp:
"Las sesiones abiertas de este usuario se cerrarán. Comunícale la nueva contraseña por un canal privado.",
close: "Cerrar",
cancel: "Cancelar",
empty: "Todavía no hay usuarios.",
accountNavigation: "Mi cuenta",
workspace: "Inicio",
logout: "Salir",
errors: {
INVALID_REQUEST: "Los datos enviados no son válidos.",
INVALID_CREDENTIALS: "El correo o la contraseña no son correctos.",
UNAUTHENTICATED: "La sesión ha caducado. Vuelve a entrar.",
FORBIDDEN: "No puedes realizar esta acción. Recarga la página si tu sesión ha cambiado.",
SETUP_CLOSED: "El administrador inicial ya está configurado. Recarga la página para acceder.",
EMAIL_EXISTS: "Ya existe un usuario con ese correo.",
USER_NOT_FOUND: "El usuario ya no está disponible.",
ADMIN_PROTECTED: "La cuenta de administrador inicial está protegida.",
TOO_MANY_REQUESTS: "Demasiados intentos. Vuelve a probar dentro de 15 minutos.",
INTERNAL_ERROR: "No se ha podido completar la operación. Inténtalo de nuevo.",
},
};
export const identityEnglish = {
loading: "Checking access…",
retry: "Try again",
setupTitle: "Set up Metis Desk",
setupHelp:
"Create the initial administrator account. For now, only this account can manage users. Setup is performed once.",
loginTitle: "Sign in to Metis Desk",
loginHelp: "Enter your credentials to open your support workspace.",
name: "Name",
email: "Email",
password: "Password",
passwordHelp: "Use between 8 and 128 characters. A long, memorable passphrase works well.",
invalidForm: "Review the fields and password length.",
createAdmin: "Create administrator",
login: "Sign in",
saving: "Saving…",
saved: "Changes saved.",
users: "Users",
usersHelp:
"Create accounts, update their details and control access. There are no configurable roles.",
createUser: "New user",
editUser: "Edit user",
save: "Save",
active: "Active",
inactive: "Inactive",
adminProtected: "The initial administrator must always remain active.",
initialAdmin: "Initial administrator",
status: "Status",
actions: "Actions",
edit: "Edit",
resetPassword: "Reset password",
changePassword: "My password",
newPassword: "New password",
currentPassword: "Current password",
resetHelp:
"This user's existing sessions will be closed. Share the new password through a private channel.",
close: "Close",
cancel: "Cancel",
empty: "No users yet.",
accountNavigation: "My account",
workspace: "Home",
logout: "Sign out",
errors: {
INVALID_REQUEST: "The submitted data is invalid.",
INVALID_CREDENTIALS: "The email or password is incorrect.",
UNAUTHENTICATED: "Your session has expired. Sign in again.",
FORBIDDEN: "You cannot perform this action. Reload the page if your session has changed.",
SETUP_CLOSED: "The initial administrator is already configured. Reload to sign in.",
EMAIL_EXISTS: "A user with this email already exists.",
USER_NOT_FOUND: "The user is no longer available.",
ADMIN_PROTECTED: "The initial administrator account is protected.",
TOO_MANY_REQUESTS: "Too many attempts. Try again in 15 minutes.",
INTERNAL_ERROR: "The operation could not be completed. Please try again.",
},
};
export { default as identityEnglish } from "../common/locales/en.json";
export { default as identitySpanish } from "../common/locales/es.json";

View File

@ -0,0 +1,140 @@
{
"inventory": "Resources",
"mailboxKind": "Mailbox type",
"corporate": "Corporate",
"external": "External",
"relatedAccount": "Related account (optional)",
"resourceTypeId": "Access type",
"systemId": "System (optional)",
"environmentId": "Environment (optional)",
"newResource": "New resource",
"editResource": "Edit resource",
"viewResource": "Details and history",
"template": "Resource template",
"status": "Status",
"active": "Active",
"inactive": "Inactive",
"notes": "Notes (optional)",
"characteristics": "Characteristics",
"chooseValue": "Select a value",
"noValue": "No data",
"history": "History",
"recordedBy": "Recorded by {{name}}",
"snapshotNotice": "This record retains the template fields captured when the resource was created.",
"resourceDescription": "Register a mailbox, shared folder, application, certificate or software license used in this company's context.",
"resourceExamples": "Examples: admin@company.com, Administration folder, corporate Zoom, signing certificate or Microsoft 365 subscription.",
"resourceGuidance": "Characteristics are optional unless the template requires them. Never store passwords or private keys.",
"resourceScope": "This record identifies the company resource. Availability and device configuration are managed separately.",
"licenseDescription": "Register a software license or subscription contracted by this company.",
"licenseExamples": "Examples: Microsoft 365 Business Standard per user, ESET Endpoint per device or a perpetual ERP license.",
"licenseGuidance": "All fields are optional. Contracted quantity records seats or units covered by the contract. Expiration and renewal dates may differ; perpetual licenses may have no expiration. Do not enter activation keys.",
"licenseScope": "The application used and its contracted license are separate resources. This record does not allocate seats or calculate remaining capacity.",
"relatedNotice": "The template remains saved if you later cancel the resource.",
"inventoryErrors": {
"INVALID_REQUEST": "Check the form data.",
"INVALID_EMAIL": "Enter a valid email address.",
"CONTEXT_FIXED": "The resource type and context cannot change.",
"REFERENCE_UNAVAILABLE": "Select valid references from this company.",
"INVALID_CHARACTERISTICS": "A characteristic is not part of the template or has an invalid value.",
"REQUIRED_CHARACTERISTICS": "Complete the required template characteristics.",
"TEMPLATE_FIXED": "A resource's template cannot change.",
"TEMPLATE_UNAVAILABLE": "Select an active resource template from this scope.",
"NAME_EXISTS": "A resource with this name and template already exists in this company.",
"RESOURCE_NOT_FOUND": "Resource not found.",
"COMPANY_NOT_FOUND": "Company not found.",
"FORBIDDEN": "Your session does not allow this operation. Sign in again.",
"UNAUTHENTICATED": "Sign in again.",
"INTERNAL_ERROR": "The operation could not be completed."
},
"yes": "Yes",
"no": "No",
"title": "Resource catalog",
"companyTitle": "Resource catalog",
"introduction": "Define the characteristics and templates used when registering resources.",
"companyIntroduction": "Use global definitions and add those needed by this company.",
"fields": "Fields",
"templates": "Templates",
"newField": "New field",
"editField": "Edit field",
"newTemplate": "New template",
"templateUsage": "Template use",
"templateUsageHelp": "Enables resources in user profiles. The use is fixed after creation.",
"usage": {
"general": "General",
"application": "Application",
"certificate": "Digital certificate"
},
"editTemplate": "Edit template",
"name": "Name",
"description": "Description (optional)",
"type": "Data type",
"unit": "Unit (optional)",
"choices": "List values",
"choicesHelp": "Enter one value per line. Existing values are retained; you can add others.",
"text": "Text",
"number": "Number",
"date": "Date",
"boolean": "Yes / No",
"choice": "List of values",
"activate": "Activate",
"deactivate": "Deactivate",
"inherited": "Global configuration",
"overridden": "Customized for this company",
"reset": "Use global configuration",
"builtin": "Built in",
"global": "Global",
"own": "Company",
"custom": "Custom",
"origin": "Origin",
"actions": "Actions",
"edit": "Edit",
"copy": "Create copy",
"copyName": "{{name}} (copy)",
"fieldCount": "{{count}} fields",
"search": "Search",
"empty": "No results.",
"loading": "Loading catalog…",
"retry": "Retry",
"save": "Save",
"saving": "Saving…",
"cancel": "Cancel",
"selectField": "Add a field to the template",
"choose": "Select a field",
"addField": "New field",
"required": "Required",
"chooseType": "Select a type",
"view": "View",
"close": "Close",
"viewDefinition": "{{name}}",
"optional": "Optional",
"remove": "Remove field: {{name}}",
"noFields": "Add at least one field. All fields are initially optional.",
"protected": "Built-in definitions are protected. You can change their availability or create a template copy.",
"helpTitle": "What is it for?",
"examplesLabel": "Show examples and guidance",
"fieldDescription": "A field describes an resource characteristic. You can reuse it in several templates.",
"fieldExamples": "Examples: Email address (text), Expiry (date), URL (text) or Provider (text).",
"fieldGuidance": "Add a unit only when useful. Never store passwords or secrets. The data type is fixed after creation.",
"templateDescription": "A template groups the fields displayed for an resource type. It is not a specific mailbox, folder, application or certificate.",
"templateExamples": "Mailbox: address and provider. Shared folder: UNC path. Certificate: holder, issuer and expiry.",
"templateGuidance": "Characteristics are optional by default. Require only information that is always known. Removing a template field does not delete its catalog definition.",
"globalHelp": "Global scope: available to all companies.",
"companyHelp": "Company scope: additions and availability changes affect this company only.",
"disabledField": "Inactive field: retained in this template.",
"disabledFieldsNotice": "This template contains inactive fields. You can activate them in the catalog before using it.",
"quickNotice": "The field remains saved if you later cancel the template.",
"errors": {
"INVALID_REQUEST": "Check the form data.",
"INVALID_CHOICES": "A list needs unique non-empty values. Other data types do not accept list values.",
"INVALID_FIELDS": "The template needs distinct valid fields.",
"NAME_EXISTS": "A definition of this type already has this name.",
"FIELD_UNAVAILABLE": "Only active global or company fields can be added.",
"FIELD_TYPE_FIXED": "The data type and template use cannot change; existing list values cannot be removed.",
"BUILTIN_PROTECTED": "Built-in definitions cannot be edited.",
"DEFINITION_NOT_FOUND": "This definition is unavailable in this scope.",
"COMPANY_NOT_FOUND": "Company not found.",
"INTERNAL_ERROR": "The operation could not be completed.",
"FORBIDDEN": "Your session does not allow this operation. Sign in again.",
"UNAUTHENTICATED": "Sign in again."
}
}

View File

@ -0,0 +1,140 @@
{
"inventory": "Recursos",
"mailboxKind": "Tipo de buzón",
"corporate": "Corporativo",
"external": "Externo",
"relatedAccount": "Cuenta relacionada (opcional)",
"resourceTypeId": "Tipo de acceso",
"systemId": "Sistema (opcional)",
"environmentId": "Entorno (opcional)",
"newResource": "Nuevo recurso",
"editResource": "Editar recurso",
"viewResource": "Ficha e historial",
"template": "Plantilla de recurso",
"status": "Estado",
"active": "Activo",
"inactive": "Inactivo",
"notes": "Notas (opcional)",
"characteristics": "Características",
"chooseValue": "Selecciona un valor",
"noValue": "Sin datos",
"history": "Historial",
"recordedBy": "Registrado por {{name}}",
"snapshotNotice": "La ficha conserva los campos de la plantilla utilizados al dar de alta el recurso.",
"resourceDescription": "Registra un buzón, carpeta compartida, aplicación, certificado o licencia de software disponible en el contexto de esta compañía.",
"resourceExamples": "Ejemplos: administracion@empresa.es, carpeta Administración, Zoom corporativo, certificado de firma o suscripción Microsoft 365.",
"resourceGuidance": "Las características son opcionales salvo que la plantilla indique lo contrario. No registres contraseñas ni claves privadas.",
"resourceScope": "Este registro identifica el recurso de la compañía. Su disponibilidad y su configuración en los equipos se gestionan por separado.",
"licenseDescription": "Registra una licencia o suscripción de software contratada por esta compañía.",
"licenseExamples": "Ejemplos: Microsoft 365 Business Standard por usuario, ESET Endpoint por dispositivo o una licencia perpetua de ERP.",
"licenseGuidance": "Todos los campos son opcionales. Cantidad contratada indica las plazas o unidades del contrato. Caducidad y renovación pueden ser distintas; una licencia perpetua puede no tener caducidad. No registres claves de activación.",
"licenseScope": "La aplicación utilizada y la licencia contratada son recursos distintos. Este registro no asigna plazas ni calcula cuántas quedan disponibles.",
"relatedNotice": "La plantilla se guardará aunque después canceles el recurso.",
"inventoryErrors": {
"INVALID_REQUEST": "Revisa los datos del formulario.",
"INVALID_EMAIL": "Introduce una dirección de correo válida.",
"CONTEXT_FIXED": "El tipo y contexto del recurso no se pueden cambiar.",
"REFERENCE_UNAVAILABLE": "Selecciona referencias válidas de esta compañía.",
"INVALID_CHARACTERISTICS": "Una característica no pertenece a la plantilla o tiene un valor inválido.",
"REQUIRED_CHARACTERISTICS": "Completa las características obligatorias de la plantilla.",
"TEMPLATE_FIXED": "La plantilla de un recurso no puede cambiar.",
"TEMPLATE_UNAVAILABLE": "Selecciona una plantilla activa de recursos de este ámbito.",
"NAME_EXISTS": "Ya existe un recurso con ese nombre y plantilla en esta compañía.",
"RESOURCE_NOT_FOUND": "No se ha encontrado el recurso.",
"COMPANY_NOT_FOUND": "No se ha encontrado la compañía.",
"FORBIDDEN": "La sesión no permite esta operación. Vuelve a iniciar sesión.",
"UNAUTHENTICATED": "Vuelve a iniciar sesión.",
"INTERNAL_ERROR": "No se ha podido completar la operación."
},
"yes": "Sí",
"no": "No",
"title": "Catálogo de recursos",
"companyTitle": "Catálogo de recursos",
"introduction": "Define las características y plantillas que utilizarás al registrar recursos.",
"companyIntroduction": "Utiliza las definiciones globales y añade las que necesite esta compañía.",
"fields": "Campos",
"templates": "Plantillas",
"newField": "Nuevo campo",
"editField": "Editar campo",
"newTemplate": "Nueva plantilla",
"templateUsage": "Uso de la plantilla",
"templateUsageHelp": "Permite utilizar sus recursos en perfiles de usuario. El uso queda fijo tras el alta.",
"usage": {
"general": "General",
"application": "Aplicación",
"certificate": "Certificado digital"
},
"editTemplate": "Editar plantilla",
"name": "Nombre",
"description": "Descripción (opcional)",
"type": "Tipo de dato",
"unit": "Unidad (opcional)",
"choices": "Valores de la lista",
"choicesHelp": "Escribe un valor por línea. Los valores existentes se conservan; puedes añadir otros.",
"text": "Texto",
"number": "Número",
"date": "Fecha",
"boolean": "Sí / No",
"choice": "Lista de valores",
"activate": "Activar",
"deactivate": "Desactivar",
"inherited": "Configuración global",
"overridden": "Personalizado en esta compañía",
"reset": "Usar configuración global",
"builtin": "De serie",
"global": "Global",
"own": "De la compañía",
"custom": "Personalizado",
"origin": "Origen",
"actions": "Acciones",
"edit": "Editar",
"copy": "Crear copia",
"copyName": "{{name}} (copia)",
"fieldCount": "{{count}} campos",
"search": "Buscar",
"empty": "No hay resultados.",
"loading": "Cargando catálogo…",
"retry": "Reintentar",
"save": "Guardar",
"saving": "Guardando…",
"cancel": "Cancelar",
"selectField": "Añadir un campo a la plantilla",
"choose": "Selecciona un campo",
"addField": "Nuevo campo",
"required": "Obligatorio",
"chooseType": "Selecciona un tipo",
"view": "Ver",
"close": "Cerrar",
"viewDefinition": "{{name}}",
"optional": "Opcional",
"remove": "Retirar campo: {{name}}",
"noFields": "Añade al menos un campo. Todos son opcionales inicialmente.",
"protected": "Las definiciones de serie están protegidas. Puedes cambiar su disponibilidad o crear una copia de una plantilla.",
"helpTitle": "¿Qué es y para qué sirve?",
"examplesLabel": "Ver ejemplos y recomendaciones",
"fieldDescription": "Un campo describe una característica que podrás rellenar en un recurso. Puedes reutilizarlo en varias plantillas.",
"fieldExamples": "Ejemplos: Dirección de correo (texto), Caducidad (fecha), URL (texto) o Proveedor (texto).",
"fieldGuidance": "Añade una unidad solo si resulta útil. No guardes contraseñas ni secretos. El tipo de dato queda fijo después del alta.",
"templateDescription": "Una plantilla reúne los campos que se mostrarán para un tipo de recurso. No representa un buzón, carpeta, aplicación o certificado concreto.",
"templateExamples": "Buzón: dirección y proveedor. Carpeta compartida: ruta UNC. Certificado: titular, emisor y caducidad.",
"templateGuidance": "Las características son opcionales por defecto. Marca como obligatorio solo lo que siempre se conozca. Retirar un campo de una plantilla no elimina el campo del catálogo.",
"globalHelp": "Ámbito global: disponible para todas las compañías.",
"companyHelp": "Ámbito de compañía: estas adiciones y cambios de disponibilidad solo afectan a esta compañía.",
"disabledField": "Campo inactivo: se conserva en esta plantilla.",
"disabledFieldsNotice": "Esta plantilla contiene campos inactivos. Puedes activarlos en el catálogo antes de utilizarla.",
"quickNotice": "El campo se guardará aunque después canceles la plantilla.",
"errors": {
"INVALID_REQUEST": "Revisa los datos del formulario.",
"INVALID_CHOICES": "Una lista necesita valores únicos y no vacíos. Los demás tipos no admiten valores de lista.",
"INVALID_FIELDS": "La plantilla necesita campos distintos y válidos.",
"NAME_EXISTS": "Ya existe una definición de este tipo con ese nombre.",
"FIELD_UNAVAILABLE": "Solo puedes añadir campos activos de recursos del catálogo global o de esta compañía.",
"FIELD_TYPE_FIXED": "El tipo de dato y el uso de la plantilla quedan fijos; tampoco se pueden eliminar valores existentes de una lista.",
"BUILTIN_PROTECTED": "Las definiciones de serie no se pueden modificar.",
"DEFINITION_NOT_FOUND": "La definición no está disponible en este ámbito.",
"COMPANY_NOT_FOUND": "No se ha encontrado la compañía.",
"INTERNAL_ERROR": "No se ha podido completar la operación.",
"FORBIDDEN": "La sesión no permite esta operación. Vuelve a iniciar sesión.",
"UNAUTHENTICATED": "Vuelve a iniciar sesión."
}
}

View File

@ -1,208 +1,2 @@
import { inventoryEnglish, inventorySpanish } from "./inventory-i18n";
export const resourcesSpanish = {
...inventorySpanish,
yes: "Sí",
no: "No",
title: "Catálogo de recursos",
companyTitle: "Catálogo de recursos",
introduction: "Define las características y plantillas que utilizarás al registrar recursos.",
companyIntroduction: "Utiliza las definiciones globales y añade las que necesite esta compañía.",
fields: "Campos",
templates: "Plantillas",
newField: "Nuevo campo",
editField: "Editar campo",
newTemplate: "Nueva plantilla",
templateUsage: "Uso de la plantilla",
templateUsageHelp:
"Permite utilizar sus recursos en perfiles de usuario. El uso queda fijo tras el alta.",
usage: { general: "General", application: "Aplicación", certificate: "Certificado digital" },
editTemplate: "Editar plantilla",
name: "Nombre",
description: "Descripción (opcional)",
type: "Tipo de dato",
unit: "Unidad (opcional)",
choices: "Valores de la lista",
choicesHelp:
"Escribe un valor por línea. Los valores existentes se conservan; puedes añadir otros.",
text: "Texto",
number: "Número",
date: "Fecha",
boolean: "Sí / No",
choice: "Lista de valores",
active: "Activo",
inactive: "Inactivo",
activate: "Activar",
deactivate: "Desactivar",
inherited: "Configuración global",
overridden: "Personalizado en esta compañía",
reset: "Usar configuración global",
builtin: "De serie",
global: "Global",
own: "De la compañía",
custom: "Personalizado",
origin: "Origen",
actions: "Acciones",
edit: "Editar",
copy: "Crear copia",
copyName: "{{name}} (copia)",
fieldCount: "{{count}} campos",
search: "Buscar",
empty: "No hay resultados.",
loading: "Cargando catálogo…",
retry: "Reintentar",
save: "Guardar",
saving: "Guardando…",
cancel: "Cancelar",
selectField: "Añadir un campo a la plantilla",
choose: "Selecciona un campo",
addField: "Nuevo campo",
required: "Obligatorio",
chooseType: "Selecciona un tipo",
view: "Ver",
close: "Cerrar",
viewDefinition: "{{name}}",
optional: "Opcional",
remove: "Retirar campo: {{name}}",
noFields: "Añade al menos un campo. Todos son opcionales inicialmente.",
protected:
"Las definiciones de serie están protegidas. Puedes cambiar su disponibilidad o crear una copia de una plantilla.",
helpTitle: "¿Qué es y para qué sirve?",
examplesLabel: "Ver ejemplos y recomendaciones",
fieldDescription:
"Un campo describe una característica que podrás rellenar en un recurso. Puedes reutilizarlo en varias plantillas.",
fieldExamples:
"Ejemplos: Dirección de correo (texto), Caducidad (fecha), URL (texto) o Proveedor (texto).",
fieldGuidance:
"Añade una unidad solo si resulta útil. No guardes contraseñas ni secretos. El tipo de dato queda fijo después del alta.",
templateDescription:
"Una plantilla reúne los campos que se mostrarán para un tipo de recurso. No representa un buzón, carpeta, aplicación o certificado concreto.",
templateExamples:
"Buzón: dirección y proveedor. Carpeta compartida: ruta UNC. Certificado: titular, emisor y caducidad.",
templateGuidance:
"Las características son opcionales por defecto. Marca como obligatorio solo lo que siempre se conozca. Retirar un campo de una plantilla no elimina el campo del catálogo.",
globalHelp: "Ámbito global: disponible para todas las compañías.",
companyHelp:
"Ámbito de compañía: estas adiciones y cambios de disponibilidad solo afectan a esta compañía.",
disabledField: "Campo inactivo: se conserva en esta plantilla.",
disabledFieldsNotice:
"Esta plantilla contiene campos inactivos. Puedes activarlos en el catálogo antes de utilizarla.",
quickNotice: "El campo se guardará aunque después canceles la plantilla.",
errors: {
INVALID_REQUEST: "Revisa los datos del formulario.",
INVALID_CHOICES:
"Una lista necesita valores únicos y no vacíos. Los demás tipos no admiten valores de lista.",
INVALID_FIELDS: "La plantilla necesita campos distintos y válidos.",
NAME_EXISTS: "Ya existe una definición de este tipo con ese nombre.",
FIELD_UNAVAILABLE:
"Solo puedes añadir campos activos de recursos del catálogo global o de esta compañía.",
FIELD_TYPE_FIXED:
"El tipo de dato y el uso de la plantilla quedan fijos; tampoco se pueden eliminar valores existentes de una lista.",
BUILTIN_PROTECTED: "Las definiciones de serie no se pueden modificar.",
DEFINITION_NOT_FOUND: "La definición no está disponible en este ámbito.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
INTERNAL_ERROR: "No se ha podido completar la operación.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
},
};
export const resourcesEnglish = {
...inventoryEnglish,
yes: "Yes",
no: "No",
title: "Resource catalog",
companyTitle: "Resource catalog",
introduction: "Define the characteristics and templates used when registering resources.",
companyIntroduction: "Use global definitions and add those needed by this company.",
fields: "Fields",
templates: "Templates",
newField: "New field",
editField: "Edit field",
newTemplate: "New template",
templateUsage: "Template use",
templateUsageHelp: "Enables resources in user profiles. The use is fixed after creation.",
usage: { general: "General", application: "Application", certificate: "Digital certificate" },
editTemplate: "Edit template",
name: "Name",
description: "Description (optional)",
type: "Data type",
unit: "Unit (optional)",
choices: "List values",
choicesHelp: "Enter one value per line. Existing values are retained; you can add others.",
text: "Text",
number: "Number",
date: "Date",
boolean: "Yes / No",
choice: "List of values",
active: "Active",
inactive: "Inactive",
activate: "Activate",
deactivate: "Deactivate",
inherited: "Global configuration",
overridden: "Customized for this company",
reset: "Use global configuration",
builtin: "Built in",
global: "Global",
own: "Company",
custom: "Custom",
origin: "Origin",
actions: "Actions",
edit: "Edit",
copy: "Create copy",
copyName: "{{name}} (copy)",
fieldCount: "{{count}} fields",
search: "Search",
empty: "No results.",
loading: "Loading catalog…",
retry: "Retry",
save: "Save",
saving: "Saving…",
cancel: "Cancel",
selectField: "Add a field to the template",
choose: "Select a field",
addField: "New field",
required: "Required",
chooseType: "Select a type",
view: "View",
close: "Close",
viewDefinition: "{{name}}",
optional: "Optional",
remove: "Remove field: {{name}}",
noFields: "Add at least one field. All fields are initially optional.",
protected:
"Built-in definitions are protected. You can change their availability or create a template copy.",
helpTitle: "What is it for?",
examplesLabel: "Show examples and guidance",
fieldDescription:
"A field describes an resource characteristic. You can reuse it in several templates.",
fieldExamples: "Examples: Email address (text), Expiry (date), URL (text) or Provider (text).",
fieldGuidance:
"Add a unit only when useful. Never store passwords or secrets. The data type is fixed after creation.",
templateDescription:
"A template groups the fields displayed for an resource type. It is not a specific mailbox, folder, application or certificate.",
templateExamples:
"Mailbox: address and provider. Shared folder: UNC path. Certificate: holder, issuer and expiry.",
templateGuidance:
"Characteristics are optional by default. Require only information that is always known. Removing a template field does not delete its catalog definition.",
globalHelp: "Global scope: available to all companies.",
companyHelp: "Company scope: additions and availability changes affect this company only.",
disabledField: "Inactive field: retained in this template.",
disabledFieldsNotice:
"This template contains inactive fields. You can activate them in the catalog before using it.",
quickNotice: "The field remains saved if you later cancel the template.",
errors: {
INVALID_REQUEST: "Check the form data.",
INVALID_CHOICES:
"A list needs unique non-empty values. Other data types do not accept list values.",
INVALID_FIELDS: "The template needs distinct valid fields.",
NAME_EXISTS: "A definition of this type already has this name.",
FIELD_UNAVAILABLE: "Only active global or company fields can be added.",
FIELD_TYPE_FIXED:
"The data type and template use cannot change; existing list values cannot be removed.",
BUILTIN_PROTECTED: "Built-in definitions cannot be edited.",
DEFINITION_NOT_FOUND: "This definition is unavailable in this scope.",
COMPANY_NOT_FOUND: "Company not found.",
INTERNAL_ERROR: "The operation could not be completed.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
},
};
export { default as resourcesEnglish } from "../common/locales/en.json";
export { default as resourcesSpanish } from "../common/locales/es.json";

View File

@ -1,116 +0,0 @@
export const inventorySpanish = {
inventory: "Recursos",
mailboxKind: "Tipo de buzón",
corporate: "Corporativo",
external: "Externo",
relatedAccount: "Cuenta relacionada (opcional)",
resourceTypeId: "Tipo de acceso",
systemId: "Sistema (opcional)",
environmentId: "Entorno (opcional)",
newResource: "Nuevo recurso",
editResource: "Editar recurso",
viewResource: "Ficha e historial",
template: "Plantilla de recurso",
status: "Estado",
active: "Activo",
inactive: "Inactivo",
notes: "Notas (opcional)",
characteristics: "Características",
chooseValue: "Selecciona un valor",
noValue: "Sin datos",
history: "Historial",
recordedBy: "Registrado por {{name}}",
snapshotNotice:
"La ficha conserva los campos de la plantilla utilizados al dar de alta el recurso.",
resourceDescription:
"Registra un buzón, carpeta compartida, aplicación, certificado o licencia de software disponible en el contexto de esta compañía.",
resourceExamples:
"Ejemplos: administracion@empresa.es, carpeta Administración, Zoom corporativo, certificado de firma o suscripción Microsoft 365.",
resourceGuidance:
"Las características son opcionales salvo que la plantilla indique lo contrario. No registres contraseñas ni claves privadas.",
resourceScope:
"Este registro identifica el recurso de la compañía. Su disponibilidad y su configuración en los equipos se gestionan por separado.",
licenseDescription:
"Registra una licencia o suscripción de software contratada por esta compañía.",
licenseExamples:
"Ejemplos: Microsoft 365 Business Standard por usuario, ESET Endpoint por dispositivo o una licencia perpetua de ERP.",
licenseGuidance:
"Todos los campos son opcionales. Cantidad contratada indica las plazas o unidades del contrato. Caducidad y renovación pueden ser distintas; una licencia perpetua puede no tener caducidad. No registres claves de activación.",
licenseScope:
"La aplicación utilizada y la licencia contratada son recursos distintos. Este registro no asigna plazas ni calcula cuántas quedan disponibles.",
relatedNotice: "La plantilla se guardará aunque después canceles el recurso.",
inventoryErrors: {
INVALID_REQUEST: "Revisa los datos del formulario.",
INVALID_EMAIL: "Introduce una dirección de correo válida.",
CONTEXT_FIXED: "El tipo y contexto del recurso no se pueden cambiar.",
REFERENCE_UNAVAILABLE: "Selecciona referencias válidas de esta compañía.",
INVALID_CHARACTERISTICS:
"Una característica no pertenece a la plantilla o tiene un valor inválido.",
REQUIRED_CHARACTERISTICS: "Completa las características obligatorias de la plantilla.",
TEMPLATE_FIXED: "La plantilla de un recurso no puede cambiar.",
TEMPLATE_UNAVAILABLE: "Selecciona una plantilla activa de recursos de este ámbito.",
NAME_EXISTS: "Ya existe un recurso con ese nombre y plantilla en esta compañía.",
RESOURCE_NOT_FOUND: "No se ha encontrado el recurso.",
COMPANY_NOT_FOUND: "No se ha encontrado la compañía.",
FORBIDDEN: "La sesión no permite esta operación. Vuelve a iniciar sesión.",
UNAUTHENTICATED: "Vuelve a iniciar sesión.",
INTERNAL_ERROR: "No se ha podido completar la operación.",
},
};
export const inventoryEnglish = {
inventory: "Resources",
mailboxKind: "Mailbox type",
corporate: "Corporate",
external: "External",
relatedAccount: "Related account (optional)",
resourceTypeId: "Access type",
systemId: "System (optional)",
environmentId: "Environment (optional)",
newResource: "New resource",
editResource: "Edit resource",
viewResource: "Details and history",
template: "Resource template",
status: "Status",
active: "Active",
inactive: "Inactive",
notes: "Notes (optional)",
characteristics: "Characteristics",
chooseValue: "Select a value",
noValue: "No data",
history: "History",
recordedBy: "Recorded by {{name}}",
snapshotNotice: "This record retains the template fields captured when the resource was created.",
resourceDescription:
"Register a mailbox, shared folder, application, certificate or software license used in this company's context.",
resourceExamples:
"Examples: admin@company.com, Administration folder, corporate Zoom, signing certificate or Microsoft 365 subscription.",
resourceGuidance:
"Characteristics are optional unless the template requires them. Never store passwords or private keys.",
resourceScope:
"This record identifies the company resource. Availability and device configuration are managed separately.",
licenseDescription: "Register a software license or subscription contracted by this company.",
licenseExamples:
"Examples: Microsoft 365 Business Standard per user, ESET Endpoint per device or a perpetual ERP license.",
licenseGuidance:
"All fields are optional. Contracted quantity records seats or units covered by the contract. Expiration and renewal dates may differ; perpetual licenses may have no expiration. Do not enter activation keys.",
licenseScope:
"The application used and its contracted license are separate resources. This record does not allocate seats or calculate remaining capacity.",
relatedNotice: "The template remains saved if you later cancel the resource.",
inventoryErrors: {
INVALID_REQUEST: "Check the form data.",
INVALID_EMAIL: "Enter a valid email address.",
CONTEXT_FIXED: "The resource type and context cannot change.",
REFERENCE_UNAVAILABLE: "Select valid references from this company.",
INVALID_CHARACTERISTICS:
"A characteristic is not part of the template or has an invalid value.",
REQUIRED_CHARACTERISTICS: "Complete the required template characteristics.",
TEMPLATE_FIXED: "A resource's template cannot change.",
TEMPLATE_UNAVAILABLE: "Select an active resource template from this scope.",
NAME_EXISTS: "A resource with this name and template already exists in this company.",
RESOURCE_NOT_FOUND: "Resource not found.",
COMPANY_NOT_FOUND: "Company not found.",
FORBIDDEN: "Your session does not allow this operation. Sign in again.",
UNAUTHENTICATED: "Sign in again.",
INTERNAL_ERROR: "The operation could not be completed.",
},
};

View File

@ -4,6 +4,8 @@ import { cn } from "cn";
import { XIcon } from "lucide-react";
import type * as React from "react";
import english from "../locales/en.json";
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}
@ -37,9 +39,11 @@ function DialogContent({
className,
children,
showCloseButton = true,
closeLabel = english.close,
...props
}: DialogPrimitive.Popup.Props & {
showCloseButton?: boolean;
closeLabel?: string;
}) {
return (
<DialogPortal>
@ -59,7 +63,7 @@ function DialogContent({
render={<Button className="absolute top-4 right-4" size="icon-sm" variant="ghost" />}
>
<XIcon />
<span className="sr-only">Close</span>
<span className="sr-only">{closeLabel}</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Popup>
@ -76,10 +80,12 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
function DialogFooter({
className,
showCloseButton = false,
closeLabel = english.close,
children,
...props
}: React.ComponentProps<"div"> & {
showCloseButton?: boolean;
closeLabel?: string;
}) {
return (
<div
@ -89,7 +95,9 @@ function DialogFooter({
>
{children}
{showCloseButton && (
<DialogPrimitive.Close render={<Button variant="outline" />}>Close</DialogPrimitive.Close>
<DialogPrimitive.Close render={<Button variant="outline" />}>
{closeLabel}
</DialogPrimitive.Close>
)}
</div>
);

View File

@ -4,7 +4,7 @@ import { test } from "node:test";
import { renderToStaticMarkup } from "react-dom/server";
import { EntityAction, EntityRowActions, EntityTable } from "./components/entity-list";
import { entityListPages, entityListRange } from "./lib/entity-list";
import { entityListLabels, entityListPages, entityListRange } from "./lib/entity-list";
const items = Array.from({ length: 21 }, (_, index) => ({
id: String(index),
@ -140,3 +140,13 @@ test("acciones: siempre última, compacta y anclada a la derecha, aunque se decl
assert.ok(row.includes("text-right"));
assert.ok(row.includes("bg-card"));
});
test("los textos comunes mantienen claves, idiomas y parámetros del rango", () => {
const spanish = entityListLabels("es");
const english = entityListLabels("en-GB");
assert.deepEqual(Object.keys(spanish).sort(), Object.keys(english).sort());
assert.equal(spanish.range(1, 10, 25, "marcas"), "Mostrando 1-10 de 25 marcas");
assert.equal(english.range(0, 0, 0, "brands"), "Showing 0-0 of 0 brands");
assert.equal(entityListLabels("fr").actions, spanish.actions);
assert.equal(english.range(1, 1, 1, "$&"), "Showing 1-1 of 1 $&");
});

View File

@ -1,49 +1,17 @@
import english from "../locales/en.json";
import spanish from "../locales/es.json";
export function entityListLabels(locale = "es") {
return locale.startsWith("en")
? {
search: "Quick search",
placeholder: "Search by name…",
clear: "Clear filters",
columns: "Columns",
sort: "Sort by",
initial: "Initial order",
ascending: "Ascending",
descending: "Descending",
previous: "Previous page",
next: "Next page",
pageSize: "Items per page",
page: "Page",
actions: "Actions",
loading: "Loading…",
unavailable: "Unable to load this list.",
empty: "No records.",
noResults: "No matching results.",
description: "View and manage",
range: (from: number, to: number, total: number, entity: string) =>
`Showing ${from}-${to} of ${total} ${entity}`,
}
: {
search: "Búsqueda rápida",
placeholder: "Buscar por nombre…",
clear: "Limpiar filtros",
columns: "Columnas",
sort: "Ordenar por",
initial: "Orden inicial",
ascending: "Ascendente",
descending: "Descendente",
previous: "Página anterior",
next: "Página siguiente",
pageSize: "Elementos por página",
page: "Página",
actions: "Acciones",
loading: "Cargando…",
unavailable: "No se ha podido cargar el listado.",
empty: "No hay registros.",
noResults: "No hay resultados para estos criterios.",
description: "Consulta y gestiona",
range: (from: number, to: number, total: number, entity: string) =>
`Mostrando ${from}-${to} de ${total} ${entity}`,
};
const labels = locale.startsWith("en") ? english : spanish;
return {
...labels,
range: (from: number, to: number, total: number, entity: string) =>
labels.range
.replace("{{from}}", String(from))
.replace("{{to}}", String(to))
.replace("{{total}}", String(total))
.replace("{{entity}}", () => entity),
};
}
export function entityListRange(total: number, pageIndex: number, pageSize: number) {

View File

@ -0,0 +1,22 @@
{
"search": "Quick search",
"placeholder": "Search by name…",
"clear": "Clear filters",
"columns": "Columns",
"sort": "Sort by",
"initial": "Initial order",
"ascending": "Ascending",
"descending": "Descending",
"previous": "Previous page",
"next": "Next page",
"pageSize": "Items per page",
"page": "Page",
"actions": "Actions",
"loading": "Loading…",
"unavailable": "Unable to load this list.",
"empty": "No records.",
"noResults": "No matching results.",
"description": "View and manage",
"range": "Showing {{from}}-{{to}} of {{total}} {{entity}}",
"close": "Close"
}

View File

@ -0,0 +1,22 @@
{
"search": "Búsqueda rápida",
"placeholder": "Buscar por nombre…",
"clear": "Limpiar filtros",
"columns": "Columnas",
"sort": "Ordenar por",
"initial": "Orden inicial",
"ascending": "Ascendente",
"descending": "Descendente",
"previous": "Página anterior",
"next": "Página siguiente",
"pageSize": "Elementos por página",
"page": "Página",
"actions": "Acciones",
"loading": "Cargando…",
"unavailable": "No se ha podido cargar el listado.",
"empty": "No hay registros.",
"noResults": "No hay resultados para estos criterios.",
"description": "Consulta y gestiona",
"range": "Mostrando {{from}}-{{to}} de {{total}} {{entity}}",
"close": "Cerrar"
}