From 3c0dbb3e3068640e3723caf3c816ddc90d5c35c4 Mon Sep 17 00:00:00 2001 From: Marcos Leandro Bonilla Borges <85517789+Max29xD@users.noreply.github.com> Date: Mon, 11 May 2026 00:09:13 -0400 Subject: [PATCH] feat(i18n): add Spanish localization and improve locale switching (#212) * feat(i18n): add Spanish localization and improve locale switching * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/App.vue | 4 +- src/i18n/index.ts | 22 +- src/i18n/locales/es.ts | 917 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 939 insertions(+), 4 deletions(-) create mode 100644 src/i18n/locales/es.ts diff --git a/src/App.vue b/src/App.vue index 3be4fe3ca..e90b6fb74 100644 --- a/src/App.vue +++ b/src/App.vue @@ -28,7 +28,7 @@ import { useDialogSources } from "@/composables/useDialogSources"; import { useNavigationTargets } from "@/composables/useNavigationTargets"; import { useDataGridActions } from "@/composables/useDataGridActions"; import { useTauriEvents } from "@/composables/useTauriEvents"; -import { setLocale, currentLocale } from "@/i18n"; +import { setLocale, currentLocale, nextLocale } from "@/i18n"; import * as api from "@/lib/api"; import { resolveDefaultDatabase } from "@/lib/defaultDatabase"; import { resolveExecutableSql } from "@/lib/sqlExecutionTarget"; @@ -377,7 +377,7 @@ function changeActiveSchema(schema: string | undefined) { } function toggleLocale() { - setLocale(currentLocale() === "zh-CN" ? "en" : "zh-CN"); + setLocale(nextLocale(currentLocale())); } function openGitHub() { openUrl("https://github.com/t8y2/dbx"); diff --git a/src/i18n/index.ts b/src/i18n/index.ts index a2f2b80f4..6a968a3cb 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,10 +1,21 @@ import { createI18n } from "vue-i18n"; import en from "./locales/en"; +import es from "./locales/es"; import zhCN from "./locales/zh-CN"; -export type Locale = "en" | "zh-CN"; +export type Locale = "en" | "es" | "zh-CN"; -const savedLocale = (localStorage.getItem("dbx-locale") as Locale) || "zh-CN"; +const supportedLocales: Locale[] = ["en", "es", "zh-CN"]; +const defaultLocale: Locale = "zh-CN"; + +function normalizeLocale(value: string | null): Locale { + if (value && supportedLocales.includes(value as Locale)) { + return value as Locale; + } + return defaultLocale; +} + +const savedLocale = normalizeLocale(localStorage.getItem("dbx-locale")); const i18n = createI18n({ legacy: false, @@ -12,6 +23,7 @@ const i18n = createI18n({ fallbackLocale: "en", messages: { en, + es, "zh-CN": zhCN, }, }); @@ -25,4 +37,10 @@ export function currentLocale(): Locale { return i18n.global.locale.value as Locale; } +export function nextLocale(current: Locale): Locale { + const index = supportedLocales.indexOf(current); + const nextIndex = index === -1 ? 0 : (index + 1) % supportedLocales.length; + return supportedLocales[nextIndex]; +} + export default i18n; diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts new file mode 100644 index 000000000..3e75eeaf0 --- /dev/null +++ b/src/i18n/locales/es.ts @@ -0,0 +1,917 @@ +export default { + app: { + name: "DBX", + }, + auth: { + setupTitle: "Configurar contraseña de acceso", + setupDescription: "Establece una contraseña para proteger tu instancia", + loginDescription: "Herramienta de administración de bases de datos", + newPassword: "Ingresa la nueva contraseña", + confirmPassword: "Confirmar contraseña", + enterPassword: "Ingresa la contraseña de acceso", + passwordMismatch: "Las contraseñas no coinciden", + setPassword: "Establecer contraseña", + login: "Iniciar sesión", + processing: "Procesando...", + loginFailed: "Contraseña incorrecta", + connectFailed: "Error de conexión", + changePassword: "Cambiar contraseña", + oldPassword: "Contraseña actual", + oldPasswordWrong: "La contraseña actual es incorrecta", + passwordChanged: "Contraseña cambiada exitosamente", + changePasswordFailed: "Error al cambiar la contraseña", + changePasswordDescription: "Ingresa tu contraseña actual y elige una nueva", + }, + toolbar: { + newConnection: "Nueva conexión", + newQuery: "Nueva consulta", + execute: "Ejecutar", + executeShortcut: "Ejecutar selección/consulta (Cmd+Enter)", + stopQuery: "Detener consulta", + explainPlan: "Plan de ejecución", + stopExplain: "Detener análisis", + formatSql: "Formatear SQL", + formatSqlFailed: "Error al formatear el SQL", + saveSql: "Guardar en biblioteca SQL", + openSql: "Abrir archivo SQL", + sqlSaved: "SQL guardado", + sqlOpenFailed: "Error al abrir el archivo: {message}", + sqlSaveFailed: "Error al guardar el archivo: {message}", + }, + updates: { + title: "Actualizaciones", + check: "Buscar actualizaciones", + availableTitle: "Actualización disponible", + availableMessage: "DBX {latest} está disponible. Estás usando la versión {current}.", + upToDate: "DBX está actualizado ({version}).", + failed: "Error al buscar actualizaciones: {error}", + rateLimited: + "Las verificaciones de actualizaciones de GitHub están temporalmente limitadas. Puedes abrir la página de lanzamientos para verificar manualmente.", + openRelease: "Abrir lanzamiento", + downloadAndInstall: "Descargar e instalar", + downloading: "Descargando {progress}%", + downloadFailed: "Error al descargar la actualización: {error}", + restart: "Salir y reiniciar", + restartFailed: "Error al reiniciar la aplicación: {error}", + exitAndUpdate: "Salir y actualizar", + reopenHint: "La aplicación se reiniciará para completar la actualización", + }, + sidebar: { + connections: "CONEXIONES", + noConnections: "Sin conexiones", + import: "Importar conexiones", + importDbx: "Importar configuración DBX", + importNavicat: "Importar Navicat NCX", + importDbeaver: "Importar DBeaver", + export: "Exportar conexiones", + showMore: "Mostrar {count} más...", + filterByType: "Filtrar por tipo", + clearFilter: "Limpiar filtro", + }, + savedSql: { + saveToLibrary: "Guardar en biblioteca SQL", + save: "Guardar", + saved: "SQL guardado", + saveFailed: "Error al guardar el SQL: {message}", + fileName: "Nombre de archivo", + folder: "Carpeta", + rootFolder: "Biblioteca SQL", + newFolder: "Nueva carpeta SQL", + newFolderDefault: "Nueva carpeta", + renameFolder: "Renombrar carpeta", + deleteFolder: "Eliminar carpeta", + deleteFolderConfirm: "¿Eliminar “{name}” y los archivos SQL que contiene?", + open: "Abrir SQL", + renameFile: "Renombrar SQL", + deleteFile: "Eliminar SQL", + deleteFileConfirm: "¿Eliminar “{name}”?", + }, + connection: { + title: "Nueva conexión", + name: "Nombre", + namePlaceholder: "Mi base de datos", + type: "Tipo", + host: "Host", + filePath: "Ruta del archivo", + user: "Usuario", + password: "Contraseña", + database: "Base de datos", + databasePlaceholder: "Opcional", + databasePlaceholderWithDefault: "Opcional, por defecto {database}", + serviceName: "Servicio/SID", + driverName: "Nombre del driver", + driverNamePlaceholder: "Nombre del proveedor o entorno", + urlParams: "Parámetros de URL", + connectionUrlOptional: "URL (opcional)", + connectionUrlPlaceholder: "postgresql://usuario:contraseña{'@'}host:5432/db?sslmode=require", + parseConnectionUrl: "Analizar URL de conexión", + parseConnectionUrlApplied: "URL de conexión aplicada", + parseConnectionUrlFailed: "Error al analizar la URL de conexión: {message}", + mode: "Modo de conexión", + modeForm: "Formulario", + searchDatabasePlaceholder: "Buscar tipos de bases de datos", + iconView: "Vista de íconos", + listView: "Vista de lista", + selectedDatabase: "Seleccionada", + noDatabaseMatches: "Sin bases de datos coincidentes", + next: "Siguiente", + back: "Anterior", + basicTab: "Conexión", + test: "Probar", + testing: "Probando...", + copyTestResult: "Copiar resultado de prueba", + saveAndConnect: "Guardar y conectar", + save: "Guardar", + editTitle: "Editar conexión", + testSuccess: "Conexión exitosa", + connecting: "Conectando a {name}...", + connectSuccess: "Conectado a {name}", + connectFailed: "Error de conexión: {message}", + lastError: "Error de conexión", + clearError: "Limpiar error de conexión", + saveFailed: "Error al guardar la conexión: {message}", + loadFailed: "Error al cargar las conexiones guardadas: {message}", + sshTunnel: "Túnel SSH", + sshEnable: "Conectar mediante túnel SSH", + sshHost: "Host SSH", + sshUser: "Usuario SSH", + sshPassword: "Contraseña SSH", + sshPasswordPlaceholder: "Dejar vacío para usar clave", + sshKeyPath: "Ruta de clave", + sshKeyPassphrase: "Frase de clave", + sshKeyPassphrasePlaceholder: "Dejar vacío si la clave no está cifrada", + sshKeyPathBrowse: "Examinar", + sshExposeLan: "Exponer túnel a la red local", + sshConnectTimeout: "Tiempo de espera SSH (segundos)", + jdbcUrl: "URL JDBC", + jdbcUrlPlaceholder: "jdbc:postgresql://localhost:5432/base_de_datos", + jdbcDriverClass: "Clase del driver (opcional)", + jdbcDriverClassPlaceholder: "La mayoría de los drivers se registran automáticamente; usa com.vendor.jdbc.Driver si es necesario", + jdbcDriverPaths: "JARs del driver JDBC", + jdbcDriverSelectPlaceholder: "Seleccionar driver importado", + jdbcDriverPathsPlaceholder: "/ruta/al/driver.jar\n/ruta/a/otro-driver.jar", + jdbcDriverBrowse: "Seleccionar JAR del driver JDBC", + jdbcDocs: "Ver documentación JDBC", + jdbcPluginHint: "Instala primero el plugin JDBC de DBX y luego importa el JAR del driver JDBC del proveedor.", + dmCompatHint: "Requiere el driver ODBC DM8 instalado en el sistema.", + dmDownload: "Descargar desde Dameng", + mongoLegacyHint: + "Para servidores MongoDB más antiguos, ingresa authSource=admin&authMechanism=SCRAM-SHA-1 aquí; agrega directConnection=true solo cuando sea necesario para conexiones directas a instancias independientes.", + compatible: "Compatible", + mainstream: "Popular", + color: "Color", + colorNone: "Sin color", + colorGreen: "Verde", + colorYellow: "Amarillo", + colorOrange: "Naranja", + colorRed: "Rojo", + colorBlue: "Azul", + colorPurple: "Morado", + }, + editor: { + pressToExecute: "Presiona {mod}+Enter para ejecutar", + pressToSaveSql: "Presiona {mod}+S para guardar el SQL", + noDatabase: "Sin base de datos seleccionada", + selectConnection: "Seleccionar conexión", + selectDatabase: "Seleccionar base de datos", + selectSchema: "Seleccionar esquema", + setDefaultDatabase: "Establecer como predeterminada", + defaultDatabase: "Predeterminada", + }, + tabs: { + sql: "SQL", + table: "Tabla", + tableData: "Datos de tabla", + redis: "Redis", + mongo: "Mongo", + objects: "Objetos", + tooltipConnection: "Conexión:", + tooltipDatabase: "Base de datos:", + tooltipTable: "Tabla:", + tooltipCollection: "Colección:", + tooltipSchema: "Esquema:", + resultN: "Resultado {n}", + scrollLeft: "Desplazar pestañas a la izquierda", + scrollRight: "Desplazar pestañas a la derecha", + }, + chart: { + title: "Gráfico", + type: "Tipo", + line: "Línea", + bar: "Barras", + pie: "Circular", + noNumericData: "No hay datos numéricos disponibles para graficar", + }, + grid: { + rows: "{count} filas", + totalRows: "Total {count} filas", + rowsAffected: "{count} filas afectadas", + querySuccess: "Consulta ejecutada exitosamente", + noRows: "Sin datos", + noRowsDescription: "Este conjunto de resultados no tiene registros.", + noSearchResults: "Sin coincidencias", + noSearchResultsDescription: "Intenta con otra palabra clave.", + noFilteredRows: "Ninguna fila coincide con el filtro", + noFilteredRowsDescription: "Ajusta el texto de búsqueda o el filtro de estado de filas.", + copy: "Copiar", + copyCell: "Copiar celda", + copyRow: "Copiar fila (JSON)", + copyRowInsert: "Copiar como INSERT", + copyAll: "Copiar todo (TSV)", + selection: "Selección", + copySelectionTsv: "Copiar selección (TSV)", + copySelectionCsv: "Copiar selección (CSV)", + copySelectionJson: "Copiar selección (JSON)", + copySelectionSql: "Copiar selección como lista SQL IN", + clearSelection: "Limpiar selección", + export: "Exportar", + exportCsv: "Exportar CSV", + exportXlsx: "Exportar XLSX", + exportJson: "Exportar JSON", + exportMarkdown: "Exportar Markdown", + exported: "Exportado", + exportFailed: "Error al exportar: {message}", + filter: "Filtrar", + localFilter: "Filtro local de columna", + localFilterFor: "Filtro local para '{column}'", + searchValues: "Buscar valores...", + value: "Valor", + count: "Conteo", + applyFilter: "Aplicar filtro", + clearLocalFilters: "Limpiar filtros locales de columnas", + moreValues: "{count} valores más, sigue escribiendo para acotar los resultados", + filterByValue: "Filtrar por este valor", + filterExcludeValue: "Excluir este valor", + filterIsNull: "Mostrar valores NULL", + filterIsNotNull: "Mostrar valores no NULL", + clearFilter: "Limpiar filtro", + sort: "Ordenar", + sortAscending: "Orden ascendente", + sortDescending: "Orden descendente", + clearSort: "Limpiar orden", + copied: "¡Copiado!", + pasted: "¡Pegado!", + search: "Buscar...", + searchOrWhere: "Buscar, o ingresar una cláusula WHERE...", + applyWhere: "Aplicar WHERE", + filterRows: "Filtrar filas", + filterAllRows: "Todas las filas", + filterChangedRows: "Modificadas", + page: "Página {page}", + rowsPerPage: "Filas por página", + save: "Guardar", + discard: "Descartar", + dismiss: "Cerrar", + addRow: "Agregar fila", + deleteRow: "Eliminar fila", + restoreRow: "Restaurar fila", + statusClean: "Sin cambios", + statusNew: "Elementos nuevos", + statusEdited: "Elementos modificados", + statusDeleted: "Elementos eliminados", + pendingChanges: "{count} pendientes", + selectedCells: "{count} seleccionadas", + cellDetails: "Detalles de celda", + cellValue: "Valor", + columnName: "Columna", + columnType: "Tipo", + columnComment: "Comentario", + rowNumber: "Fila", + valueLength: "Longitud", + nullValue: "NULL", + noComment: "Sin comentario", + formattedJson: "JSON formateado", + copyValue: "Copiar valor", + editValue: "Editar valor", + setNull: "Establecer NULL", + copyColumnName: "Copiar nombre de columna", + copySqlCondition: "Copiar condición SQL", + transpose: "Transponer fila", + rowsPerPageShort: " filas", + columnDetails: "Detalles de columna", + queryError: "Error de consulta", + refresh: "Actualizar", + commit: "Confirmar", + rollback: "Revertir", + transactionActive: "Editando", + sortUnsupported: "Este SQL no admite ordenamiento del resultado completo. Intenta con una consulta SELECT simple.", + truncatedHint: "Los resultados están limitados a 10.000 filas. Usa LIMIT/OFFSET en tu consulta para paginar.", + }, + welcome: { + title: "Espacio de trabajo de base de datos", + subtitle: "Selecciona una conexión a la izquierda para explorar el esquema, o abre una pestaña de consulta directamente.", + connections: "Conexiones", + connected: "Conectado", + databaseTypes: "Tipos de bases de datos", + quickConnections: "Inicio rápido", + quickConnectionsHint: "Haz clic en una conexión para abrir una pestaña de consulta.", + shortcuts: "Atajos", + shortcutsHint: "Inicia la siguiente acción desde aquí.", + tip: "También puedes expandir las conexiones a la izquierda y hacer clic derecho en bases de datos o tablas para más opciones.", + tipSidebar: "Haz clic en una tabla a la izquierda para ver los datos", + tipExecute: "para ejecutar la consulta", + fileOpened: "Abierto {name}", + mcpTitle: "Integración con agentes de IA", + mcpDescription: + "Además del asistente de IA integrado, puedes usar Claude Code, Cursor y otros agentes de programación para consultar tus bases de datos mediante MCP.", + mcpLearnMore: "Más información", + }, + common: { + language: "Idioma", + loading: "Cargando...", + stopping: "Deteniendo...", + close: "Cerrar", + }, + explain: { + title: "Plan de ejecución", + tree: "Árbol", + summary: "Resumen", + running: "Leyendo plan de ejecución...", + empty: "Sin plan de ejecución", + nodeCount: "{count} nodos", + node: "Nodo", + relation: "Tabla", + index: "Índice", + cost: "Costo", + rows: "Filas", + details: "Detalles", + unsupported: "El plan de ejecución aún no está disponible para esta base de datos", + emptySql: "No hay SQL para analizar", + unsafe: "Esta versión solo analiza sentencias SELECT / WITH / TABLE / VALUES", + }, + lineage: { + title: "Linaje de campo", + open: "Ver linaje de campo", + loading: "Leyendo metadatos {done}/{total}", + empty: "Sin linaje relacionado", + noFiltered: "Ningún resultado coincide con los filtros actuales", + targetField: "Campo actual", + searchPlaceholder: "Buscar tablas, campos, vistas o fragmentos SQL...", + showing: "Mostrando {shown}/{total} resultados", + all: "Todos", + certain: "Confirmado", + likely: "Probable", + possible: "Posible", + queryHistory: "Historial de consultas", + openTable: "Abrir tabla", + copy: "Copiar nombre", + copied: "Copiado", + kind: { + foreignKey: "Clave foránea", + viewReference: "Vista", + historyReference: "Historial SQL", + sameName: "Mismo nombre", + }, + description: { + foreignKeyIncoming: "{target} apunta al campo actual mediante una clave foránea. Esta es una dependencia verificada.", + foreignKeyOutgoing: "El campo actual referencia a {target} mediante una clave foránea. Esta es una dependencia verificada.", + viewLikely: "La definición de la vista menciona tanto la tabla como el campo destino, lo que generalmente indica una dependencia de consulta.", + viewPossible: + "La definición de la vista menciona un campo con el mismo nombre pero no la tabla destino, por lo que requiere confirmación.", + historyLikely: + "Una sentencia SQL del historial menciona tanto la tabla como el campo destino. Úsala como contexto de análisis de impacto.", + historyPossible: "Una sentencia SQL del historial menciona un campo con el mismo nombre. Puede estar relacionado pero requiere verificación.", + sameName: + "Otra tabla tiene un campo con el mismo nombre. Puede compartir significado de negocio, pero no es una dependencia verificada en la base de datos.", + }, + cancel: "Cancelar", + refresh: "Analizar de nuevo", + }, + connectionGroup: { + createGroup: "Nuevo grupo", + groupNamePlaceholder: "Nombre del grupo", + renameGroup: "Renombrar grupo", + deleteGroup: "Eliminar grupo", + moveToGroup: "Mover al grupo", + moveToNewGroup: "Mover a nuevo grupo", + ungrouped: "Sin grupo", + newGroup: "Nuevo grupo...", + newGroupDefault: "Nuevo grupo", + deleteGroupConfirmTitle: "Eliminar grupo", + deleteGroupConfirmMessage: '¿Eliminar el grupo "{name}"? Las conexiones dentro serán movidas al nivel superior.', + }, + databaseSearch: { + title: "Buscar en base de datos", + open: "Buscar en base de datos", + keyword: "Palabra clave", + keywordPlaceholder: "Ingresa un ID de usuario, correo, número de orden...", + limitPerTable: "Por tabla", + search: "Buscar", + searching: "Buscando", + stop: "Detener", + hint: "Analiza las tablas regulares de la base de datos o esquema actual. Se omiten las columnas binarias y cada tabla tiene un límite para evitar cargar demasiados datos a la vez.", + loadingTables: "Leyendo lista de tablas...", + progress: "Analizadas {done}/{total} tablas", + resultCount: "{count} coincidencias", + limitedTables: "Esquema grande detectado; solo se analizan las primeras {count} tablas", + results: "Resultados", + openResult: "Abrir", + waiting: "Esperando coincidencias...", + noResults: "Sin coincidencias aún", + errors: "Algunas tablas fallaron", + }, + configExport: { + passphraseTitle: "Cifrar y exportar", + passphraseImportTitle: "Descifrar e importar", + passphraseExportHint: + "Para incluir las contraseñas de conexión en la exportación, define una frase de paso para el cifrado. Necesitarás la misma frase para importar.", + passphraseImportHint: "Este archivo está cifrado. Ingresa la frase de paso para descifrarlo e importarlo.", + passphrase: "Frase de paso", + passphraseConfirm: "Confirmar frase de paso", + passphrasePlaceholder: "Ingresa la frase de paso", + passphraseConfirmPlaceholder: "Vuelve a ingresar la frase de paso", + passphraseRequired: "La frase de paso es obligatoria", + passphraseMismatch: "Las frases de paso no coinciden", + passphraseTooShort: "La frase de paso debe tener al menos 4 caracteres", + exportEncrypted: "Exportar", + decryptImport: "Descifrar e importar", + wrongPassphrase: "Frase de paso incorrecta o archivo corrupto", + exportSuccess: "Conexiones exportadas exitosamente", + importSuccess: "Se importaron {count} conexión(es)", + importNavicatSuccess: + "Se importaron {count} conexión(es) de Navicat. Completa la contraseña de las conexiones que aún la tengan vacía antes de probarlas.", + importDbeaverSuccess: + "Se importaron {count} conexión(es) de DBeaver. Completa la contraseña de las conexiones que aún la tengan vacía antes de probarlas.", + importNone: "No hay nuevas conexiones para importar", + importLayoutConfirm: "El archivo importado contiene grupos de conexiones. ¿Deseas aplicarlos?", + importLayoutTitle: "Importar grupos", + importLayoutApply: "Aplicar", + }, + ai: { + placeholder: "Describe tu consulta en lenguaje natural...", + settings: "Configuración de IA", + provider: "Proveedor", + run: "Ejecutar", + readingSchema: "Leyendo esquema", + noConnection: "No hay una conexión disponible para esta pestaña", + noSql: "No hay SQL para procesar", + sqlPreview: "Vista previa de SQL", + copySql: "Copiar SQL", + executeSql: "Ejecutar SQL", + copyAll: "Copiar todo", + copied: "Copiado", + replace: "Reemplazar en el editor", + append: "Agregar al editor", + apply: "Aplicar al editor", + clear: "Limpiar chat", + welcome: "Dime qué quieres consultar y escribiré el SQL", + newChat: "Nuevo chat", + thinking: "Procesando...", + reasoningProcess: "Proceso de razonamiento", + stopGenerating: "Detener generación", + fixWithAi: "Corregir con IA", + truncated: "Contexto truncado", + contextSummary: "{database} · {tables} tablas", + autoSqlBlocked: "El SQL generado por la IA parecía demasiado riesgoso para ejecutarse automáticamente. Revísalo manualmente antes de ejecutar.", + settingsHint: + "La configuración se almacena en el directorio de datos local de la aplicación. Las solicitudes son enviadas por el backend de Tauri en lugar de directamente desde el frontend.", + actions: { + generate: "Generar SQL", + explain: "Explicar SQL", + optimize: "Optimizar SQL", + fix: "Corregir error", + convert: "Convertir dialecto", + sampleData: "Datos de ejemplo", + }, + placeholders: { + generate: "Describe lo que quieres consultar, p. ej. pedidos por usuario en los últimos 7 días", + explain: "Opcional: indica qué quieres entender", + optimize: "Opcional: indica un objetivo, p. ej. reducir los escaneos completos de tabla", + fix: "Pega el error o describe el resultado esperado", + convert: "P. ej. convertir a PostgreSQL / MySQL / SQL Server", + sampleData: "Describe los datos de ejemplo o sentencias de prueba a generar", + }, + }, + contextMenu: { + openConnection: "Abrir conexión", + closeConnection: "Cerrar conexión", + deleteConnection: "Eliminar conexión", + confirmDeleteTitle: "Eliminar conexión", + confirmDeleteMessage: '¿Estás seguro de que deseas eliminar "{name}"? Esta acción no se puede deshacer.', + editConnection: "Editar conexión", + duplicateConnection: "Duplicar conexión", + newQuery: "Nueva consulta", + openObjectBrowser: "Explorar objetos", + viewData: "Ver datos", + editStructure: "Editar estructura", + refreshChildren: "Actualizar", + pin: "Fijar", + unpin: "Desfijar", + refreshTab: "Actualizar datos", + closeTab: "Cerrar pestaña", + closeOtherTabs: "Cerrar otras pestañas", + closeAllTabs: "Cerrar todas las pestañas", + copyName: "Copiar nombre", + setDefaultDatabase: "Establecer como base de datos predeterminada", + clearDefaultDatabase: "Limpiar base de datos predeterminada", + exportDatabase: "Exportar base de datos", + exportDatabaseSuccess: "Se exportaron {count} tablas, hasta {limit} filas cada una", + exportDatabaseFailed: "Error al exportar la base de datos: {message}", + importData: "Importar datos", + exportData: "Exportar datos", + exportStructure: "Exportar estructura", + createTable: "Crear tabla", + dropTable: "Eliminar tabla", + emptyTable: "Vaciar tabla", + truncateTable: "Truncar tabla", + duplicateStructure: "Duplicar estructura", + confirmDropTableTitle: "Eliminar tabla", + confirmDropTableMessage: + '¿Estás seguro de que deseas eliminar "{name}"? Esto borrará permanentemente la tabla y todos sus datos.', + confirmEmptyTableTitle: "Vaciar tabla", + confirmEmptyTableMessage: '¿Estás seguro de que deseas eliminar todos los datos de "{name}"?', + confirmTruncateTableTitle: "Truncar tabla", + confirmTruncateTableMessage: '¿Estás seguro de que deseas truncar "{name}"? Esto eliminará todas las filas.', + dropTableSuccess: 'Tabla "{name}" eliminada', + emptyTableSuccess: 'Todos los datos eliminados de "{name}"', + truncateTableSuccess: 'Tabla "{name}" truncada', + duplicateStructureSuccess: 'Estructura de tabla duplicada como "{name}"', + tableOperationFailed: "Error en la operación: {message}", + duplicateNameTitle: "Duplicar estructura", + duplicateNamePlaceholder: "Nombre de la nueva tabla", + createDatabase: "Crear base de datos", + dropDatabase: "Eliminar base de datos", + confirmDropDatabaseTitle: "Eliminar base de datos", + confirmDropDatabaseMessage: + '¿Estás seguro de que deseas eliminar la base de datos "{name}"? Esto borrará permanentemente la base de datos y todos sus datos.', + createDatabaseSuccess: 'Base de datos "{name}" creada', + dropDatabaseSuccess: 'Base de datos "{name}" eliminada', + createDatabaseNamePlaceholder: "Nombre de la base de datos", + createSchema: "Crear esquema", + dropSchema: "Eliminar esquema", + confirmDropSchemaTitle: "Eliminar esquema", + confirmDropSchemaMessage: + '¿Estás seguro de que deseas eliminar el esquema "{name}"? Esto borrará permanentemente el esquema y todos sus objetos.', + createSchemaSuccess: 'Esquema "{name}" creado', + dropSchemaSuccess: 'Esquema "{name}" eliminado', + createSchemaNamePlaceholder: "Nombre del esquema", + }, + tree: { + savedSql: "Biblioteca SQL", + columns: "Columnas", + indexes: "Índices", + foreignKeys: "Claves foráneas", + triggers: "Disparadores", + objectBrowser: "Explorar en el navegador de objetos ({count})", + }, + objects: { + all: "Todos", + tables: "Tablas", + views: "Vistas", + procedures: "Procedimientos", + functions: "Funciones", + table: "Tabla", + view: "Vista", + procedure: "Procedimiento", + function: "Función", + name: "Nombre", + type: "Tipo", + schemaColumn: "Esquema", + comment: "Comentario", + loadingSchemas: "Cargando esquemas...", + schema: "Esquema", + search: "Buscar tablas, vistas, funciones o procedimientos...", + loading: "Cargando objetos...", + empty: "No se encontraron objetos", + }, + structureEditor: { + title: "Editar estructura de tabla", + createTitle: "Crear tabla", + tableName: "Nombre de tabla", + refresh: "Actualizar estructura", + columns: "Columnas", + indexes: "Índices", + foreignKeys: "Claves foráneas", + triggers: "Disparadores", + addColumn: "Agregar columna", + addIndex: "Agregar índice", + columnName: "Columna", + dataType: "Tipo", + nullable: "Admite nulos", + defaultValue: "Valor por defecto", + comment: "Comentario", + actions: "Acciones", + indexName: "Índice", + indexColumns: "Columnas", + indexColumnsPlaceholder: "Seleccionar columnas...", + unique: "Único", + indexType: "Tipo", + includedColumns: "Incluidas", + includedColumnsPlaceholder: "Seleccionar columnas...", + filter: "Filtro", + primary: "Primaria", + drop: "Eliminar", + restore: "Restaurar", + remove: "Quitar", + yes: "Sí", + no: "No", + emptyReadonly: "Sin registros", + sqlPreview: "Vista previa de SQL", + noChanges: "Sin cambios", + apply: "Aplicar cambios", + ready: "Listo", + saved: "Estructura de tabla actualizada", + }, + diagram: { + title: "Diagrama de relaciones", + open: "Ver diagrama", + selectConnection: "Seleccionar conexión", + selectDatabase: "Seleccionar base de datos", + selectSchema: "Seleccionar esquema", + searchTables: "Buscar tablas, columnas, claves...", + refresh: "Actualizar diagrama", + loading: "Leyendo relaciones...", + loadingProgress: "Leyendo relaciones... {loaded}/{total}", + partialError: "Se omitieron los metadatos de {count} tablas que no pudieron cargarse", + selectTarget: "Selecciona una conexión y base de datos", + empty: "No hay tablas para mostrar", + noMatches: "No hay tablas coincidentes", + tablesCount: "{count} tablas", + relationshipsCount: "{count} relaciones", + tableMode: "Vista de tabla", + engineeringMode: "ER de ingeniería", + allTables: "Todas las tablas", + relatedTables: "Tablas relacionadas", + moreColumns: "+ {count} columnas", + exportSvg: "Exportar SVG", + exportedSvg: "SVG exportado", + exportSvgFailed: "Error al exportar SVG: {message}", + zoomIn: "Acercar", + zoomOut: "Alejar", + resetLayout: "Restablecer diseño", + }, + redis: { + selectKey: "Selecciona una clave para ver su valor", + noKeys: "No se encontraron claves", + pattern: "patrón (p. ej. usuario:*)", + keys: "{count} claves", + loadingKeys: "Cargando claves...", + loadMoreKeys: "Cargar más claves", + items: "{count} elementos", + fields: "{count} campos", + members: "{count} miembros", + entries: "{count} entradas", + noExpiry: "sin expiración", + binaryStringReadonlyHint: + "Los valores de cadena binaria se muestran como texto escapado en modo de solo lectura; la edición de bytes sin procesar no está disponible.", + }, + mongo: { + documents: "{count} documentos", + addField: "Agregar campo", + deleteField: "Eliminar campo", + field: "Campo", + value: "Valor", + fieldPlaceholder: "Nombre del campo", + emptyCollection: "Colección vacía", + selectDocument: "Selecciona un documento", + readonlyId: "_id es de solo lectura", + invalidJsonValue: "Esta celda no contiene un valor JSON válido", + duplicateField: "Nombre de campo duplicado: {field}", + edit: "Editar", + documentView: "Vista de documento", + tableView: "Vista de tabla", + }, + history: { + title: "Historial", + search: "Buscar en historial...", + empty: "Sin historial aún", + failed: "Fallida", + success: "Exitosa", + restore: "Restaurar en el editor", + copy: "Copiar SQL", + delete: "Eliminar", + viewDetails: "Ver detalles", + rollback: "Revertir", + rollbackSql: "SQL de reversión", + rollbackConfirm: "¿Ejecutar el SQL de reversión generado para esta entrada del historial?", + rollbackSuccess: "Reversión ejecutada", + rollbackFailed: "Error al revertir: {message}", + confirmDelete: "¿Eliminar esta entrada del historial?", + confirmClear: "¿Limpiar todo el historial?", + filters: { + all: "Todas", + query: "Consultas", + data_change: "Cambios de datos", + schema_change: "Cambios de esquema", + failed: "Fallidas", + }, + kinds: { + query: "Consulta", + data_change: "Cambio de datos", + schema_change: "Cambio de esquema", + import: "Importación", + transfer: "Transferencia", + }, + kindShort: { + query: "SQL", + data_change: "Datos", + schema_change: "DDL", + import: "Import", + transfer: "Mover", + }, + detail: { + kind: "Tipo", + operation: "Operación", + connection: "Conexión", + database: "Base de datos", + target: "Destino", + time: "Hora", + duration: "Duración", + affectedRows: "Filas afectadas", + status: "Estado", + error: "Error", + }, + }, + dangerDialog: { + title: "Operación peligrosa", + message: "Esta sentencia SQL puede modificar o eliminar datos de forma irreversible. ¿Estás seguro de que deseas ejecutarla?", + deleteMessage: "Esta operación de eliminación puede ser irreversible. ¿Continuar?", + deleteConfirm: "Confirmar eliminación", + deleteRowMessage: "Esta fila quedará marcada para eliminación y se borrará de la base de datos al guardar. ¿Continuar?", + deleteRowDetails: "Tabla: {table}", + deleteRowDetailsNoTable: "Fila actual del resultado", + redisKeyDetails: "Clave Redis: {key}", + redisHashFieldDetails: "Hash Redis: {key}\nCampo: {field}", + redisListItemDetails: "Lista Redis: {key}\nÍndice: {index}", + redisSetMemberDetails: "Conjunto Redis: {key}\nMiembro: {member}", + mongoDocumentDetails: "Colección MongoDB: {collection}\nDocumento _id: {id}", + mongoFieldDetails: "Campo: {field}", + mongoArrayItemDetails: "Índice del elemento del arreglo: {index}", + cancel: "Cancelar", + confirm: "Ejecutar", + }, + transfer: { + title: "Transferencia de datos", + source: "Origen", + target: "Destino", + sourceConnection: "Conexión de origen", + sourceDatabase: "Base de datos de origen", + targetConnection: "Conexión de destino", + targetDatabase: "Base de datos de destino", + tables: "Tablas", + selectAll: "Seleccionar todas", + deselectAll: "Deseleccionar todas", + createTable: "Crear tabla si no existe", + transferMode: "Modo de transferencia", + modeAppend: "Agregar (INSERT)", + modeOverwrite: "Sobrescribir (TRUNCATE + INSERT)", + modeUpsert: "Upsert (INSERT o UPDATE por PK)", + batchSize: "Tamaño de lote", + start: "Iniciar transferencia", + cancel: "Cancelar", + progress: "Transfiriendo...", + transferring: "Transfiriendo {table}...", + completed: "Transferencia completada", + failed: "Error en la transferencia", + cancelled: "Transferencia cancelada", + rowsTransferred: "{count} filas transferidas", + tableProgress: "{current} / {total} tablas", + noSqlConnections: "No hay conexiones SQL disponibles", + selectTables: "Selecciona las tablas a transferir", + searchTables: "Buscar tablas...", + noTables: "Sin tablas", + selectSourceFirst: "Selecciona primero la conexión y base de datos de origen", + tableError: "Error en tabla {table}: {error}", + overallProgress: "Progreso general", + dataTransfer: "Transferencia de datos", + }, + tableImport: { + title: "Importar datos a tabla", + target: "Tabla destino", + selectFile: "Seleccionar archivo", + file: "Archivo", + rows: "Filas", + mapped: "Mapeadas", + mapping: "Mapeo de columnas", + skipColumn: "Omitir", + preview: "Vista previa", + mode: "Modo de importación", + append: "Agregar datos", + truncate: "Truncar e importar", + progress: "Progreso", + start: "Iniciar importación", + success: "Se importaron {count} filas", + }, + sqlFile: { + title: "Ejecutar archivo SQL", + file: "Archivo", + selectSqlFile: "Seleccionar archivo SQL", + browse: "Examinar", + target: "Destino", + connection: "Conexión", + selectConnection: "Seleccionar conexión", + database: "Base de datos", + selectDatabase: "Seleccionar base de datos", + databasePlaceholder: "Nombre de la base de datos", + options: "Opciones", + continueOnError: "Continuar en caso de error", + cancelling: "Cancelando...", + cancel: "Cancelar", + execute: "Ejecutar", + statement: "Sentencia", + succeeded: "Exitosa", + failed: "Fallida", + affectedRows: "Filas afectadas", + currentStatement: "Sentencia actual", + status: { + idle: "Inactivo", + started: "Iniciado", + running: "En ejecución", + statementDone: "Sentencia completada", + statementFailed: "Sentencia fallida", + done: "Completado", + error: "Error", + cancelled: "Cancelado", + }, + }, + diff: { + title: "Comparar bases de datos", + source: "Origen", + target: "Destino", + selectConnection: "Seleccionar conexión", + selectDatabase: "Seleccionar base de datos", + selectSchema: "Seleccionar esquema", + compare: "Comparar", + comparing: "Comparando esquemas...", + noDifferences: "No se encontraron diferencias", + table: "Tabla", + status: "Estado", + details: "Detalles", + added: "Agregada", + removed: "Eliminada", + modified: "Modificada", + indexes: "Índices", + foreignKeys: "Claves foráneas", + triggers: "Disparadores", + newTable: "Nueva tabla", + dropTable: "Eliminar tabla", + generatedSql: "SQL de sincronización", + copySql: "Copiar SQL", + executeSync: "Ejecutar sincronización", + syncSuccess: "Sincronización ejecutada exitosamente", + }, + dataCompare: { + title: "Comparar datos", + selectTable: "Seleccionar tabla", + keyColumns: "Columnas clave", + keyColumnsPlaceholder: "Columnas primarias o únicas separadas por comas", + rowLimit: "Límite de filas a comparar", + rowLimitOption: "{count} filas", + rowCounts: "Origen {source} filas, destino {target} filas; se comparan hasta las primeras {limit} filas", + truncatedWarning: + "La tabla supera este límite de comparación, por lo que los resultados solo cubren las filas cargadas. Aumenta el límite o usa comparación por fragmentos más adelante.", + compare: "Comparar datos", + summary: "Agregadas {added}, eliminadas {removed}, modificadas {modified}", + noKeyColumns: "Selecciona al menos una columna clave", + missingKeyColumns: "Las columnas clave deben existir en las tablas de origen y destino: {columns}", + noCommonColumns: "Las tablas de origen y destino no tienen columnas en común para comparar", + noDifferences: "Los datos son idénticos, no se necesita SQL de sincronización", + syncSuccess: "Sincronización de datos ejecutada exitosamente", + }, + settings: { + title: "Configuración", + editorTab: "Editor", + appearanceTab: "Apariencia", + jdbcTab: "Drivers JDBC", + securityTab: "Seguridad", + aboutTab: "Acerca de", + fontFamily: "Familia tipográfica", + selectFont: "Seleccionar fuente...", + fontSize: "Tamaño de fuente", + theme: "Tema", + selectTheme: "Seleccionar tema...", + appLayout: "Diseño de la interfaz", + appLayoutSeparated: "Moderno separado", + appLayoutSeparatedDescription: "Separación clara de paneles con pestañas en bloque.", + appLayoutClassic: "Clásico compacto", + appLayoutClassicDescription: "Barra de pestañas continua con encabezados de panel compactos.", + apply: "Aplicar", + resetDefaults: "Restablecer valores por defecto", + executeMode: "Modo de ejecución (Cmd+Enter)", + executeModeAll: "Ejecutar todo el SQL", + executeModeCurrent: "Ejecutar sentencia en el cursor", + wordWrap: "Ajuste de línea", + wordWrapDescription: "Ajustar las líneas largas de SQL al ancho del editor", + preview: "Vista previa en tiempo real", + jdbcPlugin: "Plugin JDBC de DBX", + jdbcPluginInstall: "Instalar plugin JDBC", + jdbcPluginInstallSuccess: "Plugin JDBC instalado", + jdbcPluginUninstall: "Desinstalar", + jdbcPluginUninstallSuccess: "Plugin JDBC desinstalado", + jdbcPluginInstalled: "Instalado: v{version}", + jdbcPluginIncompatible: "Protocolo incompatible", + jdbcPluginNotInstalled: "No instalado", + jdbcDrivers: "JARs de drivers JDBC", + jdbcImport: "Importar JAR", + jdbcImportPath: "Ruta de importación", + jdbcDriverPathPlaceholder: "/ruta/al/driver.jar", + jdbcImportSuccess: "Se importaron {count} archivo(s) de driver", + jdbcDeleteSuccess: "Driver eliminado", + jdbcNoDrivers: "Aún no se han importado drivers JDBC.", + aboutDescription: "Una herramienta de administración de bases de datos liviana y de código abierto.", + community: "Comunidad", + qqGroup: "Grupo QQ", + project: "Proyecto", + openSource: "Repositorio de código abierto", + }, +}; \ No newline at end of file