From f4faa52c363242a2bcd4c70502874643d9cee80a Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:48 +0200 Subject: [PATCH 01/12] fix(i18n): migrate es translations from voseo to castellano in shared and dashboard --- features/dashboard/i18n/es.ts | 2 +- features/shared/i18n/es.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/features/dashboard/i18n/es.ts b/features/dashboard/i18n/es.ts index 0260ca8..540d53c 100644 --- a/features/dashboard/i18n/es.ts +++ b/features/dashboard/i18n/es.ts @@ -36,7 +36,7 @@ const dict = { degraded: "Degradado", unreachable: "No disponible", }, - hint: "Verificá que LocalStack esté corriendo", + hint: "Verifica que LocalStack esté corriendo", columns: { service: "Servicio", status: "Estado", diff --git a/features/shared/i18n/es.ts b/features/shared/i18n/es.ts index 10e39bc..1daa5a0 100644 --- a/features/shared/i18n/es.ts +++ b/features/shared/i18n/es.ts @@ -30,13 +30,13 @@ const dict = { endpointInvalidUrl: "Debe ser una URL absoluta válida", regionSectionTitle: "Región de AWS", regionInputLabel: "Región", - regionDescription: "Seleccioná la región de AWS para tu entorno local", - regionPlaceholder: "Seleccioná una región", + regionDescription: "Selecciona la región de AWS para tu entorno local", + regionPlaceholder: "Selecciona una región", regionSave: "Guardar", regionSaving: "Guardando…", regionSuccess: "Región actualizada", regionInvalid: "Debe ser una región de AWS válida", - regionProfileActiveInfo: "La región está controlada por el perfil activo. Editá el perfil para cambiarla.", + regionProfileActiveInfo: "La región está controlada por el perfil activo. Edita el perfil para cambiarla.", profilesSectionTitle: "Perfiles de entorno", profilesCounter: "{{count}}/10 perfiles", profileAdd: "Crear perfil", @@ -53,7 +53,7 @@ const dict = { profileActiveBadge: "Activo", profileNameDuplicate: "Ya existe un perfil con ese nombre", profileCapReached: "Se alcanzó el máximo de 10 perfiles", - profileDeleteActive: "Desactivá el perfil antes de eliminarlo", + profileDeleteActive: "Desactiva el perfil antes de eliminarlo", profileNotFound: "Perfil no encontrado", profileInvalidEndpoint: "Debe ser una URL absoluta válida", profileInvalidRegion: "Debe ser una región de AWS válida", @@ -63,7 +63,7 @@ const dict = { profileActivateSuccess: "Perfil activado", profileDeactivateSuccess: "Perfil desactivado", profileEmpty: "Sin perfiles todavía", - profileDeleteConfirm: "¿Estás seguro de que querés eliminar este perfil?", + profileDeleteConfirm: "¿Estás seguro de que quieres eliminar este perfil?", profileExport: "Exportar perfiles", profileImport: "Importar perfiles", profileImportSuccess: "{{count}} perfil(es) importado(s)", From c43703f396dd01f389ddac92648e74eca15c21e0 Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:52 +0200 Subject: [PATCH 02/12] fix(i18n): migrate es translations from voseo to castellano in dynamodb --- features/dynamodb/i18n/es.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/features/dynamodb/i18n/es.ts b/features/dynamodb/i18n/es.ts index e54b255..cc3bdcb 100644 --- a/features/dynamodb/i18n/es.ts +++ b/features/dynamodb/i18n/es.ts @@ -23,7 +23,7 @@ const dict = { createTableDialog: { trigger: "Crear tabla", title: "Crear tabla DynamoDB", - description: "Creá una nueva tabla DynamoDB en LocalStack.", + description: "Crea una nueva tabla DynamoDB en LocalStack.", nameLabel: "Nombre de la tabla", namePlaceholder: "mi-tabla", nameHint: "3–255 caracteres, letras, números, guiones, guiones bajos, puntos", @@ -44,7 +44,7 @@ const dict = { nameInvalid: "El nombre de la tabla debe tener entre 3 y 255 caracteres y contener solo letras, números, guiones, guiones bajos y puntos.", pkNameRequired: "El nombre de la clave de partición es obligatorio.", - pkTypeInvalid: "Seleccioná un tipo de clave de partición válido.", + pkTypeInvalid: "Selecciona un tipo de clave de partición válido.", skIncomplete: "El tipo de clave de ordenamiento es obligatorio cuando se indica un nombre de clave de ordenamiento.", }, @@ -74,9 +74,9 @@ const dict = { trigger: "Insertar ítem", title: "Insertar ítem", description: - 'Insertá o reemplazá un ítem usando JSON nativo. Ejemplo: {"pk": "valor", "count": 1}', + 'Inserta o reemplaza un ítem usando JSON nativo. Ejemplo: {"pk": "valor", "count": 1}', jsonLabel: "JSON del ítem", - jsonHint: "Escribí JSON nativo — no el formato AttributeValue de DynamoDB.", + jsonHint: "Escribe JSON nativo — no el formato AttributeValue de DynamoDB.", jsonPlaceholder: '{"pk": "valor", "sk": "valor-ordenamiento"}', cancel: "Cancelar", submit: "Insertar ítem", @@ -99,18 +99,18 @@ const dict = { resourceNotFound: "La tabla especificada no existe.", validation: "Parámetros de solicitud inválidos.", conditionalCheckFailed: "No se pudo evaluar una condición especificada en la operación.", - limitExceeded: "Demasiadas solicitudes. Intentá de nuevo.", + limitExceeded: "Demasiadas solicitudes. Inténtalo de nuevo.", endpointUnreachable: - "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", }, editItemDialog: { trigger: "Editar", title: "Editar ítem", - description: "Editá los atributos no clave de este ítem. Los campos clave están bloqueados.", + description: "Edita los atributos no clave de este ítem. Los campos clave están bloqueados.", keyFieldsLabel: "Campos clave (bloqueados)", jsonLabel: "JSON del ítem", - jsonHint: "Editá los atributos no clave usando JSON nativo.", + jsonHint: "Edita los atributos no clave usando JSON nativo.", cancel: "Cancelar", submit: "Guardar cambios", saving: "Guardando…", @@ -125,7 +125,7 @@ const dict = { modeQuery: "Consulta", pkLabel: "Valor de clave de partición", pkPlaceholder: "ej. usuario-1", - pkHint: "Ingresá un valor de clave de partición para consultar ítems. La PK es obligatoria.", + pkHint: "Introduce un valor de clave de partición para consultar ítems. La PK es obligatoria.", skLabel: "Valor de clave de ordenamiento (opcional)", skPlaceholder: "ej. pedido-5", search: "Buscar", @@ -146,7 +146,7 @@ const dict = { errors: { connectFailed: "No se pudo conectar a DynamoDB", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", tableNotFound: "Tabla no encontrada", tableNotFoundDetail: "LocalStack no tiene una tabla para esta URL. Puede haberse borrado, reiniciado LocalStack o el enlace estar desactualizado.", @@ -156,7 +156,7 @@ const dict = { seedDialog: { trigger: "Importar datos", title: "Importar datos", - description: "Importá ítems desde un archivo JSON o CSV a esta tabla.", + description: "Importa ítems desde un archivo JSON o CSV a esta tabla.", fileLabel: "Archivo de datos", fileHint: "Acepta .json (array de objetos) o .csv (fila de encabezados + filas de datos). Nota: CSV no soporta campos entre comillas con comas.", @@ -164,13 +164,13 @@ const dict = { overwriteHint: "Desactivar no tiene efecto en la versión actual — los ítems siempre se sobreescriben por clave. Esta opción está reservada para un futuro modo de omisión si ya existe.", fileSizeWarning: - "El archivo supera 500 KB. Subir payloads grandes puede ser lento o fallar. Considerá dividir el archivo.", - errorInvalidFile: "Tipo de archivo inválido. Seleccioná un archivo .json o .csv.", - errorEmptyArray: "El archivo contiene un array vacío. Agregá al menos un ítem.", + "El archivo supera 500 KB. Subir payloads grandes puede ser lento o fallar. Considera dividir el archivo.", + errorInvalidFile: "Tipo de archivo inválido. Selecciona un archivo .json o .csv.", + errorEmptyArray: "El archivo contiene un array vacío. Agrega al menos un ítem.", errorParseJson: - "No se pudo analizar el archivo como JSON. Asegurate de que sea un array JSON válido.", + "No se pudo analizar el archivo como JSON. Asegúrate de que sea un array JSON válido.", errorParseCsv: - "El archivo CSV no tiene filas de datos. Agregá al menos una fila después del encabezado.", + "El archivo CSV no tiene filas de datos. Agrega al menos una fila después del encabezado.", successToast: "Se importaron {count} ítems exitosamente.", errorPartialFail: "{failed} de {total} ítems no se pudieron importar.", importing: "Importando…", From 32724e8aba60890a17e838e9673118880bd067d5 Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:55 +0200 Subject: [PATCH 03/12] fix(i18n): migrate es translations from voseo to castellano in lambda --- features/lambda/i18n/es.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/features/lambda/i18n/es.ts b/features/lambda/i18n/es.ts index 19cd952..22c9fe5 100644 --- a/features/lambda/i18n/es.ts +++ b/features/lambda/i18n/es.ts @@ -35,14 +35,14 @@ const dict = { nameLabel: "Nombre de la función", namePlaceholder: "mi-funcion", runtimeLabel: "Runtime", - runtimePlaceholder: "Seleccioná un runtime", + runtimePlaceholder: "Selecciona un runtime", handlerLabel: "Handler", handlerPlaceholder: "index.handler", handlerHint: "El punto de entrada de tu código (ej. index.handler).", descriptionLabel: "Descripción", descriptionPlaceholder: "Descripción opcional", roleLabel: "ARN del rol de ejecución", - roleHint: "Valor por defecto de LocalStack. Reemplazalo con un ARN real al apuntar a AWS.", + roleHint: "Valor por defecto de LocalStack. Reemplázalo con un ARN real al apuntar a AWS.", cancel: "Cancelar", submit: "Crear", creating: "Creando…", @@ -52,14 +52,14 @@ const dict = { required: "El nombre de la función es obligatorio.", tooLong: "El nombre de la función no puede superar los 64 caracteres.", invalidPattern: "El nombre solo puede contener letras, números, guiones y guiones bajos.", - invalidRuntime: "Seleccioná un runtime válido.", + invalidRuntime: "Selecciona un runtime válido.", handlerRequired: "El handler es obligatorio.", roleRequired: "El ARN del rol de ejecución es obligatorio.", }, invokeDialog: { title: "Invocar {functionName}", payloadLabel: "Payload (JSON)", - payloadPlaceholder: "Dejá vacío o ingresá un objeto JSON válido…", + payloadPlaceholder: "Deja vacío o introduce un objeto JSON válido…", cancel: "Cancelar", submit: "Invocar", invoking: "Invocando…", @@ -74,8 +74,8 @@ const dict = { updateCodeDialog: { title: "Actualizar código de la función", fileLabel: "Paquete de despliegue", - fileHint: "Subí un archivo .zip (hasta 50 MB).", - selectFile: "Seleccioná un archivo .zip.", + fileHint: "Sube un archivo .zip (hasta 50 MB).", + selectFile: "Selecciona un archivo .zip.", fileTooLarge: "El archivo supera el límite de 50 MB.", uploading: "Subiendo… {percent}%", finalizing: "Procesando en el servidor…", @@ -118,10 +118,10 @@ const dict = { sdkErrors: { notFound: "La función especificada no existe.", invalidParam: "Parámetro de función inválido.", - tooManyRequests: "Demasiadas solicitudes. Intentá de nuevo.", + tooManyRequests: "Demasiadas solicitudes. Inténtalo de nuevo.", serviceError: "Error del servicio Lambda.", payloadTooLarge: "El payload es demasiado grande.", - endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", codeStorageExceeded: "Se superó el límite de almacenamiento de código de la cuenta.", invalidZip: "El archivo subido no es un paquete de despliegue Lambda válido.", @@ -129,7 +129,7 @@ const dict = { errors: { connectFailed: "No se pudo conectar a Lambda", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", connectFailedDetailGeneric: "No se pudo alcanzar LocalStack desde el servidor de la app. Comprueba que esté en marcha, que AWS_ENDPOINT_URL sea correcto y define NEXT_PUBLIC_AWS_ENDPOINT_URL con la misma URL (necesario para mensajes en el cliente si usas una IP de red local).", functionNotFound: "Función no encontrada", From 642b39149eac2975f14918585167ef1d3a611cf6 Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:58 +0200 Subject: [PATCH 04/12] fix(i18n): migrate es translations from voseo to castellano in sqs and sns --- features/sns/i18n/es.ts | 6 +++--- features/sqs/i18n/es.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/features/sns/i18n/es.ts b/features/sns/i18n/es.ts index e101d95..b7e0012 100644 --- a/features/sns/i18n/es.ts +++ b/features/sns/i18n/es.ts @@ -34,7 +34,7 @@ const dict = { delete: "Eliminar", deleteTitle: "Eliminar topic", deleteConfirm: - "¿Seguro que querés eliminar {topic}? Esta acción no se puede deshacer.", + "¿Seguro que quieres eliminar {topic}? Esta acción no se puede deshacer.", viewDetail: "Ver detalle", publish: "Publicar mensaje", }, @@ -99,13 +99,13 @@ const dict = { limitExceeded: "Se alcanzó el límite de topics para esta cuenta.", kmsError: "La clave KMS asociada a este topic no está accesible.", notAuthorized: "Sin autorización para realizar esta acción en SNS.", - endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", }, errors: { connectFailed: "No se pudo conectar a SNS", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", connectFailedDetailGeneric: "No se pudo alcanzar LocalStack desde el servidor de la app. Comprueba que esté en marcha, que AWS_ENDPOINT_URL sea correcto y define NEXT_PUBLIC_AWS_ENDPOINT_URL con la misma URL (necesario para mensajes en el cliente si usas una IP de red local).", topicNotFound: "Topic no encontrado", diff --git a/features/sqs/i18n/es.ts b/features/sqs/i18n/es.ts index 43e65e7..1a5e8e1 100644 --- a/features/sqs/i18n/es.ts +++ b/features/sqs/i18n/es.ts @@ -33,7 +33,7 @@ const dict = { createQueueValidation: { required: "El nombre de la cola es obligatorio.", standardCannotEndFifo: - "Las colas estándar no pueden terminar en .fifo. Desmarcá FIFO o usá una cola FIFO.", + "Las colas estándar no pueden terminar en .fifo. Desmarca FIFO o usa una cola FIFO.", nameTooLong: "El nombre debe tener como máximo 80 caracteres (incluido .fifo en colas FIFO).", fifoMustEndFifo: "Los nombres de cola FIFO deben terminar en .fifo.", @@ -47,7 +47,7 @@ const dict = { delete: "Eliminar", deleteTitle: "Eliminar cola", deleteConfirm: - "¿Seguro que querés eliminar {queue}? Los mensajes de la cola se perderán. Esta acción no se puede deshacer.", + "¿Seguro que quieres eliminar {queue}? Los mensajes de la cola se perderán. Esta acción no se puede deshacer.", viewDetail: "Ver detalle", }, queueDetail: { @@ -108,15 +108,15 @@ const dict = { notFound: "La cola especificada no existe.", limitExceeded: "Se alcanzó el límite de colas para esta cuenta.", notAuthorized: "Sin autorización para realizar esta acción en SQS.", - endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", receiptHandleInvalid: - "La ventana de visibilidad de este mensaje expiró — recibí un nuevo lote para reintentar.", + "La ventana de visibilidad de este mensaje expiró — recibe un nuevo lote para reintentar.", }, errors: { connectFailed: "No se pudo conectar a SQS", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", connectFailedDetailGeneric: "No se pudo alcanzar LocalStack desde el servidor de la app. Comprueba que esté en marcha, que AWS_ENDPOINT_URL sea correcto y define NEXT_PUBLIC_AWS_ENDPOINT_URL con la misma URL (necesario para mensajes en el cliente si usas una IP de red local).", queueNotFound: "Cola no encontrada", From d73a662b92bc2c91dc0bc8d73caeb734e65dca8c Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:22:01 +0200 Subject: [PATCH 05/12] fix(i18n): migrate es translations from voseo to castellano in logs, seed, timeline, snapshots, inspector --- features/inspector/i18n/es.ts | 2 +- features/logs/i18n/es.ts | 2 +- features/seed/i18n/es.ts | 6 +++--- features/snapshots/i18n/es.ts | 4 ++-- features/timeline/i18n/es.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/features/inspector/i18n/es.ts b/features/inspector/i18n/es.ts index 98f4ddc..b0aea43 100644 --- a/features/inspector/i18n/es.ts +++ b/features/inspector/i18n/es.ts @@ -5,7 +5,7 @@ type InspectorDictTranslated = WidenStringLiterals; const dict = { title: "Inspector de Solicitudes AWS", - description: "Inspeccioná cada llamada al SDK de AWS realizada por Server Actions — filtrá, buscá y repetí.", + description: "Inspecciona cada llamada al SDK de AWS realizada por Server Actions — filtra, busca y repite.", toolbar: { filters: { service: { diff --git a/features/logs/i18n/es.ts b/features/logs/i18n/es.ts index 05ee3aa..e6f5963 100644 --- a/features/logs/i18n/es.ts +++ b/features/logs/i18n/es.ts @@ -5,7 +5,7 @@ type LogsDictTranslated = WidenStringLiterals; const dict = { title: "CloudWatch Logs", - description: "Transmití, filtrá y buscá entradas de log desde el endpoint de AWS configurado.", + description: "Transmite, filtra y busca entradas de log desde el endpoint de AWS configurado.", filters: { service: "Servicio", level: "Nivel", diff --git a/features/seed/i18n/es.ts b/features/seed/i18n/es.ts index fa91258..27d0afd 100644 --- a/features/seed/i18n/es.ts +++ b/features/seed/i18n/es.ts @@ -6,10 +6,10 @@ type SeedDictTranslated = WidenStringLiterals; const dict = { page: { title: "Datos de demo", - description: "Cargá recursos predefinidos en tu entorno de LocalStack o reiniciá todo.", + description: "Carga recursos predefinidos en tu entorno de LocalStack o reinicia todo.", }, presets: { - sectionTitle: "Elegí un preset", + sectionTitle: "Elige un preset", ecommerce: { name: "E-commerce", description: "Buckets S3, colas SQS, tablas DynamoDB, funciones Lambda y topics SNS para una plataforma de e-commerce.", @@ -26,7 +26,7 @@ const dict = { load: { button: "Cargar preset", loading: "Cargando…", - noPresetSelected: "Seleccioná un preset para continuar", + noPresetSelected: "Selecciona un preset para continuar", successTitle: "Preset cargado", errorTitle: "Error al cargar", }, diff --git a/features/snapshots/i18n/es.ts b/features/snapshots/i18n/es.ts index f973a7e..67ac344 100644 --- a/features/snapshots/i18n/es.ts +++ b/features/snapshots/i18n/es.ts @@ -6,7 +6,7 @@ type SnapshotsDictTranslated = WidenStringLiterals; const dict = { page: { title: "Snapshots", - description: "Capturá, exportá, importá y restaurá tu entorno de LocalStack.", + description: "Captura, exporta, importa y restaura tu entorno de LocalStack.", }, create: { sectionTitle: "Crear Snapshot", @@ -34,7 +34,7 @@ const dict = { restoring: "Restaurando…", successTitle: "Restauración completa", errorTitle: "Error al restaurar", - noSnapshot: "No hay snapshot para restaurar. Creá o importá uno primero.", + noSnapshot: "No hay snapshot para restaurar. Crea o importa uno primero.", snapshotInfo: "Snapshot del {{date}} — {{tables}} tablas, {{queues}} colas, {{buckets}} buckets", }, diff --git a/features/timeline/i18n/es.ts b/features/timeline/i18n/es.ts index 84666e7..358434f 100644 --- a/features/timeline/i18n/es.ts +++ b/features/timeline/i18n/es.ts @@ -29,7 +29,7 @@ const dict = { }, empty: { title: "No se encontraron eventos", - body: "No se encontraron eventos para el rango de tiempo seleccionado. Asegurate de que LocalStack esté corriendo y los servicios estén siendo invocados.", + body: "No se encontraron eventos para el rango de tiempo seleccionado. Asegúrate de que LocalStack esté corriendo y los servicios estén siendo invocados.", }, } as const satisfies TimelineDictTranslated; From d10defc2d30ffe4e75ddb016ede6305d14d7d2b3 Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:22:23 +0200 Subject: [PATCH 06/12] docs: add getting started, LocalStack/Floci setup, architecture, and contributing guides Closes #109 Closes #110 Closes #111 Closes #112 --- CONTRIBUTING.md | 5 ++ README.md | 13 ++-- docs/architecture.md | 143 +++++++++++++++++++++++++++++++++++++++ docs/contributing.md | 127 ++++++++++++++++++++++++++++++++++ docs/getting-started.md | 88 ++++++++++++++++++++++++ docs/localstack-setup.md | 139 +++++++++++++++++++++++++++++++++++++ 6 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 docs/architecture.md create mode 100644 docs/contributing.md create mode 100644 docs/getting-started.md create mode 100644 docs/localstack-setup.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e15deb0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contributing + +Thanks for contributing! Full guidelines live in [`docs/contributing.md`](./docs/contributing.md). + +For AI agent conventions, see [`AGENTS.md`](./AGENTS.md). diff --git a/README.md b/README.md index 8327f4a..caab96b 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,7 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next- First, run the development server: ```bash -npm run dev -# or -yarn dev -# or pnpm dev -# or -bun dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. @@ -118,3 +112,10 @@ Releases are cut from the `Release` workflow (Actions → **Release** → _Run w SemVer bump (`patch | minor | major`) and a channel (`stable | alpha | beta | rc`); the workflow bumps `package.json`, tags the commit, builds multi-arch, and publishes to both registries. +## Documentation + +- [Getting Started](./docs/getting-started.md) — prerequisites, install, run, and test locally +- [LocalStack / Floci Setup](./docs/localstack-setup.md) — local AWS emulation with Docker +- [Architecture](./docs/architecture.md) — feature-sliced layout, RSC, i18n routing +- [Contributing](./docs/contributing.md) — workflow, commit conventions, PR guidelines + diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..2d7e51a --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,143 @@ +# Architecture + +This document describes the high-level structure of the application. It covers the request flow, the feature-sliced directory layout, the AWS SDK boundary, i18n routing, and state management conventions. + +No implementation code is included — this is a structural reference. + +--- + +## Request flow + +```mermaid +flowchart LR + Browser["Browser\n(React Client)"] + AppRouter["Next.js App Router\n(Server Components\n+ Route Handlers)"] + SDK["AWS SDK\n(server-side only)"] + Emulator["LocalStack / Floci\n(local)"] + AWS["AWS\n(production)"] + + Browser -->|RSC payload / fetch| AppRouter + AppRouter -->|serialized data| Browser + AppRouter --> SDK + SDK --> Emulator + SDK --> AWS +``` + +The browser never talks to AWS directly. All AWS SDK calls happen inside Server Components or Route Handlers. The browser receives serialized data (JSON, RSC payload). + +--- + +## Feature-sliced structure + +The codebase is organized around AWS service domains. Each domain lives under `features/` and follows a consistent internal layout: + +```mermaid +flowchart TD + Features["features/"] + + Features --> S3["s3/"] + Features --> SQS["sqs/"] + Features --> DynamoDB["dynamodb/"] + Features --> Lambda["lambda/"] + Features --> SNS["sns/"] + Features --> Logs["logs/"] + Features --> Inspector["inspector/"] + Features --> Snapshots["snapshots/"] + Features --> Terminal["terminal/"] + Features --> Seed["seed/"] + Features --> Config["config/"] + Features --> Dashboard["dashboard/"] + Features --> Timeline["timeline/"] + Features --> Shared["shared/\n(cross-cutting)"] + + S3 --> Layers["lib/\nservices/\nuse-cases/\ncomponents/\ni18n/\ntypes/"] +``` + +### Layer responsibilities + +| Layer | Purpose | +|-------|---------| +| `lib/` | AWS SDK client instantiation and low-level API wrappers (server-side only) | +| `services/` | Business logic that composes `lib/` calls | +| `use-cases/` | Application-level orchestration; called from Server Components or Route Handlers | +| `components/` | React components — Server Components by default, Client Components only when interactivity requires it | +| `i18n/` | Translation keys and locale-specific strings for this feature | +| `types/` | TypeScript types shared within the feature | +| `stores/` | Zustand stores for feature-local client UI state (present in some features) | + +### Adding a new feature slice + +When integrating a new AWS service, create `features/{service}/` and populate the layers you need. Not every layer is required — start with `lib/`, `use-cases/`, and `components/`. Add `services/` when business logic grows. + +--- + +## AWS SDK boundary + +The AWS SDK is **server-side only**. This is an architectural constraint, not a preference. + +**Why**: The browser has no secure path to AWS credentials. Exposing credentials to the client is a security vulnerability. Even for local emulators with dummy credentials, the app follows the same rule to ensure the pattern is safe in any environment. + +**Where**: SDK clients are initialized in `lib/aws/config.ts` (shared config) and `features/{service}/lib/` (per-feature clients). They are imported only in: +- Server Components (files without `"use client"`) +- Route Handlers (`app/api/**`) +- `use-cases/` and `services/` layers (which are called from the above) + +Client Components receive serialized data passed down as props or fetched via Route Handlers. + +--- + +## i18n routing + +The app uses Next.js App Router's locale-based routing. All pages live under `app/[lang]/`: + +``` +app/ +└── [lang]/ + ├── page.tsx ← locale-aware home + ├── s3/page.tsx + ├── sqs/page.tsx + └── ... +``` + +- Supported locales are defined in the shared i18n configuration. +- The `[lang]` segment is resolved at request time by the middleware. +- Fallback locale is `en` — requests without a locale prefix are redirected. +- Translation strings live in `features/{service}/i18n/` (feature-scoped) and `features/shared/i18n/` (global strings). + +--- + +## State management + +**Zustand** is used for client UI state. Two placement rules apply: + +| Store location | Use for | +|---------------|---------| +| `features/shared/stores/` | UI state shared across features (e.g. mobile nav open/close, active profile) | +| `features/{service}/stores/` | Feature-local state (e.g. CloudWatch Logs polling state, Inspector buffer) | + +Server state (data from AWS) is NOT stored in Zustand. It is fetched server-side and passed as props or streamed via RSC. + +--- + +## Key patterns + +- **RSC-first**: all components are Server Components by default. Add `"use client"` only when you need browser APIs, event handlers, or hooks. +- **Server Actions for mutations**: form submissions and write operations use Next.js Server Actions rather than client-side fetch calls. +- **Responsive design tokens**: Tailwind v4 theme tokens are defined in `app/globals.css` (`@theme inline`). Do not add custom breakpoint config files — extend the theme there. +- **Docker output**: the app builds as a Next.js standalone output, suitable for the published Docker image. + +--- + +## Cross-cutting features + +Some features are not AWS-service-specific but span the whole application: + +| Feature | Purpose | +|---------|---------| +| `inspector` | Request/response inspector for AWS SDK calls; buffer managed server-side | +| `snapshots` | Save and restore AWS resource state | +| `terminal` | Embedded terminal for running CLI commands against the emulator | +| `seed` | Preset data seeding for LocalStack / Floci environments | +| `config` | Application configuration management | +| `timeline` | Event timeline across AWS services | +| `shared` | Common components, hooks, stores, utilities, and i18n strings | diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..b7b46a3 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,127 @@ +# Contributing + +Thanks for contributing to this project! This guide covers the development workflow, commit conventions, testing requirements, and PR guidelines. + +--- + +## Prerequisites + +Follow [Getting Started](./getting-started.md) first to get a working local environment. Come back here once `pnpm dev` and `pnpm test` both run without errors. + +--- + +## Local setup + +1. Fork the repository on GitHub. +2. Clone your fork and add the upstream remote: + ```bash + git clone https://github.com//aws-local-ui.git + cd aws-local-ui + git remote add upstream https://github.com/sisques-labs/aws-local-ui.git + ``` +3. Install dependencies: + ```bash + pnpm install + ``` +4. Branch from `dev` — never from `main`: + ```bash + git checkout dev + git pull upstream dev + git checkout -b feat/my-feature + ``` + +--- + +## Branching strategy + +| Branch pattern | Purpose | +|---------------|---------| +| `feat/*` | New features | +| `fix/*` | Bug fixes | +| `chore/*` | Maintenance, dependency updates, docs | + +All pull requests target `dev`. The `dev` branch merges into `main` on release. Never open a PR directly against `main`. + +--- + +## Commit conventions + +This project uses [Conventional Commits](https://www.conventionalcommits.org/). + +**Format**: `(): ` + +**Examples**: +``` +feat(s3): add bucket creation dialog +fix(logs): prevent duplicate polling on reconnect +chore(deps): update vitest to 3.x +docs(contributing): add responsive UI section +``` + +**Rules**: +- Use the imperative mood in the subject ("add", not "added" or "adds") +- Keep the subject under 72 characters +- Reference related issues in the body when relevant: `Closes #123` +- **`Co-Authored-By` and AI attribution lines are prohibited** — do not add them to any commit message + +--- + +## Testing + +This project uses **Vitest** in strict TDD mode. + +```bash +pnpm test # run all tests +pnpm test --ui # open Vitest UI +``` + +Rules: +- All tests must pass before opening a PR (`pnpm test` exits 0) +- New features and bug fixes require corresponding tests +- Tests live next to the files they test (e.g. `lib/aws/config.test.ts` alongside `lib/aws/config.ts`) +- Do not add `it.skip` or `test.skip` without a linked issue explaining why + +--- + +## Linting and formatting + +```bash +pnpm lint # ESLint +pnpm format # Prettier +``` + +Both run automatically on staged files via husky + lint-staged on commit. Fix any errors before pushing. + +--- + +## Responsive UI requirements + +All new screens and components must work from narrow mobile (~375px) up to desktop: + +- **Minimum viewport**: 375px — test at this width before opening a PR +- **Touch targets**: primary interactive elements must be at least **44×44px** (`min-h-11 min-w-11` before any `md:` override) +- **Layout pattern**: prefer `flex-col` with `sm:` or `md:` row layouts for toolbars +- **Table columns**: hide non-essential columns below `sm:` breakpoint +- **Theme tokens**: Tailwind v4 tokens are defined in `app/globals.css` (`@theme inline`) — do not add custom breakpoint config files + +--- + +## Next.js conventions + +Before writing any Next.js code, **read the relevant guide** in `node_modules/next/dist/docs/`. This version of Next.js has breaking changes — APIs and conventions may differ from your training data or prior experience. Heed deprecation notices. + +--- + +## PR guidelines + +- Keep PRs focused and small — aim for ~400 changed lines or fewer +- Use a descriptive title following the Conventional Commits format +- Link related issues in the PR description (`Closes #109`) +- Fill in the PR template if one is present +- Do not merge your own PRs — wait for at least one review + +--- + +## AI agent collaboration + +If you are an AI agent contributing to this project, [AGENTS.md](../AGENTS.md) is the authoritative source for conventions that apply to you — including tool use, file structure, state management, and testing rules. This guide summarizes conventions for human contributors; `AGENTS.md` covers agent-specific behavior in full detail. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..3f84e9c --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,88 @@ +# Getting Started + +This guide takes you from a clean clone to a running local development environment. + +--- + +## Prerequisites + +Before you begin, make sure you have the following installed: + +- **Node.js 24** (hard requirement — earlier versions are not supported) +- **pnpm** — the only package manager used in this project (`npm` and `yarn` are not supported) +- **Docker** — required to run a local AWS emulator (LocalStack or Floci) +- A running local AWS emulator on port `4566` — see [LocalStack / Floci Setup](./localstack-setup.md) + +> **Tip**: Use [nvm](https://github.com/nvm-sh/nvm) or [fnm](https://github.com/Schniz/fnm) to manage Node versions. Run `node -v` to confirm you are on Node 24. + +--- + +## 1. Clone the repository + +```bash +git clone https://github.com/sisques-labs/aws-local-ui.git +cd aws-local-ui +``` + +--- + +## 2. Install dependencies + +```bash +pnpm install +``` + +> Do not use `npm install` or `yarn install`. The lockfile is `pnpm-lock.yaml` and mixing package managers will cause dependency inconsistencies. + +--- + +## 3. Configure environment variables + +> **WARNING**: Set `AWS_ENDPOINT_URL` BEFORE running the dev server. Without it, the app cannot reach your local AWS emulator and all service panels will show connection errors. + +Create a `.env.local` file at the project root (or export the variables in your shell): + +```bash +# Required — point the server-side AWS SDK at your local emulator +AWS_ENDPOINT_URL=http://localhost:4566 + +# Required for browser-side error hints +NEXT_PUBLIC_AWS_ENDPOINT_URL=http://localhost:4566 + +# Standard LocalStack / Floci dummy credentials +AWS_DEFAULT_REGION=us-east-1 +AWS_ACCESS_KEY_ID=test +AWS_SECRET_ACCESS_KEY=test +``` + +See [LocalStack / Floci Setup](./localstack-setup.md) for instructions on starting the emulator and understanding the full endpoint precedence rules. + +--- + +## 4. Start the development server + +```bash +pnpm dev +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser. The app uses i18n routing — the default entry point is `http://localhost:3000/en` (or whichever locale is configured as the fallback). + +You should see the dashboard with at least one AWS service panel (S3, SQS, DynamoDB, Lambda, SNS, or CloudWatch Logs) without connection errors. + +--- + +## 5. Run the tests + +```bash +pnpm test +``` + +This project follows **strict TDD with Vitest**. All tests must pass before opening a pull request. See [AGENTS.md](../AGENTS.md) for the full testing conventions. + +--- + +## Next steps + +- **Set up your local AWS emulator** → [LocalStack / Floci Setup](./localstack-setup.md) +- **Understand the codebase layout** → [Architecture](./architecture.md) +- **Make a contribution** → [Contributing](./contributing.md) diff --git a/docs/localstack-setup.md b/docs/localstack-setup.md new file mode 100644 index 0000000..8cb22ce --- /dev/null +++ b/docs/localstack-setup.md @@ -0,0 +1,139 @@ +# LocalStack / Floci Setup + +This guide covers how to run a local AWS emulator so the dashboard can operate without a real AWS account. + +Two emulators are supported: **Floci** (recommended) and **LocalStack** (alternative). Both expose the same endpoint (`http://localhost:4566`) so the rest of the configuration is identical. + +--- + +## Option 1 — Floci (recommended) + +[Floci](https://github.com/floci-io/floci) is a free, MIT-licensed AWS emulator with no authentication token required. It supports 52+ AWS services and uses real Docker backends for Lambda, RDS, ECS, and EKS. + +> Floci was created because LocalStack froze free-tier security updates in March 2026. Floci is the recommended emulator for this project. + +### Docker Compose + +Create a `docker-compose.yml` in a local working directory (do **not** commit it to this repo — it is intentionally absent): + +```yaml +services: + floci: + image: floci/floci:latest + ports: + - "4566:4566" +``` + +Start it: + +```bash +docker compose up -d +``` + +### CLI (alternative) + +If you have the Floci CLI installed: + +```bash +floci start +eval $(floci env) # exports AWS_* env vars automatically +``` + +See [Floci's official documentation](https://github.com/floci-io/floci) for CLI install instructions and the full list of supported services. + +### Environment variables + +```bash +AWS_ENDPOINT_URL=http://localhost:4566 +AWS_DEFAULT_REGION=us-east-1 +AWS_ACCESS_KEY_ID=test +AWS_SECRET_ACCESS_KEY=test +``` + +--- + +## Option 2 — LocalStack + +[LocalStack](https://www.localstack.cloud/) is the original local AWS emulator. Latest features require an auth token. + +### Docker Compose + +```yaml +services: + localstack: + image: localstack/localstack + ports: + - "4566:4566" + environment: + - SERVICES=s3,sqs,dynamodb,lambda,sns,logs +``` + +Start it: + +```bash +docker compose up -d +``` + +> Do **not** pin a specific image version — use `latest stable`. See [LocalStack's official Docker Hub page](https://hub.docker.com/r/localstack/localstack) for current release notes. + +### Environment variables + +Same as Floci: + +```bash +AWS_ENDPOINT_URL=http://localhost:4566 +AWS_DEFAULT_REGION=us-east-1 +AWS_ACCESS_KEY_ID=test +AWS_SECRET_ACCESS_KEY=test +``` + +--- + +## Environment configuration + +There are two ways to configure the endpoint: + +### A) Environment variables (recommended for local dev) + +Set `AWS_ENDPOINT_URL` in your `.env.local` file or shell before running `pnpm dev`. See [Getting Started](./getting-started.md) for the full variable list. + +### B) In-app Settings UI + +The dashboard has a **Settings** page that lets you configure the endpoint at runtime. The value is stored in an **httpOnly cookie**, which means it is sent with every server-side request but is not accessible to client-side JavaScript. + +--- + +## Endpoint precedence + +The app resolves the active endpoint in this order (first match wins): + +1. **Active profile** — profile selected in the Settings UI +2. **Region-override cookie** — set via the Settings UI region picker +3. **Endpoint-override cookie** — set via the Settings UI endpoint field +4. **`AWS_ENDPOINT_URL` environment variable** — fallback for local dev + +--- + +## Verifying connectivity + +Once your emulator is running, confirm it is reachable: + +```bash +curl -s http://localhost:4566/_localstack/health | head -5 +# or for Floci: +curl -s http://localhost:4566/ +``` + +You should receive a JSON response. If you get a connection-refused error, the emulator is not running — see Troubleshooting below. + +--- + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---------|-------------|-----| +| `Connection refused` on port 4566 | Emulator not running | Run `docker compose up -d` and check `docker ps` | +| Dashboard shows "endpoint not configured" | `AWS_ENDPOINT_URL` not set | Add it to `.env.local` and restart `pnpm dev` | +| Services return 404 | Wrong port or emulator | Confirm `docker ps` shows port `4566` mapped | +| CORS errors in browser console | Browser making direct AWS calls | All AWS SDK calls must be server-side only — see [Architecture](./architecture.md) | +| Missing `AWS_ENDPOINT_URL` | Env var not exported | Confirm with `echo $AWS_ENDPOINT_URL` before running `pnpm dev` | From ea545d56f733e3636388719ca044b81515ff983f Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:27:18 +0200 Subject: [PATCH 07/12] docs(readme): replace Next.js boilerplate with project description --- README.md | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index caab96b..c02c18e 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,33 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# Loopback -## Getting Started +A local operator dashboard for AWS services running on your machine — built for teams using [LocalStack](https://www.localstack.cloud/) or [Floci](https://github.com/floci-io/floci). -First, run the development server: +Loopback gives you a visual interface to browse, inspect, and interact with your local AWS resources without leaving the browser. No credentials, no AWS console, no context switching. -```bash -pnpm dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +## Services -## Learn More +| Service | What you can do | +|---------|----------------| +| S3 | Browse buckets, upload/download objects | +| SQS | View queues, send and consume messages | +| SNS | Manage topics and subscriptions | +| DynamoDB | Browse tables, query and edit items | +| Lambda | List functions, invoke with custom payloads | +| CloudWatch Logs | Stream and filter log groups | -To learn more about Next.js, take a look at the following resources: +Additional tools: SDK Request Inspector, Snapshots, Timeline, Terminal, Seed, Config. -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## Quick start -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +See [Getting Started](./docs/getting-started.md) for full prerequisites and setup steps. -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +```bash +pnpm install +export AWS_ENDPOINT_URL=http://localhost:4566 +pnpm dev +``` -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +Open [http://localhost:3000](http://localhost:3000). ## Docker @@ -118,4 +118,3 @@ bumps `package.json`, tags the commit, builds multi-arch, and publishes to both - [LocalStack / Floci Setup](./docs/localstack-setup.md) — local AWS emulation with Docker - [Architecture](./docs/architecture.md) — feature-sliced layout, RSC, i18n routing - [Contributing](./docs/contributing.md) — workflow, commit conventions, PR guidelines - From 0389523f2b32d531c142092698b892fa59a0f45a Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:48 +0200 Subject: [PATCH 08/12] fix(i18n): migrate es translations from voseo to castellano in shared and dashboard --- features/dashboard/i18n/es.ts | 2 +- features/shared/i18n/es.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/features/dashboard/i18n/es.ts b/features/dashboard/i18n/es.ts index 0260ca8..540d53c 100644 --- a/features/dashboard/i18n/es.ts +++ b/features/dashboard/i18n/es.ts @@ -36,7 +36,7 @@ const dict = { degraded: "Degradado", unreachable: "No disponible", }, - hint: "Verificá que LocalStack esté corriendo", + hint: "Verifica que LocalStack esté corriendo", columns: { service: "Servicio", status: "Estado", diff --git a/features/shared/i18n/es.ts b/features/shared/i18n/es.ts index 10e39bc..1daa5a0 100644 --- a/features/shared/i18n/es.ts +++ b/features/shared/i18n/es.ts @@ -30,13 +30,13 @@ const dict = { endpointInvalidUrl: "Debe ser una URL absoluta válida", regionSectionTitle: "Región de AWS", regionInputLabel: "Región", - regionDescription: "Seleccioná la región de AWS para tu entorno local", - regionPlaceholder: "Seleccioná una región", + regionDescription: "Selecciona la región de AWS para tu entorno local", + regionPlaceholder: "Selecciona una región", regionSave: "Guardar", regionSaving: "Guardando…", regionSuccess: "Región actualizada", regionInvalid: "Debe ser una región de AWS válida", - regionProfileActiveInfo: "La región está controlada por el perfil activo. Editá el perfil para cambiarla.", + regionProfileActiveInfo: "La región está controlada por el perfil activo. Edita el perfil para cambiarla.", profilesSectionTitle: "Perfiles de entorno", profilesCounter: "{{count}}/10 perfiles", profileAdd: "Crear perfil", @@ -53,7 +53,7 @@ const dict = { profileActiveBadge: "Activo", profileNameDuplicate: "Ya existe un perfil con ese nombre", profileCapReached: "Se alcanzó el máximo de 10 perfiles", - profileDeleteActive: "Desactivá el perfil antes de eliminarlo", + profileDeleteActive: "Desactiva el perfil antes de eliminarlo", profileNotFound: "Perfil no encontrado", profileInvalidEndpoint: "Debe ser una URL absoluta válida", profileInvalidRegion: "Debe ser una región de AWS válida", @@ -63,7 +63,7 @@ const dict = { profileActivateSuccess: "Perfil activado", profileDeactivateSuccess: "Perfil desactivado", profileEmpty: "Sin perfiles todavía", - profileDeleteConfirm: "¿Estás seguro de que querés eliminar este perfil?", + profileDeleteConfirm: "¿Estás seguro de que quieres eliminar este perfil?", profileExport: "Exportar perfiles", profileImport: "Importar perfiles", profileImportSuccess: "{{count}} perfil(es) importado(s)", From 576702d0e6f73def39027f255b70a4c6a1ad2d24 Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:52 +0200 Subject: [PATCH 09/12] fix(i18n): migrate es translations from voseo to castellano in dynamodb --- features/dynamodb/i18n/es.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/features/dynamodb/i18n/es.ts b/features/dynamodb/i18n/es.ts index e54b255..cc3bdcb 100644 --- a/features/dynamodb/i18n/es.ts +++ b/features/dynamodb/i18n/es.ts @@ -23,7 +23,7 @@ const dict = { createTableDialog: { trigger: "Crear tabla", title: "Crear tabla DynamoDB", - description: "Creá una nueva tabla DynamoDB en LocalStack.", + description: "Crea una nueva tabla DynamoDB en LocalStack.", nameLabel: "Nombre de la tabla", namePlaceholder: "mi-tabla", nameHint: "3–255 caracteres, letras, números, guiones, guiones bajos, puntos", @@ -44,7 +44,7 @@ const dict = { nameInvalid: "El nombre de la tabla debe tener entre 3 y 255 caracteres y contener solo letras, números, guiones, guiones bajos y puntos.", pkNameRequired: "El nombre de la clave de partición es obligatorio.", - pkTypeInvalid: "Seleccioná un tipo de clave de partición válido.", + pkTypeInvalid: "Selecciona un tipo de clave de partición válido.", skIncomplete: "El tipo de clave de ordenamiento es obligatorio cuando se indica un nombre de clave de ordenamiento.", }, @@ -74,9 +74,9 @@ const dict = { trigger: "Insertar ítem", title: "Insertar ítem", description: - 'Insertá o reemplazá un ítem usando JSON nativo. Ejemplo: {"pk": "valor", "count": 1}', + 'Inserta o reemplaza un ítem usando JSON nativo. Ejemplo: {"pk": "valor", "count": 1}', jsonLabel: "JSON del ítem", - jsonHint: "Escribí JSON nativo — no el formato AttributeValue de DynamoDB.", + jsonHint: "Escribe JSON nativo — no el formato AttributeValue de DynamoDB.", jsonPlaceholder: '{"pk": "valor", "sk": "valor-ordenamiento"}', cancel: "Cancelar", submit: "Insertar ítem", @@ -99,18 +99,18 @@ const dict = { resourceNotFound: "La tabla especificada no existe.", validation: "Parámetros de solicitud inválidos.", conditionalCheckFailed: "No se pudo evaluar una condición especificada en la operación.", - limitExceeded: "Demasiadas solicitudes. Intentá de nuevo.", + limitExceeded: "Demasiadas solicitudes. Inténtalo de nuevo.", endpointUnreachable: - "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", }, editItemDialog: { trigger: "Editar", title: "Editar ítem", - description: "Editá los atributos no clave de este ítem. Los campos clave están bloqueados.", + description: "Edita los atributos no clave de este ítem. Los campos clave están bloqueados.", keyFieldsLabel: "Campos clave (bloqueados)", jsonLabel: "JSON del ítem", - jsonHint: "Editá los atributos no clave usando JSON nativo.", + jsonHint: "Edita los atributos no clave usando JSON nativo.", cancel: "Cancelar", submit: "Guardar cambios", saving: "Guardando…", @@ -125,7 +125,7 @@ const dict = { modeQuery: "Consulta", pkLabel: "Valor de clave de partición", pkPlaceholder: "ej. usuario-1", - pkHint: "Ingresá un valor de clave de partición para consultar ítems. La PK es obligatoria.", + pkHint: "Introduce un valor de clave de partición para consultar ítems. La PK es obligatoria.", skLabel: "Valor de clave de ordenamiento (opcional)", skPlaceholder: "ej. pedido-5", search: "Buscar", @@ -146,7 +146,7 @@ const dict = { errors: { connectFailed: "No se pudo conectar a DynamoDB", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", tableNotFound: "Tabla no encontrada", tableNotFoundDetail: "LocalStack no tiene una tabla para esta URL. Puede haberse borrado, reiniciado LocalStack o el enlace estar desactualizado.", @@ -156,7 +156,7 @@ const dict = { seedDialog: { trigger: "Importar datos", title: "Importar datos", - description: "Importá ítems desde un archivo JSON o CSV a esta tabla.", + description: "Importa ítems desde un archivo JSON o CSV a esta tabla.", fileLabel: "Archivo de datos", fileHint: "Acepta .json (array de objetos) o .csv (fila de encabezados + filas de datos). Nota: CSV no soporta campos entre comillas con comas.", @@ -164,13 +164,13 @@ const dict = { overwriteHint: "Desactivar no tiene efecto en la versión actual — los ítems siempre se sobreescriben por clave. Esta opción está reservada para un futuro modo de omisión si ya existe.", fileSizeWarning: - "El archivo supera 500 KB. Subir payloads grandes puede ser lento o fallar. Considerá dividir el archivo.", - errorInvalidFile: "Tipo de archivo inválido. Seleccioná un archivo .json o .csv.", - errorEmptyArray: "El archivo contiene un array vacío. Agregá al menos un ítem.", + "El archivo supera 500 KB. Subir payloads grandes puede ser lento o fallar. Considera dividir el archivo.", + errorInvalidFile: "Tipo de archivo inválido. Selecciona un archivo .json o .csv.", + errorEmptyArray: "El archivo contiene un array vacío. Agrega al menos un ítem.", errorParseJson: - "No se pudo analizar el archivo como JSON. Asegurate de que sea un array JSON válido.", + "No se pudo analizar el archivo como JSON. Asegúrate de que sea un array JSON válido.", errorParseCsv: - "El archivo CSV no tiene filas de datos. Agregá al menos una fila después del encabezado.", + "El archivo CSV no tiene filas de datos. Agrega al menos una fila después del encabezado.", successToast: "Se importaron {count} ítems exitosamente.", errorPartialFail: "{failed} de {total} ítems no se pudieron importar.", importing: "Importando…", From 243ee47d1b60fe3aa31abf9c7171a30b2e40898c Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:55 +0200 Subject: [PATCH 10/12] fix(i18n): migrate es translations from voseo to castellano in lambda --- features/lambda/i18n/es.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/features/lambda/i18n/es.ts b/features/lambda/i18n/es.ts index 19cd952..22c9fe5 100644 --- a/features/lambda/i18n/es.ts +++ b/features/lambda/i18n/es.ts @@ -35,14 +35,14 @@ const dict = { nameLabel: "Nombre de la función", namePlaceholder: "mi-funcion", runtimeLabel: "Runtime", - runtimePlaceholder: "Seleccioná un runtime", + runtimePlaceholder: "Selecciona un runtime", handlerLabel: "Handler", handlerPlaceholder: "index.handler", handlerHint: "El punto de entrada de tu código (ej. index.handler).", descriptionLabel: "Descripción", descriptionPlaceholder: "Descripción opcional", roleLabel: "ARN del rol de ejecución", - roleHint: "Valor por defecto de LocalStack. Reemplazalo con un ARN real al apuntar a AWS.", + roleHint: "Valor por defecto de LocalStack. Reemplázalo con un ARN real al apuntar a AWS.", cancel: "Cancelar", submit: "Crear", creating: "Creando…", @@ -52,14 +52,14 @@ const dict = { required: "El nombre de la función es obligatorio.", tooLong: "El nombre de la función no puede superar los 64 caracteres.", invalidPattern: "El nombre solo puede contener letras, números, guiones y guiones bajos.", - invalidRuntime: "Seleccioná un runtime válido.", + invalidRuntime: "Selecciona un runtime válido.", handlerRequired: "El handler es obligatorio.", roleRequired: "El ARN del rol de ejecución es obligatorio.", }, invokeDialog: { title: "Invocar {functionName}", payloadLabel: "Payload (JSON)", - payloadPlaceholder: "Dejá vacío o ingresá un objeto JSON válido…", + payloadPlaceholder: "Deja vacío o introduce un objeto JSON válido…", cancel: "Cancelar", submit: "Invocar", invoking: "Invocando…", @@ -74,8 +74,8 @@ const dict = { updateCodeDialog: { title: "Actualizar código de la función", fileLabel: "Paquete de despliegue", - fileHint: "Subí un archivo .zip (hasta 50 MB).", - selectFile: "Seleccioná un archivo .zip.", + fileHint: "Sube un archivo .zip (hasta 50 MB).", + selectFile: "Selecciona un archivo .zip.", fileTooLarge: "El archivo supera el límite de 50 MB.", uploading: "Subiendo… {percent}%", finalizing: "Procesando en el servidor…", @@ -118,10 +118,10 @@ const dict = { sdkErrors: { notFound: "La función especificada no existe.", invalidParam: "Parámetro de función inválido.", - tooManyRequests: "Demasiadas solicitudes. Intentá de nuevo.", + tooManyRequests: "Demasiadas solicitudes. Inténtalo de nuevo.", serviceError: "Error del servicio Lambda.", payloadTooLarge: "El payload es demasiado grande.", - endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", codeStorageExceeded: "Se superó el límite de almacenamiento de código de la cuenta.", invalidZip: "El archivo subido no es un paquete de despliegue Lambda válido.", @@ -129,7 +129,7 @@ const dict = { errors: { connectFailed: "No se pudo conectar a Lambda", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", connectFailedDetailGeneric: "No se pudo alcanzar LocalStack desde el servidor de la app. Comprueba que esté en marcha, que AWS_ENDPOINT_URL sea correcto y define NEXT_PUBLIC_AWS_ENDPOINT_URL con la misma URL (necesario para mensajes en el cliente si usas una IP de red local).", functionNotFound: "Función no encontrada", From 3ccb5856b94622cafd1562ba3e05678e37f66521 Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:21:58 +0200 Subject: [PATCH 11/12] fix(i18n): migrate es translations from voseo to castellano in sqs and sns --- features/sns/i18n/es.ts | 6 +++--- features/sqs/i18n/es.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/features/sns/i18n/es.ts b/features/sns/i18n/es.ts index e101d95..b7e0012 100644 --- a/features/sns/i18n/es.ts +++ b/features/sns/i18n/es.ts @@ -34,7 +34,7 @@ const dict = { delete: "Eliminar", deleteTitle: "Eliminar topic", deleteConfirm: - "¿Seguro que querés eliminar {topic}? Esta acción no se puede deshacer.", + "¿Seguro que quieres eliminar {topic}? Esta acción no se puede deshacer.", viewDetail: "Ver detalle", publish: "Publicar mensaje", }, @@ -99,13 +99,13 @@ const dict = { limitExceeded: "Se alcanzó el límite de topics para esta cuenta.", kmsError: "La clave KMS asociada a este topic no está accesible.", notAuthorized: "Sin autorización para realizar esta acción en SNS.", - endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", }, errors: { connectFailed: "No se pudo conectar a SNS", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", connectFailedDetailGeneric: "No se pudo alcanzar LocalStack desde el servidor de la app. Comprueba que esté en marcha, que AWS_ENDPOINT_URL sea correcto y define NEXT_PUBLIC_AWS_ENDPOINT_URL con la misma URL (necesario para mensajes en el cliente si usas una IP de red local).", topicNotFound: "Topic no encontrado", diff --git a/features/sqs/i18n/es.ts b/features/sqs/i18n/es.ts index 43e65e7..1a5e8e1 100644 --- a/features/sqs/i18n/es.ts +++ b/features/sqs/i18n/es.ts @@ -33,7 +33,7 @@ const dict = { createQueueValidation: { required: "El nombre de la cola es obligatorio.", standardCannotEndFifo: - "Las colas estándar no pueden terminar en .fifo. Desmarcá FIFO o usá una cola FIFO.", + "Las colas estándar no pueden terminar en .fifo. Desmarca FIFO o usa una cola FIFO.", nameTooLong: "El nombre debe tener como máximo 80 caracteres (incluido .fifo en colas FIFO).", fifoMustEndFifo: "Los nombres de cola FIFO deben terminar en .fifo.", @@ -47,7 +47,7 @@ const dict = { delete: "Eliminar", deleteTitle: "Eliminar cola", deleteConfirm: - "¿Seguro que querés eliminar {queue}? Los mensajes de la cola se perderán. Esta acción no se puede deshacer.", + "¿Seguro que quieres eliminar {queue}? Los mensajes de la cola se perderán. Esta acción no se puede deshacer.", viewDetail: "Ver detalle", }, queueDetail: { @@ -108,15 +108,15 @@ const dict = { notFound: "La cola especificada no existe.", limitExceeded: "Se alcanzó el límite de colas para esta cuenta.", notAuthorized: "Sin autorización para realizar esta acción en SQS.", - endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegurate de que esté en ejecución.", + endpointUnreachable: "No se puede conectar a LocalStack en {endpoint}. Asegúrate de que esté en ejecución.", unknown: "Ocurrió un error inesperado.", receiptHandleInvalid: - "La ventana de visibilidad de este mensaje expiró — recibí un nuevo lote para reintentar.", + "La ventana de visibilidad de este mensaje expiró — recibe un nuevo lote para reintentar.", }, errors: { connectFailed: "No se pudo conectar a SQS", connectFailedDetail: - "No se pudo alcanzar {endpoint}. Asegurate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", + "No se pudo alcanzar {endpoint}. Asegúrate de que LocalStack esté en ejecución y de que AWS_ENDPOINT_URL esté configurado correctamente.", connectFailedDetailGeneric: "No se pudo alcanzar LocalStack desde el servidor de la app. Comprueba que esté en marcha, que AWS_ENDPOINT_URL sea correcto y define NEXT_PUBLIC_AWS_ENDPOINT_URL con la misma URL (necesario para mensajes en el cliente si usas una IP de red local).", queueNotFound: "Cola no encontrada", From 3ba8c22a8ad34c2954ae9c6be84fd9104d040675 Mon Sep 17 00:00:00 2001 From: JSisques Date: Sat, 23 May 2026 19:22:01 +0200 Subject: [PATCH 12/12] fix(i18n): migrate es translations from voseo to castellano in logs, seed, timeline, snapshots, inspector --- features/inspector/i18n/es.ts | 57 +++++++++++++++++++++++++++++++++++ features/logs/i18n/es.ts | 2 +- features/seed/i18n/es.ts | 6 ++-- features/snapshots/i18n/es.ts | 4 +-- features/timeline/i18n/es.ts | 2 +- 5 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 features/inspector/i18n/es.ts diff --git a/features/inspector/i18n/es.ts b/features/inspector/i18n/es.ts new file mode 100644 index 0000000..b0aea43 --- /dev/null +++ b/features/inspector/i18n/es.ts @@ -0,0 +1,57 @@ +import type { InspectorDict } from "./en"; +import type { WidenStringLiterals } from "@/features/shared/i18n/widen-literals"; + +type InspectorDictTranslated = WidenStringLiterals; + +const dict = { + title: "Inspector de Solicitudes AWS", + description: "Inspecciona cada llamada al SDK de AWS realizada por Server Actions — filtra, busca y repite.", + toolbar: { + filters: { + service: { + label: "Servicio", + all: "Todos los servicios", + }, + status: { + label: "Estado", + all: "Todos", + success: "Éxito", + error: "Error", + }, + text: { + placeholder: "Buscar operación o payload…", + }, + }, + view: { + label: "Vista", + list: "Lista", + timeline: "Línea de tiempo", + }, + clearBuffer: "Limpiar", + statusPolling: "En vivo", + statusError: "Error", + statusIdle: "Inactivo", + lastUpdated: "Actualizado hace {time}", + }, + empty: { + title: "Sin solicitudes aún", + body: "Las llamadas al SDK de AWS realizadas por Server Actions aparecerán aquí.", + }, + card: { + duration: "{ms}ms", + attempts: "{n} intentos", + retries: "{n} reintentos", + }, + detail: { + title: "Detalle de solicitud", + input: "Entrada", + output: "Salida", + attempts: "Intentos", + duration: "Duración", + timestamp: "Marca de tiempo", + error: "Error", + closeLabel: "Cerrar", + }, +} as const satisfies InspectorDictTranslated; + +export default dict; diff --git a/features/logs/i18n/es.ts b/features/logs/i18n/es.ts index 05ee3aa..e6f5963 100644 --- a/features/logs/i18n/es.ts +++ b/features/logs/i18n/es.ts @@ -5,7 +5,7 @@ type LogsDictTranslated = WidenStringLiterals; const dict = { title: "CloudWatch Logs", - description: "Transmití, filtrá y buscá entradas de log desde el endpoint de AWS configurado.", + description: "Transmite, filtra y busca entradas de log desde el endpoint de AWS configurado.", filters: { service: "Servicio", level: "Nivel", diff --git a/features/seed/i18n/es.ts b/features/seed/i18n/es.ts index fa91258..27d0afd 100644 --- a/features/seed/i18n/es.ts +++ b/features/seed/i18n/es.ts @@ -6,10 +6,10 @@ type SeedDictTranslated = WidenStringLiterals; const dict = { page: { title: "Datos de demo", - description: "Cargá recursos predefinidos en tu entorno de LocalStack o reiniciá todo.", + description: "Carga recursos predefinidos en tu entorno de LocalStack o reinicia todo.", }, presets: { - sectionTitle: "Elegí un preset", + sectionTitle: "Elige un preset", ecommerce: { name: "E-commerce", description: "Buckets S3, colas SQS, tablas DynamoDB, funciones Lambda y topics SNS para una plataforma de e-commerce.", @@ -26,7 +26,7 @@ const dict = { load: { button: "Cargar preset", loading: "Cargando…", - noPresetSelected: "Seleccioná un preset para continuar", + noPresetSelected: "Selecciona un preset para continuar", successTitle: "Preset cargado", errorTitle: "Error al cargar", }, diff --git a/features/snapshots/i18n/es.ts b/features/snapshots/i18n/es.ts index f973a7e..67ac344 100644 --- a/features/snapshots/i18n/es.ts +++ b/features/snapshots/i18n/es.ts @@ -6,7 +6,7 @@ type SnapshotsDictTranslated = WidenStringLiterals; const dict = { page: { title: "Snapshots", - description: "Capturá, exportá, importá y restaurá tu entorno de LocalStack.", + description: "Captura, exporta, importa y restaura tu entorno de LocalStack.", }, create: { sectionTitle: "Crear Snapshot", @@ -34,7 +34,7 @@ const dict = { restoring: "Restaurando…", successTitle: "Restauración completa", errorTitle: "Error al restaurar", - noSnapshot: "No hay snapshot para restaurar. Creá o importá uno primero.", + noSnapshot: "No hay snapshot para restaurar. Crea o importa uno primero.", snapshotInfo: "Snapshot del {{date}} — {{tables}} tablas, {{queues}} colas, {{buckets}} buckets", }, diff --git a/features/timeline/i18n/es.ts b/features/timeline/i18n/es.ts index 84666e7..358434f 100644 --- a/features/timeline/i18n/es.ts +++ b/features/timeline/i18n/es.ts @@ -29,7 +29,7 @@ const dict = { }, empty: { title: "No se encontraron eventos", - body: "No se encontraron eventos para el rango de tiempo seleccionado. Asegurate de que LocalStack esté corriendo y los servicios estén siendo invocados.", + body: "No se encontraron eventos para el rango de tiempo seleccionado. Asegúrate de que LocalStack esté corriendo y los servicios estén siendo invocados.", }, } as const satisfies TimelineDictTranslated;