diff --git a/GUIA_DE_DESARROLLO.md b/GUIA_DE_DESARROLLO.md index dec9de0..a862d62 100644 --- a/GUIA_DE_DESARROLLO.md +++ b/GUIA_DE_DESARROLLO.md @@ -50,7 +50,7 @@ Dónde va cada cosa: | Otro HTML en una pantalla del dev | Función en `views/mobile_ui.js` + una línea en su vista | | Una pantalla nueva | Vista propia en `views/mobile_*.js` + ruta en `fork_routes.js` | | Algo en Ajustes del móvil | `views/mobile_settings.js` | -| Un texto nuevo | `translations/i18n_fork.js`, en los 11 idiomas | +| Un texto nuevo | `translations/i18n_fork.js`, en los 13 idiomas | | Un icono | `views/mobile_icons.js` | | Un workflow (perfil) o la barra de abajo de un perfil | `models/fork_workflows.js` (`FORK_WORKFLOWS` y `BARS`) | | Algo de la APK | `android/scripts/sobre_oficial.sh` o `android/icono/` | @@ -214,6 +214,10 @@ la app del dev ya se aparta de la barra de estado y de la de gestos, y si se sum el CSS el hueco sale dos veces. Se usan `var(--inset-top)` y `var(--inset-bottom)`, que valen 0 y están en `mobile_base.css`. +Botones de solo icono: debajo llevan su nombre en letra de 12 px, sacado de su `title` con +`attr(title)` en CSS (en el móvil no hay hover). El texto es el del dev, ya traducido. Si +un botón nuevo del dev no se entiende y no trae `title`, se le pide al dev. + Zonas táctiles: 44 px como mínimo para botones, fichas y casillas (con su etiqueta); 24 px para los enlaces que van dentro de una línea de texto (nombres, etiquetas). @@ -242,8 +246,9 @@ módulos y la vuelta a Ajustes tras guardar el perfil; se quitó el 20/10/2026 p cualquier cambio suyo lo rompía sin avisar. Ahora Ajustes enlaza a `/profile/edit` y `/feed/create`, y guardar los módulos vuelve a donde decide el dev. -Textos. En `i18n_fork.js` y en los 11 idiomas (en, es, fr, eu, de, it, pt, zh, ar, -hi, ru). Antes, mirad que la clave no exista ya en `oasis_en.js`, porque si existe la +Textos. En `i18n_fork.js` y en los 13 idiomas (en, es, ca, gl, fr, eu, de, it, pt, zh, +ar, hi, ru). Un idioma que falte en `i18n_fork.js` coge el bloque en inglés entero y pisa +las claves que el dev sí tenía traducidas (pasó con catalán y gallego hasta el 22/10). Antes, mirad que la clave no exista ya en `oasis_en.js`, porque si existe la estaríais cambiando también en escritorio. En el código siempre con texto de reserva: `i18n.miClave || 'My text'`. @@ -268,7 +273,9 @@ nombre es el de la ruta: `/tribes` es `tribes`. En emulador x86 la APK no arranca por debajo de Android 12 (el traductor ARM no sabe ejecutar el node del dev), así que el viejo se prueba con el servidor local y el navegador de pruebas del WebView. -- Los textos nuevos, en los 11 idiomas. +- Los textos nuevos, en los 13 idiomas. `node TESTS/idiomas/run.mjs` recorre todas las + pantallas en todos los idiomas buscando scroll de lado, filas que se deslizan y textos + rotos o sin traducir (está en `TESTS/`, que no se sube). - Nada en línea, que la CSP lo bloquea. - Commit en castellano, sin coautorías de herramientas. - Sin claves, contraseñas ni nada de `.ssb`. diff --git a/src/client/assets/styles/mobile_ux.css b/src/client/assets/styles/mobile_ux.css index 1c3f316..a994ee9 100644 --- a/src/client/assets/styles/mobile_ux.css +++ b/src/client/assets/styles/mobile_ux.css @@ -476,7 +476,11 @@ form.oasis-phone-state { position: fixed !important; top: calc(var(--inset-top) + 12px); right: 62px; z-index: 101; /* encima de la barra de arriba (100), debajo de la hoja del menu (1000) */ margin: 0 !important; padding: 0 !important; width: auto !important; } -form.oasis-phone-state .oasis-phone-state-btn { +/* mobile.css lo fija con .oasis-nav-side-toggle ~ .header ...: hace falta mas especificidad */ +form.oasis-phone-state .oasis-phone-state-btn, +body .oasis-nav-side-toggle ~ .header form.oasis-phone-state .oasis-phone-state-btn, +body form.oasis-phone-state button.oasis-phone-state-btn.pm-exposition-chip { + min-width: 44px !important; max-width: 44px !important; max-height: 44px !important; width: 44px !important; height: 44px !important; min-height: 44px !important; padding: 0 !important; margin: 0 !important; display: inline-flex !important; align-items: center; justify-content: center; border-radius: 50% !important; box-shadow: none !important; font-size: 20px !important; @@ -887,3 +891,55 @@ body .oasis-mobile-topbar .omt-logo img, body .oasis-mobile-topbar .omt-avatar i .main-column input[type="datetime-local"], .main-column input[type="date"], .main-column textarea, .main-column select { background-color: var(--a26-field, var(--a26-bg)) !important; } + +/* arabe: el texto de la columna de derecha a izquierda. El dev no pone dir=rtl; las + barras de arriba y abajo se quedan como estan */ +html[lang="ar"] .main-column { direction: rtl; text-align: right; } +html[lang="ar"] .main-column input, html[lang="ar"] .main-column textarea { direction: rtl; } + +/* ---------- feed: la cuenta de difusiones a la izquierda del texto, como en la oficial ---------- + la columna se apilaba encima con un h1 enorme, y .feed-row pintaba otra tarjeta dentro */ +.main-column div.feed-row { display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; align-items: flex-start !important; } +.main-column [class$="-card"] div.feed-row, .main-column [class*="-card "] div.feed-row { + background: transparent !important; border: 0 !important; box-shadow: none !important; padding: 0 !important; margin: 0 !important; +} +.main-column div.feed-row > div.refeed-column { + flex: 0 0 44px !important; width: 44px !important; display: flex !important; flex-direction: column !important; align-items: center !important; + margin: 0 10px 0 0 !important; padding: 0 !important; min-height: 0 !important; +} +.main-column div.refeed-column > h1 { font-size: 18px !important; line-height: 1.2 !important; margin: 0 !important; color: var(--a26-title, var(--ux-fg)) !important; } +.main-column div.feed-row > div.feed-main { flex: 1 1 auto !important; width: auto !important; min-width: 0 !important; margin: 0 !important; padding: 0 !important; } + +/* ---------- menu ⋯ de la tarjeta: el panel con su ancho (lo encogia el max-width:100% del dev) ---------- */ +.main-column details.card-menu > div.card-menu-items { + min-width: 220px !important; max-width: none !important; width: auto !important; right: 0 !important; left: auto !important; + padding: 6px 0 !important; +} +.main-column div.card-menu-items > *, .main-column div.card-menu-items > form > button { + width: 100% !important; max-width: none !important; justify-content: flex-start !important; margin: 0 !important; +} +.main-column div.card-menu-items div.confirm-bar { width: auto !important; max-width: none !important; } +/* dentro del panel, cada accion es una fila (icono y nombre), no el boton redondo */ +.main-column div.card-menu-items .btn-singleview, .main-column div.card-menu-items summary.btn-singleview { + width: 100% !important; height: auto !important; min-height: 48px !important; border-radius: 0 !important; border: 0 !important; + background: transparent !important; justify-content: flex-start !important; padding: 0 14px !important; +} +.main-column div.card-menu-items .btn-singleview::after { content: attr(title); margin-left: 10px; font-size: 15px; font-weight: 600; } +.main-column div.card-menu-items .btn-singleview > .btn-label, .main-column div.card-menu-items .btn-singleview > span:not(.btn-icon) { display: none !important; } + +/* ---------- nombre debajo de los botones redondos de la tarjeta ---------- + en el movil no hay hover y el title no se ve al mantener pulsado: se pinta debajo con + attr(title), que es el texto del dev ya traducido. Solo CSS */ +.main-column div.content-actions > .btn-singleview, +.main-column div.content-actions > form > .btn-singleview, +.main-column div.content-actions > details.card-menu > summary.card-menu-toggle { position: relative !important; overflow: visible !important; } +.main-column div.content-actions > .btn-singleview[title]::after, +.main-column div.content-actions > form > .btn-singleview[title]::after, +.main-column div.content-actions > details.card-menu > summary.card-menu-toggle[title]::after { + content: attr(title); position: absolute; top: calc(100% + 3px); left: 50%; transform: translateX(-50%); + width: 72px; text-align: center; white-space: normal; overflow-wrap: normal; word-break: normal; hyphens: auto; + font-family: var(--ux-text); font-size: 12px; line-height: 1.15; font-weight: 600; letter-spacing: 0; text-transform: none; + color: var(--ux-muted); pointer-events: none; +} +.main-column div.card-header.card-top > div.content-actions { padding-bottom: 32px !important; } +.main-column div.content-actions > * + * { margin-left: 28px !important; } diff --git a/src/client/assets/translations/i18n_fork.js b/src/client/assets/translations/i18n_fork.js index acc09cd..6ad5832 100644 --- a/src/client/assets/translations/i18n_fork.js +++ b/src/client/assets/translations/i18n_fork.js @@ -308,6 +308,282 @@ module.exports = { spreadedDescription: "Lista de posts replicados por habitante.", totalspreads: "Réplicas totales" }, + ca: { + bbMenu: "Menú", + bbMenuClose: "Tanca", + uxPickHint: "Toca'n un i queda desat. El pots canviar quan vulguis a Configuració.", + uxPickCurrent: "En ús", + pubBarJoining: "T'estàs unint al pub… pot trigar fins a un minut. No tanquis l'app.", + pubBarJoinFailed: "Si no has entrat al pub, el més habitual és: no hi ha connexió (wifi o dades), el pub ha caigut o la invitació ja s'ha fet servir. Prova amb un altre pub de la llista o més tard.", + pubBarDownload: "Descarregant la xarxa de {pub} ({tier})…", + pubBarTierRecent: "el més recent", + pubBarTierFull: "tot l'anterior", + pubBarRestore: "Desant al teu mòbil: {pct} % · {n} missatges", + pubBarHint: "Pots continuar fent servir l'app mentrestant.", + pubBarDone: "Ja ets a {pub}: {n} missatges descarregats.", + pubBarErrNoSnap: "T'has unit a {pub}, però no ofereix una còpia per descarregar de cop. El que publiqui la gent anirà arribant de mica en mica mentre l'app estigui oberta.", + pubBarErrNet: "No es pot connectar amb {pub}. Comprova que tens Internet (wifi o dades) i torna-ho a provar d'aquí a una estona; si continua igual, el pub pot haver caigut.", + pubBarErrOther: "La descàrrega des de {pub} s'ha tallat ({error}). Ja hi ets dins; la resta arribarà de mica en mica.", + pubBarClose: "Tanca", + pubBarBackupRestore: "Restaurant la teva còpia: {pct} % · {n} missatges", + pubBarBackupDone: "Còpia restaurada: {n} missatges.", + pubBarBackupErr: "No s'ha pogut restaurar la còpia: {error}. Revisa el fitxer i la contrasenya.", + quickTitle: "El bàsic", + quickHint: "El que vas triar en començar, a un toc.", + quick_identity: "Identitat", + quick_workflow: "Perfil", + quick_ux: "Portada", + quick_colors: "Colors", + quick_community: "Comunitat", + quick_greeting: "Salutació", + quickJoinPub: "Unir-me a un pub", + quickSeePeers: "Amb qui sincronitzes", + quickNoName: "Encara sense nom", + quickEditProfile: "Canviar nom i foto", + quickWriteGreeting: "Escriure la salutació", + welcomeHintColors: "Fons, text i color de realç de l'app.", + colorsHint: "Toca una paleta i s'aplica.", + colorsCustom: "Al teu gust", + colorsBg: "Fons", + colorsFg: "Text", + colorsAccent: "Realç", + colorsApply: "Aplica", + colorsFixed: "El text no es llegia sobre aquest fons, així que s'ha posat en negre o blanc.", + colorsKeep: "Deixar aquests", + palette_oasis: "Estàndard", + palette_acampada: "Plaça", + palette_noche: "Matinada", + palette_bosque: "Hort", + palette_lila: "Violeta", + palette_brasa: "Foguera", + palette_papel: "Fanzín", + palette_contraste: "Pancarta", + welcomeStepUseMobile: "Perfil", + welcomeHintUseMobile: "Per a què fas servir Oasis. Encén els mòduls que calen.", + wfHint_activists: "Assemblees, tribus, votacions, fòrums i documents compartits", + wfHint_acampada: "El just per a una acampada: xats, sales, comissions i votar", + wfHint_full: "Tots els mòduls", + wfHint_default: "Tots els mòduls", + wfHint_social: "Feed, fotos, esdeveniments i amistats", + wfHint_kids: "Escola, jocs i fer coses", + wfHint_night: "Blogs, música, vídeos i xerrada", + wfHint_jobs: "Feina, projectes, mercat i oficina", + wfHint_business: "Economia, banca, oficina i documents", + wfHint_mobile: "El bàsic per al mòbil", + workflow_acampada: "Acampada", + workflow_activista: "Activista", + wfHint_activista: "Per a un col·lectiu: el d'Acampada més campanyes, alertes, enquestes, calendari i tasques", + workflow_minimal: "Mínim", + wfHint_minimal: "Acció directa: convocar, coordinar-se, trucar i avisar", + wfNameMobile_activists: "Moviment", + wfModuleCount: "{n} mòduls", + activityFilter: "Filtra", + tribeSectionsMore: "Més", + tribeSectionsLabel: "Seccions", + voteYes: "Sí", + voteNo: "No", + voteAbstention: "Abstenció", + voteGreen: "Verd · d'acord", + voteYellow: "Groc · dubtes", + voteRed: "Vermell · en contra", + voteOwnNote: "Has creat aquesta votació, així que no hi pots votar: qui proposa es queda neutral.", + voteKindLabel: "Tipus de votació", + voteKind_classic: "Sí / No", + voteKindHint_classic: "Sí, no, abstenció i les opcions de sempre", + voteKind_traffic: "Semàfor", + voteKindHint_traffic: "Verd d'acord · groc dubtes · vermell en contra, com en assemblea", + voteKind_scale: "Termòmetre", + voteKindHint_scale: "De l'1 (fred) al 5 (calent)", + deleteConfirmQuestion: "Esborrar per sempre?", + deleteConfirmYes: "Sí, esborra", + welcomeBannerTextMobile: "Prepara Oasis en un minut.", + welcomeBannerActionMobile: "Començar", + footerPeersNone: "Encara no sincronitzes amb ningú", + footerPeersSome: "Sincronitzant amb {n}", + tribeMapUrlLabel: "Enllaç del mapa (opcional)", + tribeHintPrivacy: "Privada: només la veuen els seus membres i va xifrada. Pública: la veu tota la xarxa.", + tribeHintMode: "Oberta: qualsevol membre pot convidar. Estricta: només convida qui la va crear.", + modulesOnCount: "{on} de {total} seccions actives", + skinIntroMobile: "Tria com es veu Oasis al teu mòbil. S'aplica en tocar-lo.", + workflowsDescriptionMobile: "Cada perfil encén uns mòduls i porta la seva barra de baix. Van de menys a més; canvia'l quan vulguis.", + imageMetadataUnsafe: "No s'han pogut treure les metadades d'aquesta imatge (ubicació, càmera…), així que no s'ha pujat. Desa-la com a JPEG o PNG i torna-ho a provar.", + identitiesActive: "En ús", + identitiesBackup: "Exportar o importar una identitat", + identitiesCreate: "Crear identitat", + identitiesDescription: "Cada identitat viu a la seva pròpia carpeta, amb la seva clau, la seva base de dades i els seus blobs. El canvi s'aplica en reiniciar.", + identitiesInvalidName: "Nom no vàlid. Lletres, números, guió i guió baix, fins a 32.", + identitiesNewName: "Nom de la nova identitat", + identitiesRestart: "Reinicia ara", + identitiesRestartNeeded: "Seleccionada. Reinicia Oasis per entrar amb aquesta identitat.", + identitiesRestarting: "Reiniciant. Torna a obrir Oasis d'aquí a uns segons.", + identitiesSameFeed: "Comparteix clau amb una altra identitat: no publiquis mai des de totes dues.", + identitiesTitle: "Identitats", + identitiesUse: "Fer servir aquesta", + bbAdd: "Afegeix", + bbDone: "Fet", + bbFull: "Màxim assolit", + bbManage: "Edita", + bbPickDesc: "Tria fins a 4 accessos directes per a la barra inferior.", + bbPickNone: "Cap", + bbPickTitle: "Personalitzar la barra", + bbQuickActions: "Accions ràpides", + bbRemove: "Treu", + bbYourBar: "La teva barra", + menuAll: "Tot", + menuBlogs: "Blogs", + menuSocial: "Social", + bookmarkCategoryLabel: "Categoria", + bookmarkCategoryPlaceholder: "Opcional", + chatReply: "Respon", + chatShareUrl: "Comparteix l'enllaç", + filter: "Filtra", + forumFilterHot: "Candent", + peerLastChange: "Últim canvi", + profileVisibilityDevice: "Dispositiu", + publishBlog: "Publicar blog", + spreadHint: "Difon això entre qui et segueix (es replica pel teu feed).", + spreadMore: "més", + spreadLabel: "Difondre", + totalspreads: "Rèpliques totals", + }, + gl: { + bbMenu: "Menú", + bbMenuClose: "Pechar", + uxPickHint: "Toca un e queda gardado. Podes cambialo cando queiras en Axustes.", + uxPickCurrent: "En uso", + pubBarJoining: "Uníndote ao pub… pode tardar ata un minuto. Non peches a app.", + pubBarJoinFailed: "Se non entraches no pub, o máis habitual é: non hai conexión (wifi ou datos), o pub está caído ou o convite xa se usou. Proba con outro pub da lista ou máis tarde.", + pubBarDownload: "Descargando a rede de {pub} ({tier})…", + pubBarTierRecent: "o máis recente", + pubBarTierFull: "todo o anterior", + pubBarRestore: "Gardando no teu móbil: {pct} % · {n} mensaxes", + pubBarHint: "Podes seguir usando a app mentres tanto.", + pubBarDone: "Xa estás en {pub}: {n} mensaxes descargadas.", + pubBarErrNoSnap: "Uníchete a {pub}, pero non ofrece unha copia para descargar dunha vez. O que publique a xente irá chegando aos poucos mentres a app estea aberta.", + pubBarErrNet: "Non se pode conectar con {pub}. Comproba que tes Internet (wifi ou datos) e volve probar nun anaco; se segue igual, o pub pode estar caído.", + pubBarErrOther: "A descarga desde {pub} cortouse ({error}). Xa estás dentro; o demais chegará aos poucos.", + pubBarClose: "Pechar", + pubBarBackupRestore: "Restaurando a túa copia: {pct} % · {n} mensaxes", + pubBarBackupDone: "Copia restaurada: {n} mensaxes.", + pubBarBackupErr: "Non se puido restaurar a copia: {error}. Revisa o ficheiro e o contrasinal.", + quickTitle: "O básico", + quickHint: "O que escolliches ao comezar, a un toque.", + quick_identity: "Identidade", + quick_workflow: "Perfil", + quick_ux: "Portada", + quick_colors: "Cores", + quick_community: "Comunidade", + quick_greeting: "Saúdo", + quickJoinPub: "Unirme a un pub", + quickSeePeers: "Con quen sincronizas", + quickNoName: "Aínda sen nome", + quickEditProfile: "Cambiar nome e foto", + quickWriteGreeting: "Escribir o saúdo", + welcomeHintColors: "Fondo, texto e cor de realce da app.", + colorsHint: "Toca unha paleta e aplícase.", + colorsCustom: "Ao teu gusto", + colorsBg: "Fondo", + colorsFg: "Texto", + colorsAccent: "Realce", + colorsApply: "Aplicar", + colorsFixed: "O texto non se lía sobre ese fondo, así que se puxo en negro ou branco.", + colorsKeep: "Deixar estas", + palette_oasis: "Estándar", + palette_acampada: "Praza", + palette_noche: "Madrugada", + palette_bosque: "Horta", + palette_lila: "Violeta", + palette_brasa: "Fogueira", + palette_papel: "Fanzine", + palette_contraste: "Pancarta", + welcomeStepUseMobile: "Perfil", + welcomeHintUseMobile: "Para que usas Oasis. Acende os módulos que fan falta.", + wfHint_activists: "Asembleas, tribos, votacións, foros e documentos compartidos", + wfHint_acampada: "O xusto para unha acampada: chats, salas, comisións e votar", + wfHint_full: "Todos os módulos", + wfHint_default: "Todos os módulos", + wfHint_social: "Feed, fotos, eventos e amizades", + wfHint_kids: "Escola, xogos e facer cousas", + wfHint_night: "Blogs, música, vídeos e conversa", + wfHint_jobs: "Traballo, proxectos, mercado e oficina", + wfHint_business: "Economía, banca, oficina e documentos", + wfHint_mobile: "O básico para o móbil", + workflow_acampada: "Acampada", + workflow_activista: "Activista", + wfHint_activista: "Para un colectivo: o de Acampada máis campañas, alertas, enquisas, calendario e tarefas", + workflow_minimal: "Mínimo", + wfHint_minimal: "Acción directa: convocar, coordinarse, chamar e avisar", + wfNameMobile_activists: "Movemento", + wfModuleCount: "{n} módulos", + activityFilter: "Filtrar", + tribeSectionsMore: "Máis", + tribeSectionsLabel: "Seccións", + voteYes: "Si", + voteNo: "Non", + voteAbstention: "Abstención", + voteGreen: "Verde · de acordo", + voteYellow: "Amarelo · dúbidas", + voteRed: "Vermello · en contra", + voteOwnNote: "Creaches esta votación, así que non podes votar nela: quen propón queda neutral.", + voteKindLabel: "Tipo de votación", + voteKind_classic: "Si / Non", + voteKindHint_classic: "Si, non, abstención e as opcións de sempre", + voteKind_traffic: "Semáforo", + voteKindHint_traffic: "Verde de acordo · amarelo dúbidas · vermello en contra, como en asemblea", + voteKind_scale: "Termómetro", + voteKindHint_scale: "Do 1 (frío) ao 5 (quente)", + deleteConfirmQuestion: "Borrar para sempre?", + deleteConfirmYes: "Si, borrar", + welcomeBannerTextMobile: "Prepara Oasis nun minuto.", + welcomeBannerActionMobile: "Comezar", + footerPeersNone: "Aínda non sincronizas con ninguén", + footerPeersSome: "Sincronizando con {n}", + tribeMapUrlLabel: "Ligazón do mapa (opcional)", + tribeHintPrivacy: "Privada: só a ven os seus membros e vai cifrada. Pública: vea toda a rede.", + tribeHintMode: "Aberta: calquera membro pode convidar. Estrita: só convida quen a creou.", + modulesOnCount: "{on} de {total} seccións activas", + skinIntroMobile: "Escolle como se ve Oasis no teu móbil. Aplícase ao tocalo.", + workflowsDescriptionMobile: "Cada perfil acende uns módulos e trae a súa barra de abaixo. Van de menos a máis; cámbiao cando queiras.", + imageMetadataUnsafe: "Non se puideron quitar os metadatos desta imaxe (localización, cámara…), así que non se subiu. Gárdaa como JPEG ou PNG e volve tentalo.", + identitiesActive: "En uso", + identitiesBackup: "Exportar ou importar unha identidade", + identitiesCreate: "Crear identidade", + identitiesDescription: "Cada identidade vive no seu propio cartafol, coa súa clave, a súa base de datos e os seus blobs. O cambio aplícase ao reiniciar.", + identitiesInvalidName: "Nome non válido. Letras, números, guión e guión baixo, ata 32.", + identitiesNewName: "Nome da nova identidade", + identitiesRestart: "Reiniciar agora", + identitiesRestartNeeded: "Seleccionada. Reinicia Oasis para entrar con esta identidade.", + identitiesRestarting: "Reiniciando. Volve abrir Oasis nuns segundos.", + identitiesSameFeed: "Comparte clave con outra identidade: non publiques nunca desde as dúas.", + identitiesTitle: "Identidades", + identitiesUse: "Usar esta", + bbAdd: "Engadir", + bbDone: "Feito", + bbFull: "Máximo alcanzado", + bbManage: "Editar", + bbPickDesc: "Escolle ata 4 accesos directos para a barra inferior.", + bbPickNone: "Ningún", + bbPickTitle: "Personalizar a barra", + bbQuickActions: "Accións rápidas", + bbRemove: "Quitar", + bbYourBar: "A túa barra", + menuAll: "Todo", + menuBlogs: "Blogs", + menuSocial: "Social", + bookmarkCategoryLabel: "Categoría", + bookmarkCategoryPlaceholder: "Opcional", + chatReply: "Responder", + chatShareUrl: "Compartir ligazón", + filter: "Filtrar", + forumFilterHot: "Candente", + peerLastChange: "Último cambio", + profileVisibilityDevice: "Dispositivo", + publishBlog: "Publicar blog", + spreadHint: "Difunde isto entre quen te segue (replícase polo teu feed).", + spreadMore: "máis", + spreadLabel: "Difundir", + totalspreads: "Réplicas totais", + }, fr: { // usabilidad (19/10/2026): votaciones, borrar, modulos, pie bbMenu: "Menu",