diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json
new file mode 100644
index 000000000..2b8c89b3b
--- /dev/null
+++ b/app/appearance/langs/es_ES.json
@@ -0,0 +1,908 @@
+{
+ "tabLimit": "Límite de la pestaña",
+ "tabLimit1": "Ordenar por hora de apertura, cerrar la primera pestaña abierta",
+ "pasteEscaped": "Pegar texto escapado",
+ "resetRepoTip": "Restablecer el repositorio de datos borrará completamente la clave y todas las instantáneas, ¿estás seguro de que quieres restablecer?",
+ "resetRepo": "Restablecer el repositorio de datos",
+ "createSnapshot": "Crear instantánea",
+ "dataSnapshot": "Instantánea de datos",
+ "copyKey": "Copiar cadena de claves",
+ "importKey": "Importar la clave",
+ "keyPlaceholder": "Por favor, pegue la cadena de claves aquí",
+ "key": "Clave",
+ "genKey": "Generación de la clave",
+ "dataRepoKeyTip": "El repositorio de datos se utiliza para cifrar y guardar instantáneas de datos. Las instantáneas pueden crearse y revertirse en el historial de datos",
+ "dataRepoKeyTip2": "Una vez inicializada la clave en el dispositivo principal, los demás dispositivos deben importarla. Si las claves de los dispositivos son incoherentes, las instantáneas de datos no se pueden sincronizar con la nube, así que asegúrate de utilizar la misma clave en todos los dispositivos",
+ "dataRepoKey": "Clave del repositorio de datos",
+ "plsChoose": "Por favor, elija primero",
+ "clearMessage": "Leer",
+ "freeSub": "Suscripción de prueba gratuita",
+ "sortByUpdateTimeDesc": "Descendente por tiempo de actualización",
+ "sortByUpdateTimeAsc": "Ascendente por tiempo de actualización",
+ "sortByDownloadsDesc": "Descendente por descargas",
+ "sortByDownloadsAsc": "Ascendente por descargas",
+ "activationCode": "Código de activación",
+ "activationCodePlaceholder": "Por favor, introduzca aquí su código de activación de la suscripción",
+ "exportDataTip": "Empaquetar y exportar todos los archivos de la carpeta workspace/data/
como un archivo zip",
+ "importDataTip": "Importar el archivo zip exportado, sobrescribiendo la carpeta workspace/data/
por la ruta",
+ "includeChildDoc": "Incluir los documentos de los niños",
+ "text": "Texto",
+ "lastUsed": "Fuentes utilizadas recientemente",
+ "removedNotebook": "Cuaderno de notas eliminado",
+ "querySyntax": "Sintaxis de consulta",
+ "rollback": "Rollback",
+ "custom": "Personalizado",
+ "feedback": "Comentarios",
+ "inbox": "Bandeja de entrada",
+ "turnToStaticRef": "Texto de anclaje estático ref",
+ "turnToDynamicRef": "Texto de anclaje dinámico ref",
+ "turnToStatic": "Texto de anclaje estático",
+ "turnToDynamic": "Texto de anclaje dinámico",
+ "sizeLimit": "Límite",
+ "trafficStat": "Estadísticas de tráfico",
+ "hideHeadingBelowBlocks": "Ocultar bloques por debajo del encabezamiento",
+ "matchDiacritics": " Hacer coincidir los diacríticos",
+ "foldTip": "¿Seguro que se cambia al modo de edición y se registra el estado de plegado?",
+ "copyHPath": "Copiar ruta legible",
+ "justify": "Justificar",
+ "height": "Altura",
+ "deactivateUser": "Desactivar cuenta",
+ "deactivateUserTip": "
64
",
+ "searchCaseSensitive": "Distingue mayúsculas y minúsculas",
+ "searchCaseSensitive1": "Después de habilitarlo, todas las funciones relacionadas con la búsqueda distinguirán entre mayúsculas y minúsculas",
+ "toggleWin": "Ocultar/Mostrar Ventana",
+ "customSort": "Clasificación personalizada",
+ "downloadingUpdate": "Descargando el paquete de actualización, por favor espere...",
+ "collapse": "Colapsar",
+ "blockEmbed": "Integrar Bloque",
+ "gutterTip": "Arrastre para mover la posiciónPor defecto
para utilizar la familia de fuentes del tema",
+ "newNameFile": "El nombre del nuevo documento es",
+ "newContentFile": "El contenido del nuevo documento es",
+ "exporting": "Exportando, por favor espere...",
+ "exported": "Exportación completada: ",
+ "refExpired": "El bloque de contenido de búsqueda no existe",
+ "emptyContent": "No hay contenido relacionado",
+ "useBrowserView": "Ver en el navegador",
+ "userLocalPDF": "Abrir con la herramienta local de PDF",
+ "copyID": "ID de copia",
+ "newBookmark": "Crear una etiqueta de marcador",
+ "generateHistory": "Intervalo de Generación de Historial (minutos, poner a 0 para desactivar)",
+ "generateHistoryInterval": "El historial se genera automáticamente cuando se edita o se borra, y se puede ver y retroceder en el historial de datos",
+ "historyRetentionDays": "Días de retención del historial",
+ "historyRetentionDaysTip": "Los datos históricos que superen los días de retención se eliminarán automática y completamente",
+ "clearHistory": "Borrar todo el historial",
+ "confirmClearHistory": "¿Está seguro de que desea borrar completamente todos los datos históricos del área de trabajo?",
+ "fileNameASC": "Nombre alfabeto ASC",
+ "fileNameDESC": "Nombre alfabeto DESC",
+ "modifiedASC": "Tiempo modificado ASC",
+ "modifiedDESC": "Tiempo Modificado DESC",
+ "fileNameNatASC": "Nombre Natural ASC",
+ "fileNameNatDESC": "Nombre Natural DESC",
+ "refCountASC": "Número de referencia ASC",
+ "refCountDESC": "Número de referencia DESC",
+ "createdASC": "Tiempo de Creación ASC",
+ "createdDESC": "Tiempo de creación DESC",
+ "sort": "Ordenar",
+ "enterFullscreen": "Entrar en pantalla completa",
+ "exitFullscreen": "Salir de pantalla completa",
+ "clearUnused": "Activos no utilizados",
+ "clearAll": "¿Está seguro de limpiar todos los activos no referenciados?",
+ "paste": "Pegar",
+ "pasteRef": "Pegar bloque ref",
+ "pasteEmbed": "Pegar bloque incrustado",
+ "cut": "Cortar",
+ "mentions": "Mención",
+ "colorBorder": "Color del borde",
+ "colorFont": "Color de la fuente",
+ "colorGraph": "Color del gráfico",
+ "colorInline": "Color del documento en línea",
+ "colorPrimary": "Color de fondo",
+ "colorScroll": "Color de desplazamiento",
+ "colorTab": "Color de la pestaña",
+ "colorTip": "Color de la información sobre la herramienta",
+ "--b3-theme-primary": "Primario",
+ "--b3-theme-primary-light": "Primario - ligero",
+ "--b3-theme-primary-lighter": "Primario - Poco ligero",
+ "--b3-theme-primary-lightest": "Primario - Más ligero",
+ "--b3-theme-secondary": "Secundario",
+ "--b3-theme-background": "Fondo",
+ "--b3-theme-surface": "Superficie",
+ "--b3-theme-error": "Error",
+ "--b3-theme-on-primary": "Color de la fuente en el primario",
+ "--b3-theme-on-secondary": "Color de la fuente en el secundario",
+ "--b3-theme-on-background": "Color de fuente en el fondo",
+ "--b3-theme-on-surface": "Color de la fuente en la superficie",
+ "--b3-theme-on-error": "Color de la fuente en caso de error",
+ "--b3-border-color": "Borde",
+ "--b3-scroll-color": "Desplazar",
+ "--b3-list-hover": "Color de la Lista por encima",
+ "--b3-tab-background": "Fondo de la pestaña",
+ "--b3-tooltips-color": "Fondo de la información sobre",
+ "--b3-graph-p-point": "Punto de párrafo",
+ "--b3-graph-heading-point": "Punto de título",
+ "--b3-graph-math-point": "Punto de Matemáticas",
+ "--b3-graph-code-point": "Punto de código",
+ "--b3-graph-table-point": "Punto de tabla",
+ "--b3-graph-list-point": "Punto de lista",
+ "--b3-graph-todo-point": "Punto de lista de tareas",
+ "--b3-graph-olist-point": "Punto de lista de órdenes",
+ "--b3-graph-listitem-point": "Punto de la lista de elementos",
+ "--b3-graph-bq-point": "Punto de la lista de bloques",
+ "--b3-graph-super-point": "Super punto",
+ "--b3-graph-doc-point": "Punto de documento",
+ "--b3-graph-tag-point": "Punto de etiqueta",
+ "--b3-graph-asset-point": "Punto de Activo",
+ "--b3-graph-line": "Línea ",
+ "--b3-graph-ref-line": "Línea entre enlace",
+ "--b3-graph-tag-line": "Línea entre etiqueta y nodo",
+ "--b3-graph-tag-tag-line": "Línea entre etiqueta y etiqueta",
+ "--b3-graph-asset-line": "Línea entre activo y nodo",
+ "--b3-graph-hl-point": "Elegir punto",
+ "--b3-graph-hl-line": "Elegir línea",
+ "--b3-protyle-inline-strong-color": "Negrita",
+ "--b3-protyle-inline-em-color": "cursiva",
+ "--b3-protyle-inline-s-color": "strike",
+ "--b3-protyle-inline-link-color": "Enlace",
+ "--b3-protyle-inline-mark-background": "Fondo de marca",
+ "--b3-protyle-inline-mark-color": "Marca",
+ "--b3-protyle-inline-tag-color": "Etiqueta",
+ "--b3-protyle-inline-blockref-color": "Bloque Ref",
+ "open": "Encender",
+ "sync": "Sincronización",
+ "syncNow": "Sincronizar ahora",
+ "cloudBook": "Cuaderno de notas en la nube",
+ "payment": "Pago",
+ "refresh": "Actualizar",
+ "accountManage": "Gestión de la cuenta",
+ "logout": "Cierre de sesión",
+ "refreshUser": "Información del usuario actualizada",
+ "insertBottom": "Abrir debajo de la pestaña",
+ "insertRight": "Abrir a la derecha la pestaña",
+ "downloadCloud": "Descargar",
+ "downloadCloudTip": "Después de la descarga, la copia de seguridad en la nube se utilizará para sobrescribir la copia de seguridad local. ¿Desea descargarla?",
+ "account3Tip": "La copia de seguridad local sobrescribirá la copia de seguridad en la nube. ¿Quieres subirla?",
+ "account1": "Haga clic para pagar",
+ "account2": ",
para separar, la propia coma puede escaparse con \\,
",
+ "md37": "La longitud máxima del bloque de texto de anclaje dinámico",
+ "md38": "La longitud máxima del texto de anclaje que se renderiza automáticamente cuando el texto de anclaje del bloque ref no está personalizado, el valor por defecto es 96
caracteres",
+ "md39": "Dirección de PlantUML Serve",
+ "md40": "Dejar en blanco para restablecer el valor por defecto https://www.plantuml.com/plantuml/svg/~1
",
+ "fileTree2": "El árbol de documentos seleccionará automáticamente el documento actual cuando se cambie la pestaña del editor",
+ "fileTree3": "No se requiere confirmación al borrar documentos",
+ "fileTree4": "Si no se activa, aparecerá un cuadro de confirmación cada vez que se elimine un documento",
+ "fileTree5": "Ref crear ubicación de guardado de documentos",
+ "fileTree6": "Al utilizar ((
, la ruta de guardado del nuevo documento (por ejemplo, /carpeta1/carpeta2/, se utiliza la ruta relativa del documento actual si no empieza por /)",
+ "fileTree7": "Abrir en la pestaña actual",
+ "fileTree8": "La pestaña del documento recién abierto sustituirá a la pestaña no modificada",
+ "fileTree11": "Nueva nota diaria",
+ "fileTree12": "Nueva pantilla de nombre de documento",
+ "fileTree13": "El fragmento de plantilla se utiliza por defecto cuando se crea un nuevo documento para nombrar, por ejemplo {{now | date \"20060102150405\"}}
",
+ "fileTree14": "Ruta de guardado (admite variables de plantilla de formato de fecha, como /Notas del día/{{now | date \"2006/01\"}}/{{now | date \"2006-01-02\"}}
)",
+ "fileTree15": "Ruta de la plantilla (por ejemplo, dailynote.md
, el archivo debe colocarse en el espacio de trabajo/data/templates/)",
+ "fileTree16": "Número máximo a listar",
+ "fileTree17": "Si hay demasiados subdocumentos, se puede utilizar esta restricción para mejorar el rendimiento",
+ "fileTree18": "Permite la creación de subdocumentos de más de 7 niveles",
+ "fileTree19": "Algunos sistemas operativos tienen limitaciones técnicas que pueden impedir la copia manual de los datos del espacio de trabajo después de crear subdocumentos de más de 7 niveles",
+ "export11": "Método de manejo de contenido del bloque de referencia de contenido al exportar",
+ "export12": "Método de manejo de contenido del bloque de incrustación de contenido al exportar",
+ "export13": "Símbolo de envoltura de texto ancla",
+ "export14": "Por favor, rellene el símbolo de la izquierda del texto de anclaje y el símbolo de la derecha del texto de anclaje en el cuadro de entrada por turnos",
+ "export15": "Símbolo del paquete de la etiqueta",
+ "export16": "Por favor, rellene el símbolo del lado izquierdo de la etiqueta y el símbolo del lado derecho de la etiqueta en el cuadro de entrada por turnos",
+ "export17": "Añadir título del documento",
+ "export18": "Insertar el título del documento como título 1 al principio",
+ "export19": "Ruta de acceso al ejecutable de Pandoc",
+ "export20": "La exportación de archivos Word .docx requiere la conversión del formato mediante Pandoc",
+ "blockRef": "Bloque de referencia",
+ "theme11": "Usar tema en modo claro",
+ "theme12": "Usar tema en modo oscuro",
+ "theme13": "Personalizar el tema actual",
+ "theme14": "Después de habilitarlo, se utilizará el tema personalizado, y después de cerrarlo, se utilizará el tema original",
+ "theme2": "Selecciona los iconos utilizados en la interfaz de usuario",
+ "language1": "Seleccionar el idioma de visualización de la interfaz de usuario",
+ "summary": "Resumen",
+ "bookmark": "Marcador",
+ "icon": "Icono",
+ "appearance": "Apariencia",
+ "export0": "Texto original",
+ "export1": "Bloque de cita",
+ "export2": "Texto de anclaje con URL de bloque",
+ "export3": "Sólo texto de anclaje",
+ "export4": "Notas a pie de página",
+ "export5": "Ref. de anotación en PDF",
+ "export6": "Sobre el manejo del texto ancla en las anotaciones PDF al exportar",
+ "export7": "Nombre de archivo - Número de página - Texto ancla",
+ "export8": "Sólo texto ancla",
+ "graphConfig2": "Filtro de recuento de referencias",
+ "selectOpen": "Seleccionar siempre el documento abierto",
+ "selectOpen1": "Seleccionar documento abierto",
+ "closeAll": "Cerrar todo",
+ "closeOthers": "Cerrar Otros",
+ "closeLeft": "Cerrar pestañas a la izquierda",
+ "closeRight": "Cerrar pestañas a la derecha",
+ "closeUnmodified": "Cerrar pestañas sin modificar",
+ "newFileTip": "Por favor, abra primero un cuaderno de notas",
+ "copyBlockRef": "Copiar Bloque Referencia",
+ "copyBlockEmbed": "Copiar bloque incrustado",
+ "linkLevel": "Profundidad",
+ "mark": "Marca",
+ "splitLR": "Dividir a la derecha",
+ "splitMoveR": "Dividir y mover a la derecha",
+ "splitTB": "Dividir hacia abajo",
+ "splitMoveB": "Dividir y mover hacia abajo",
+ "debug": "Herramientas para desarrolladores",
+ "fileTree": "Árbol de documentos",
+ "graphView": "Vista gráfica",
+ "sponsor": "Hecho por amor, Obtén el título",
+ "relativeRelation": "Enlazado",
+ "parentRelation": "Padre - Hijo",
+ "openInNewTab": "Abrir en una nueva pestaña",
+ "help": "Ayuda",
+ "paragraphBeginningSpace": "Dos espacios vacíos al principio del párrafo",
+ "outline": "Esquema",
+ "newFile": "Nuevo documento",
+ "close": "Cerrar",
+ "delete": "Borrar",
+ "rename": "Cambiar nombre",
+ "cancel": "Cancelar",
+ "confirm": "Confirmar",
+ "confirmDelete": "Borrar",
+ "confirmDeleteCloudDir": "¿Seguro que quieres eliminar el directorio de sincronización en la nube?",
+ "back": "Atrás",
+ "mount": "Abrir cuaderno de notas",
+ "newNotebook": "Nuevo cuaderno de notas",
+ "fileNameRule": "No se permite /",
+ "slogan": "Construye tu jardín digital eterno",
+ "showInFolder": "Mostrar en la carpeta",
+ "search": "Buscar",
+ "config": "Ajustes",
+ "userName": "Nombre de usuario",
+ "password": "Contraseña",
+ "theme": "Tema",
+ "language": "Idioma",
+ "about": "Acerca de",
+ "about1": "Copyright (c) 2020-presente • Yunnan Liandi Technology Co., Ltd. • Política de privacidad • Acuerdo de usuario6806
, las direcciones IP que pueden conectarse son las siguientes",
+ "about4": "Abrir el navegador",
+ "about5": "Código de autorización de acceso",
+ "about6": "Después de la configuración, se utilizará como contraseña de autenticación de acceso, déjelo en blanco para cerrar la autenticación",
+ "about7": "Directorio del espacio de trabajo",
+ "about8": "Después de cambiar el espacio de trabajo, los datos del espacio de trabajo actual no se copiarán en el espacio de trabajo de destinodata
del espacio de trabajo actual en el sistema de archivos al espacio de trabajo de destinoConexión directa
. La aplicación se cerrará automáticamente después de la modificación, por favor reinicie manualmente",
+ "checkUpdate": "Comprobar actualización",
+ "currentVer": "Versión actual",
+ "visitAnnouncements": "Ver los anuncios del sistema",
+ "themeLight": "Claro",
+ "themeDark": "Oscuro",
+ "pasteAsPlainText": "Pegar como texto sin formato",
+ "assets": "Activos",
+ "alignCenter": "Centro",
+ "alignLeft": "Izquierda",
+ "alignRight": "Derecha",
+ "alternateText": "Texto alternativo",
+ "bold": "Negrita",
+ "both": "editor y vista previa",
+ "check": "Lista de tareas",
+ "code": "Bloque de códigos",
+ "code-theme": "Vista previa del tema Code Block",
+ "column": "Columna",
+ "content-theme": "Vista previa del tema de contenido",
+ "copied": "Copiado",
+ "copy": "Copiar",
+ "delete-column": "Borrar columna",
+ "delete-row": "Borrar fila",
+ "devtools": "Árbol de sintaxis abstracta",
+ "down": "Abajo",
+ "downloadTip": "El navegador no soporta la función de descarga",
+ "edit-mode": "Modo Interruptor",
+ "emoji": "Emoji",
+ "export": "Exportar",
+ "fileTypeError": "el tipo de archivo es desconocido",
+ "fullscreen": "Alternar pantalla completa",
+ "generate": "Generar",
+ "headings": "Cabeceras",
+ "imageURL": "URL de la imagen",
+ "indent": "sangría",
+ "info": "Información",
+ "inline-code": "Código Inline",
+ "insert-after": "Insertar línea después",
+ "insert-before": "Insertar línea antes",
+ "insertColumnLeft": "Insertar 1 a la izquierda",
+ "insertColumnRight": "Insertar 1 derecha",
+ "insertRowAbove": "Insertar 1 arriba",
+ "insertRowBelow": "Insertar 1 abajo",
+ "italic": "Cursiva",
+ "line": "Divisor",
+ "link": "Enlace",
+ "list": "Lista",
+ "more": "Más",
+ "nameEmpty": "El nombre está vacío",
+ "ordered-list": "Lista ordenada",
+ "outdent": "anular la sangría",
+ "over": "sobre",
+ "preview": "Vista previa de la exportación",
+ "quote": "Párrafo cita",
+ "startRecord": "Iniciar grabación",
+ "endRecord": "Finalizar grabación",
+ "record-tip": "El dispositivo no admite la grabación",
+ "recording": "grabando...",
+ "redo": "Rehacer",
+ "remove": "Eliminar",
+ "row": "Fila",
+ "splitView": "Vista dividida",
+ "strike": "Strike",
+ "table": "Tabla",
+ "title": "Título",
+ "tooltipText": "Globo de ayuda",
+ "undo": "Deshacer",
+ "up": "Arriba",
+ "update": "Actualización",
+ "insertAsset": "Insertar imagen o archivo",
+ "uploadError": "error de subida",
+ "uploading": "Subiendo...",
+ "wysiwyg": "WYSIWYG",
+ "_label": "Español",
+ "_time": {
+ "albl": "hace",
+ "blbl": "a partir de ahora",
+ "now": "ahora",
+ "1s": "1 segundo %s",
+ "xs": "%d segundos %s",
+ "1m": "1 minuto %s",
+ "xm": "%d minutos %s",
+ "1h": "1 hora %s",
+ "xh": "%d horas %s",
+ "1d": "1 día %s",
+ "xd": "%d días %s",
+ "1w": "1 semana %s",
+ "xw": "%d semanas %s",
+ "1M": "1 mes %s",
+ "xM": "%d meses %s",
+ "1y": "1 año %s",
+ "2y": "2 años %s",
+ "xy": "%d años %s",
+ "max": "mucho tiempo %s"
+ },
+ "_kernel": {
+ "0": "Consulta al cuaderno de notas fallido",
+ "1": "Nombre de archivo duplicado",
+ "2": "Lista de archivos de la caja [%s] y la ruta [%s] falló: %s",
+ "3": "Leer el archivo de libro [%s] falló: %s",
+ "4": "Obtener información meta del cuaderno [%s] falló: %s",
+ "5": "Mover el cuaderno [%s] falló: %s",
+ "6": "Crear cuaderno [%s] carpeta [%s] falló: %s",
+ "7": "Eliminar libreta [%s] ruta [%s] falló: %s",
+ "8": "Comprobar la actualización falló",
+ "9": "Una nueva versión está disponible, por favor, busque el anuncio de lanzamiento %s",
+ "10": "Es la última versión",
+ "11": "Por favor, configure primero [Configuración - Nube - Contraseña de encriptación de extremo a extremo].",
+ "12": "Fallo en la consulta de activos [%s]",
+ "13": "No se puede crear un archivo que empiece por .",
+ "14": "Exportación fallida: %s",
+ "15": "El bloque de contenido con ID [%s] no se encontró, por favor inténtelo de nuevo más tarde en el menú del panel del árbol del documento [Reconstruir índice]",
+ "16": "Por favor, introduzca el nombre del documento",
+ "17": "La sincronización inicial falló en el arranque. Puede producirse una sobreescritura de datos imprevisible si continúa operando. Por favor, realice primero una sincronización",
+ "18": "Falló la obtención de la cuenta de usuario de la comunidad",
+ "19": "La información del usuario ha caducado, por favor, inicie sesión de nuevo",
+ "20": "No se puede convertir en título al incluir subdocumentos",
+ "21": "Copia de seguridad completada",
+ "22": "Haciendo copia de seguridad, por favor espere...",
+ "23": "Copia de seguridad fallida: %s",
+ "24": "Fallo en la obtención de la información de sincronización con la nube: %s",
+ "25": "El nombre del atributo sólo admite letras y dígitos en inglés",
+ "26": "Por favor, inicialice primero la clave de repositorio de datos en [Configuración - Acerca de - Clave de repositorio de datos]",
+ "27": "Falló la comprobación de la integridad de los datos",
+ "28": "Contraseña de cifrado de extremo a extremo incorrecta, no se pueden descifrar los datos",
+ "29": "Esta función requiere una suscripción de pago (Si se ha suscrito, actualice o vuelva a conectarse en configuración - cuenta)",
+ "30": "Fallo en la obtención de la información de la copia de seguridad en la nube",
+ "31": "Falló la autentificación de la cuenta, por favor, inicie sesión de nuevo",
+ "32": "Fallo en la eliminación de la libreta en la nube",
+ "33": "Permisos insuficientes para leer y escribir archivos o acceso a la red, por favor comprueba los permisos de la carpeta del espacio de trabajo y la configuración del software antivirus/firewall. Si has ejecutado SiYuan como administrador antes, por favor considera cambiar a un nuevo directorio de espacio de trabajo, y no lo ejecutes como administrador en el futuro (el directorio de espacio de trabajo actual puede que ya no sea accesible por los usuarios ordinarios)",
+ "34": "Esta operación no es compatible con el modo de sólo lectura",
+ "35": "Reconstruyendo el índice, por favor espere...",
+ "36": "Por favor, comprueba la actualización de la versión en la tienda de aplicaciones",
+ "37": "No incluyas espacios ni símbolos especiales en el nombre del directorio de sincronización con la nube",
+ "38": "El número de palabras clave mencionadas [%d] son demasiados, actualmente solo admite hasta [512] palabras clave",
+ "39": "La contraseña de E2EE no puede estar en blanco",
+ "40": "Fallo en la desencriptación de datos",
+ "41": "Carga completada",
+ "42": "La configuración se ha completado, la aplicación se cerrará automáticamente, por favor reinicie más tarde...",
+ "43": "Se ha superado la capacidad máxima de almacenamiento del espacio en la nube [%s] y la carga de datos no puede continuar",
+ "44": "El proceso de análisis de la plantilla ha fallado: %s",
+ "45": "Abriendo, por favor espere...",
+ "46": "La descarga del [%s] falló debido a problemas de red, por favor inténtelo más tarde",
+ "47": "Fallo en la desinstalación: %s",
+ "48": "Sólo se listan los primeros [%d] subdocumentos, si necesita ajustarlos, modifique [Configuración - Árbol de documentos - Número máximo a listar]",
+ "49": "Por favor, especifique la ruta de guardado de las notas diarias en la Configuración de la Libreta",
+ "50": "Resolviendo referencia [%s]",
+ "51": "Bloque de contenido de la caché [%d]",
+ "52": "Cada vez que abra la guía del usuario se restablecerán los datos del cuaderno, así que no guarde ningún dato en ella",
+ "53": "Se ha completado el índice de [%d] documentos y quedan [%d] por procesar",
+ "54": "Indexando referencias...",
+ "55": "Referencias indexadas de [%d] documentos",
+ "56": "Documentos indexados [%d]",
+ "57": "Fallo en la creación de la clave temporal",
+ "58": "Después de reconstruir el índice, la interfaz se actualizará automáticamente más tarde...",
+ "59": " Falló la configuración de sincronización de la lista de ignorados",
+ "60": "Fallo al obtener el paquete de actualización: %s",
+ "61": "Cargando, por favor espere...",
+ "62": "La recuperación se ha completado y el índice se reconstruirá...",
+ "63": "Recuperando, por favor espere...",
+ "64": "Hay [%d] archivos en total, tardará un tiempo en indexarse, por favor espere...",
+ "65": "Exportando datos...",
+ "66": "Archivo de datos [%s] creado",
+ "67": "Cargado en %s, descargado en %s",
+ "68": "Descargando, por favor espere...",
+ "69": "Descarga completada",
+ "70": "Error en la copia del cuaderno [%s] del archivo [%s]: %s",
+ "71": "Fallo en la inserción del archivo de activos, por favor reabra el documento",
+ "72": "El contenido se ha copiado en el portapapeles del sistema, por favor vaya a SiYuan para pegar",
+ "73": "Importando, por favor espere...",
+ "74": "El kernel no ha sido arrancado completamente [%d%%], por favor, inténtelo de nuevo más tarde",
+ "75": "Fallo en el bloqueo del archivo [%s]",
+ "76": "El archivo de datos ha sido bloqueado por otro programa. (Si se utiliza un disco de sincronización de terceros, compruebe el estado de la sincronización)",
+ "77": "Ruta inválida [%s]",
+ "78": "Los viejos y nuevos caminos se repiten",
+ "79": "Sólo admite la importación de documentos Markdown",
+ "80": "Sincronización fallida: %s",
+ "81": "Sincronizando datos...",
+ "82": "Sincronizado en %s",
+ "83": "El código de autorización de acceso es incorrecto",
+ "84": "Los metadatos utilizados para la sincronización se han dañado, consulte aquí para resolver",
+ "85": "El archivo está ocupado por otros programas. No utilice el administrador de archivos del sistema para abrir la carpeta del área de trabajo durante el funcionamiento; no utilice un disco de sincronización en tiempo real de terceros y compruebe si la carpeta del área de trabajo tiene permisos de escritura",
+ "86": "Por favor, configure [Ajustes - Acerca de - Código de autorización de acceso]",
+ "87": "No se puede mover a esta ubicación",
+ "88": "Se ha terminado de analizar [%d] archivos de datos, quedan por procesar [%d]",
+ "89": "Los datos locales sobrescribirán los datos del directorio de sincronización en la nube %s",
+ "90": "Los datos del directorio de sincronización en la nube %s sobrescribirán los datos locales",
+ "91": "Los datos locales y el directorio de sincronización en la nube %s tienen los mismos datos",
+ "92": "La contraseña de encriptación de extremo a extremo está establecida",
+ "93": "Descarga fallida: %s",
+ "94": "Carga fallida: %s",
+ "95": "Saliendo...",
+ "96": "La sincronización falló al salir. Por favor, realice manualmente una sincronización para asegurarse de que los datos locales son coherentes con los datos de la nube",
+ "97": "Forzar la salida",
+ "98": "No configure el espacio de trabajo bajo la ruta de instalación, de lo contrario se perderán los datos al desinstalar o actualizar los programas",
+ "99": "La limpieza de datos ha finalizado",
+ "100": "Limpieza de datos...",
+ "101": "El recordatorio de configuración [%s] se ha completado",
+ "102": "Configurando la contraseña de cifrado de extremo a extremo...",
+ "103": "[%d] archivos de datos han sido descargados, y [%d] quedan por descargar",
+ "104": "[%d] archivos de datos han sido cargados, y [%d] restantes por cargar",
+ "105": "Transmisión de red completada",
+ "106": "La descarga de datos se ha completado y la desencriptación está en curso...",
+ "107": "Moviendo documento [%s]",
+ "108": "Limpiando índices obsoletos...",
+ "109": "Eliminación de recordatorios completada [%s]",
+ "110": "Renombrar...",
+ "111": "Guardando documento [%s]...",
+ "112": "No incluir marcador de sintaxis Markdown",
+ "113": "Completando la escritura de datos...",
+ "114": "La etiqueta no puede estar vacía",
+ "115": "Por favor, configure primero [Ajustes - Exportación - Ruta ejecutable de Pandoc]",
+ "116": "Procesando, por favor espere...",
+ "117": "[%s] no es un ejecutable válido de Pandoc",
+ "118": "La configuración actual no permite la creación de subdocumentos bajo un documento de 7 niveles de profundidad",
+ "119": "Descargando imagen web [%s]",
+ "120": "Descarga completa, [%d] archivos en total",
+ "121": "No hay ninguna imagen de red en este documento",
+ "122": "Esta función necesita ser configurada en el escritorio de SiYuan",
+ "123": "La función de sincronización solo puede activarse después de añadir/seleccionar el directorio de sincronización en la nube",
+ "124": "Por favor, active la sincronización en la nube en [Ajustes - Activar sincronización en la nube]",
+ "125": "La sincronización automática ha fallado demasiadas veces, por favor intente activar la sincronización manualmente, si todavía hay un problema, por favor infórmelo a través de Problemas GitHub",
+ "126": "El marcador no puede estar vacío",
+ "127": "There are [%d] days left before the subscription expires, after which the cloud data will be completely deleted. Please visit Aquí para la renovación, si no necesita renovar, salga de su cuenta para cerrar el recordatorio",
+ "128": "La suscripción ha caducado, los datos de la nube se eliminarán completamente después de la expiración. Para renovar, visite Aquí, si no necesita renovar, salga de su cuenta para cerrar el recordatorio",
+ "129": "Número de archivos transferidos %d\nTotal de bytes recibidos %s\n",
+ "130": "Número de archivos transferidos %d\nTotal de bytes enviados %s\n",
+ "131": "Descargado en %.2fs",
+ "132": "Cargado en %.2fs",
+ "133": "No hay cambios en los datos locales",
+ "134": "Para evitar que los datos recién restaurados sean sobrescritos por la sincronización, se ha suspendido automáticamente la función de sincronización de datos",
+ "135": "Por favor, asegúrese de que todos los dispositivos han sido actualizados a la última versión y, a continuación, active la sincronización después de cambiar aleatoriamente un documento en el dispositivo principal y, finalmente, active la sincronización en otros dispositivos",
+ "136": "Inicializando la clave del repositorio de datos...",
+ "137": "Fallo en la inicialización de la clave del repositorio de datos",
+ "138": "La clave del repositorio de datos está configurada",
+ "139": "Se está generando la instantánea de datos, se han procesado [%s] archivos de datos, quedan [%s]...",
+ "140": "Fallo en la creación de la instantánea de datos: %s",
+ "141": "Fallo en la reversión de la instantánea de datos",
+ "142": "El memo de la instantánea no puede estar vacío",
+ "143": "Creando instantánea de datos...",
+ "144": "Restableciendo repositorio de datos...",
+ "145": "Restablecimiento del repositorio de datos completado",
+ "146": "Fallo en el restablecimiento del repositorio de datos: %s",
+ "147": "Creó una nueva instantánea de datos, tomó %.2fs",
+ "148": "Se ha comprobado la instantánea de los datos y no se encontro ningún cambio",
+ "149": "La instantánea de datos ha sido sincronizada, ha tomado %.2fs"
+ }
+}
diff --git a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy
index 26a084492..2650ba4b3 100644
--- a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy
+++ b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy
@@ -7,7 +7,7 @@
"title": "Please Start Here",
"title-img": "background-color:#556;background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);background-size:80px 140px;background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;",
"type": "doc",
- "updated": "20211223213256"
+ "updated": "20210528120213"
},
"Children": [
{
@@ -79,7 +79,7 @@
"Type": "NodeSuperBlock",
"Properties": {
"id": "20210528120135-bznvpp6",
- "updated": "20211223213241"
+ "updated": "20210528120213"
},
"Children": [
{
@@ -301,7 +301,7 @@
"Type": "NodeSuperBlock",
"Properties": {
"id": "20210528120140-1cfmrhm",
- "updated": "20211223213241"
+ "updated": "20210528120213"
},
"Children": [
{
@@ -332,7 +332,7 @@
"ListData": {},
"Properties": {
"id": "20201225220954-kjfoqak",
- "updated": "20211223213241"
+ "updated": "20210528120213"
},
"Children": [
{
@@ -476,7 +476,7 @@
},
{
"Type": "NodeLinkDest",
- "Data": "https://github.com/siyuan-note/siyuan/projects"
+ "Data": "https://github.com/orgs/siyuan-note/projects/1"
},
{
"Type": "NodeCloseParen"
diff --git a/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa.sy b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa.sy
index 6d65488ac..f24467307 100644
--- a/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa.sy
+++ b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa.sy
@@ -7,7 +7,7 @@
"title": "请从这里开始",
"title-img": "background-color:#556;background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);background-size:80px 140px;background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;",
"type": "doc",
- "updated": "20220426104415"
+ "updated": "20210528115049"
},
"Children": [
{
@@ -79,7 +79,7 @@
"Type": "NodeSuperBlock",
"Properties": {
"id": "20210528115012-vst5lwt",
- "updated": "20210502233951"
+ "updated": "20210528115049"
},
"Children": [
{
@@ -302,7 +302,7 @@
"Type": "NodeSuperBlock",
"Properties": {
"id": "20210528115016-kgyy5mm",
- "updated": "20211223213134"
+ "updated": "20210528115049"
},
"Children": [
{
@@ -333,7 +333,7 @@
"ListData": {},
"Properties": {
"id": "20201225220955-5tzog8p",
- "updated": "20211223213134"
+ "updated": "20210528115049"
},
"Children": [
{
@@ -477,7 +477,7 @@
},
{
"Type": "NodeLinkDest",
- "Data": "https://github.com/siyuan-note/siyuan/projects"
+ "Data": "https://github.com/orgs/siyuan-note/projects/1"
},
{
"Type": "NodeCloseParen"
diff --git a/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq.sy b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq.sy
index 69f338c6d..11253dc02 100644
--- a/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq.sy
+++ b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq.sy
@@ -6,7 +6,7 @@
"id": "20211226115423-d5z1joq",
"title": "請從這裡開始",
"title-img": "background-color:#556;background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);background-size:80px 140px;background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;",
- "updated": "20211228143127"
+ "updated": "20211226115745"
},
"Children": [
{
@@ -78,7 +78,7 @@
"Type": "NodeSuperBlock",
"Properties": {
"id": "20211226115745-rorv31l",
- "updated": "20211228143127"
+ "updated": "20211226115745"
},
"Children": [
{
@@ -302,7 +302,7 @@
"Type": "NodeSuperBlock",
"Properties": {
"id": "20211226115745-44ibza6",
- "updated": "20211227210338"
+ "updated": "20211226115745"
},
"Children": [
{
@@ -333,7 +333,7 @@
"ListData": {},
"Properties": {
"id": "20211226115745-dowhjkw",
- "updated": "20211227210338"
+ "updated": "20211226115745"
},
"Children": [
{
@@ -448,7 +448,7 @@
},
"Properties": {
"id": "20211226115745-x2tcgwg",
- "updated": "20211225212644"
+ "updated": "20211226115745"
},
"Children": [
{
@@ -478,7 +478,7 @@
},
{
"Type": "NodeLinkDest",
- "Data": "https://github.com/siyuan-note/siyuan/projects"
+ "Data": "https://github.com/orgs/siyuan-note/projects/1"
},
{
"Type": "NodeCloseParen"
@@ -1027,39 +1027,6 @@
}
]
},
- {
- "ID": "20211226115803-zbeg72e",
- "Type": "NodeHeading",
- "HeadingLevel": 2,
- "Properties": {
- "id": "20211226115803-zbeg72e",
- "updated": "20211225213107"
- },
- "Children": [
- {
- "Type": "NodeText",
- "Data": "📺 影片教學"
- }
- ]
- },
- {
- "ID": "20211226115803-6h5pn90",
- "Type": "NodeIFrame",
- "Data": "\u003ciframe src=\"https://player.bilibili.com/player.html?bvid=BV1dF411Y79u\u0026amp;page=1\u0026amp;high_quality=1\u0026amp;as_wide=1\u0026amp;allowfullscreen=true\" data-src=\"\" border=\"0\" frameborder=\"no\" framespacing=\"0\" allowfullscreen=\"true\" sandbox=\"allow-top-navigation-by-user-activation allow-same-origin allow-forms allow-scripts allow-popups\" style=\"height: 543px; width: 868px;\"\u003e\u003c/iframe\u003e",
- "Properties": {
- "id": "20211226115803-6h5pn90",
- "updated": "20211117110638"
- }
- },
- {
- "ID": "20211226115803-kfcm0yc",
- "Type": "NodeIFrame",
- "Data": "\u003ciframe src=\"https://player.bilibili.com/player.html?bvid=BV15L4y1z7Sp\u0026amp;page=1\u0026amp;high_quality=1\u0026amp;as_wide=1\u0026amp;allowfullscreen=true\" data-src=\"\" border=\"0\" frameborder=\"no\" framespacing=\"0\" allowfullscreen=\"true\" sandbox=\"allow-top-navigation-by-user-activation allow-same-origin allow-forms allow-scripts allow-popups\" style=\"height: 553px; width: 870px;\"\u003e\u003c/iframe\u003e",
- "Properties": {
- "id": "20211226115803-kfcm0yc",
- "updated": "20211117110545"
- }
- },
{
"ID": "20211226115803-fcyr6ye",
"Type": "NodeParagraph",
diff --git a/kernel/api/history.go b/kernel/api/history.go
index 0255a09ea..bf64d1800 100644
--- a/kernel/api/history.go
+++ b/kernel/api/history.go
@@ -71,6 +71,7 @@ func clearWorkspaceHistory(c *gin.Context) {
return
}
util.PushClearMsg(msgId)
+ time.Sleep(500 * time.Millisecond)
util.PushMsg(model.Conf.Language(99), 1000*5)
}
diff --git a/kernel/model/import.go b/kernel/model/import.go
index a103a5144..54d388ed6 100644
--- a/kernel/model/import.go
+++ b/kernel/model/import.go
@@ -394,6 +394,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
})
targetPaths := map[string]string{}
+ assetsDone := map[string]string{}
// md 转换 sy
i := 0
@@ -481,15 +482,19 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
}
fullPath, exist = assets[absDest]
if exist {
- name := filepath.Base(fullPath)
- ext := filepath.Ext(name)
- name = strings.TrimSuffix(name, ext)
- name += "-" + ast.NewNodeID() + ext
- assetTargetPath := filepath.Join(assetDirPath, name)
- delete(assets, absDest)
- if err = gulu.File.Copy(fullPath, assetTargetPath); nil != err {
- util.LogErrorf("copy asset from [%s] to [%s] failed: %s", fullPath, assetTargetPath, err)
- return ast.WalkContinue
+ existName := assetsDone[absDest]
+ var name string
+ if "" == existName {
+ name = filepath.Base(fullPath)
+ name = util.AssetName(name)
+ assetTargetPath := filepath.Join(assetDirPath, name)
+ if err = gulu.File.Copy(fullPath, assetTargetPath); nil != err {
+ util.LogErrorf("copy asset from [%s] to [%s] failed: %s", fullPath, assetTargetPath, err)
+ return ast.WalkContinue
+ }
+ assetsDone[absDest] = name
+ } else {
+ name = existName
}
n.Tokens = gulu.Str.ToBytes("assets/" + name)
}
@@ -568,9 +573,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
}
if exist {
name := filepath.Base(absolutePath)
- ext := filepath.Ext(name)
- name = strings.TrimSuffix(name, ext)
- name += "-" + ast.NewNodeID() + ext
+ name = util.AssetName(name)
assetTargetPath := filepath.Join(assetDirPath, name)
if err = gulu.File.CopyFile(absolutePath, assetTargetPath); nil != err {
util.LogErrorf("copy asset from [%s] to [%s] failed: %s", absolutePath, assetTargetPath, err)
diff --git a/kernel/model/liandi.go b/kernel/model/liandi.go
index a215cbce3..356283377 100644
--- a/kernel/model/liandi.go
+++ b/kernel/model/liandi.go
@@ -159,8 +159,8 @@ func AutoRefreshUser() {
util.PushErrMsg(Conf.Language(128), 0)
return
}
- remains := (expired - time.Now().Add(24*time.Hour).UnixMilli()) / 1000 / 60 / 60 / 24
- if 0 <= remains && 15 > remains { // 15 后过期
+ remains := (expired - time.Now().UnixMilli()) / 1000 / 60 / 60 / 24
+ if 0 < remains && 15 > remains { // 15 后过期
time.Sleep(3 * time.Minute)
util.PushErrMsg(fmt.Sprintf(Conf.Language(127), remains), 0)
return
diff --git a/kernel/model/upload.go b/kernel/model/upload.go
index 20bd1cdb7..038ae5e5d 100644
--- a/kernel/model/upload.go
+++ b/kernel/model/upload.go
@@ -159,19 +159,7 @@ func Upload(c *gin.Context) {
// 已经存在同样数据的资源文件的话不重复保存
succMap[baseName] = existAsset.Path
} else {
- _, id := util.LastID(fName)
- ext := path.Ext(fName)
- fName = fName[0 : len(fName)-len(ext)]
- if !util.IsIDPattern(id) {
- id = ast.NewNodeID()
- fName = fName + "-" + id + ext
- } else {
- if !util.IsIDPattern(fName) {
- fName = fName[:len(fName)-len(id)-1] + "-" + id + ext
- } else {
- fName = fName + ext
- }
- }
+ fName = util.AssetName(fName)
writePath := filepath.Join(assetsDirPath, fName)
if _, err = f.Seek(0, io.SeekStart); nil != err {
errFiles = append(errFiles, fName)
diff --git a/kernel/server/serve.go b/kernel/server/serve.go
index 1b1c3dc88..55f738cf2 100644
--- a/kernel/server/serve.go
+++ b/kernel/server/serve.go
@@ -122,11 +122,55 @@ func serveAppearance(ginServer *gin.Engine) {
}
siyuan.GET("/appearance/*filepath", func(c *gin.Context) {
filePath := filepath.Join(appearancePath, strings.TrimPrefix(c.Request.URL.Path, "/appearance/"))
- if strings.HasSuffix(c.Request.URL.Path, "/theme.js") && !gulu.File.IsExist(filePath) {
- // 主题 js 不存在时生成空内容返回
- c.Data(200, "application/x-javascript", nil)
- return
+ if strings.HasSuffix(c.Request.URL.Path, "/theme.js") {
+ if !gulu.File.IsExist(filePath) {
+ // 主题 js 不存在时生成空内容返回
+ c.Data(200, "application/x-javascript", nil)
+ return
+ }
+ } else if strings.Contains(c.Request.URL.Path, "/langs/") && strings.HasSuffix(c.Request.URL.Path, ".json") {
+ lang := path.Base(c.Request.URL.Path)
+ lang = strings.TrimSuffix(lang, ".json")
+ if "zh_CN" != lang && "en_US" != lang {
+ // 多语言配置缺失项使用对应英文配置项补齐 https://github.com/siyuan-note/siyuan/issues/5322
+
+ enUSFilePath := filepath.Join(appearancePath, "langs", "en_US.json")
+ enUSData, err := os.ReadFile(enUSFilePath)
+ if nil != err {
+ util.LogFatalf("read en_US.json [%s] failed: %s", enUSFilePath, err)
+ return
+ }
+ enUSMap := map[string]interface{}{}
+ if err = gulu.JSON.UnmarshalJSON(enUSData, &enUSMap); nil != err {
+ util.LogFatalf("unmarshal en_US.json [%s] failed: %s", enUSFilePath, err)
+ return
+ }
+
+ for {
+ data, err := os.ReadFile(filePath)
+ if nil != err {
+ c.JSON(200, enUSMap)
+ return
+ }
+
+ langMap := map[string]interface{}{}
+ if err = gulu.JSON.UnmarshalJSON(data, &langMap); nil != err {
+ util.LogErrorf("unmarshal json [%s] failed: %s", filePath, err)
+ c.JSON(200, enUSMap)
+ return
+ }
+
+ for enUSDataKey, enUSDataValue := range enUSMap {
+ if _, ok := langMap[enUSDataKey]; !ok {
+ langMap[enUSDataKey] = enUSDataValue
+ }
+ }
+ c.JSON(200, langMap)
+ return
+ }
+ }
}
+
c.File(filePath)
})
diff --git a/kernel/util/file.go b/kernel/util/file.go
index 409fabd5c..6fdf2ccab 100644
--- a/kernel/util/file.go
+++ b/kernel/util/file.go
@@ -24,6 +24,7 @@ import (
"strings"
"github.com/88250/gulu"
+ "github.com/88250/lute/ast"
)
func IsEmptyDir(p string) bool {
@@ -47,6 +48,23 @@ func RemoveID(name string) string {
return name + ext
}
+func AssetName(name string) string {
+ _, id := LastID(name)
+ ext := path.Ext(name)
+ name = name[0 : len(name)-len(ext)]
+ if !IsIDPattern(id) {
+ id = ast.NewNodeID()
+ name = name + "-" + id + ext
+ } else {
+ if !IsIDPattern(name) {
+ name = name[:len(name)-len(id)-1] + "-" + id + ext
+ } else {
+ name = name + ext
+ }
+ }
+ return name
+}
+
func LastID(p string) (name, id string) {
name = path.Base(p)
ext := path.Ext(name)
diff --git a/kernel/util/working.go b/kernel/util/working.go
index 01d45032c..3c91a6ef3 100644
--- a/kernel/util/working.go
+++ b/kernel/util/working.go
@@ -64,7 +64,7 @@ func Boot() {
readOnly := flag.Bool("readonly", false, "read-only mode")
accessAuthCode := flag.String("accessAuthCode", "", "access auth code")
ssl := flag.Bool("ssl", false, "for https and wss")
- lang := flag.String("lang", "en_US", "zh_CN/zh_CHT/en_US/fr_FR")
+ lang := flag.String("lang", "en_US", "zh_CN/zh_CHT/en_US/fr_FR/es_ES")
mode := flag.String("mode", "prod", "dev/prod")
flag.Parse()