Files
chat/frontend/src/lib/i18n.ts
T
2026-09-21 16:34:14 +02:00

2185 lines
97 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useSyncExternalStore } from "react";
/**
* Platform i18n — TypeScript dictionaries (NOT JSON).
* English is the source of truth: `StringKey = keyof typeof en`, and every
* other language is typed `Record<StringKey, string>`, so a missing
* translation is a COMPILE error. Fallback chain: current lang -> English.
*/
export const LANGS = [
{ id: "en", label: "English" },
{ id: "es", label: "Español" },
{ id: "fr", label: "Français" },
{ id: "ru", label: "Русский" },
{ id: "it", label: "Italiano" },
{ id: "pt", label: "Português" },
] as const;
export type LangId = (typeof LANGS)[number]["id"];
const en = {
"common.close": "Close",
"common.cancel": "Cancel",
"common.save": "Save",
"common.done": "Done",
"common.copy": "Copy",
"common.copied": "Copied",
"common.create": "Create",
"common.delete": "Delete",
"common.loading": "Loading…",
"common.saving": "Saving…",
"common.username": "Username",
"common.displayName": "Display name",
"common.select": "Select…",
"common.theme": "Theme",
"common.themeAuto": "Auto",
"common.themeAutoFull": "Auto (follows system)",
"common.light": "Light",
"common.dark": "Dark",
"common.saveFailed": "Save failed",
"common.backToLogin": "Back to login",
"loading.tagline": "Preparing your chats",
"login.welcome": "Welcome back",
"login.subtitle": "Sign in to KisAssistant to continue",
"login.usernamePh": "Username",
"login.keyPh": "Access key",
"login.failed": "Login failed",
"login.unlock": "Unlock",
"login.newHere": "New here?",
"login.register": "Register",
"login.regTipOn": "Create an account",
"login.regTipOff": "Registration currently disabled on this platform",
"login.requestAccess": "Request access",
"login.forgot": "Forgot your access key?",
"login.perk1": "Private by design — no cookies, no tracking",
"login.perk2": "Your own models, streamed in real time",
"login.adminNotePre": "Admin key lives in the server ",
"login.adminNotePost": "; user keys are issued by the admin.",
"login.totpIntro": "Two-factor for @{user} — enter the 6-digit code from your authenticator app.",
"login.totpAria": "Authenticator code",
"login.totpInvalid": "Invalid code",
"login.verify": "Verify",
"sidebar.newChat": "New chat",
"sidebar.collapse": "Collapse sidebar",
"sidebar.closeDrawer": "Close chats",
"sidebar.searchPh": "Search chats…",
"sidebar.searchAria": "Search conversations",
"sidebar.clearSearch": "Clear search",
"sidebar.searching": "Searching…",
"sidebar.noMatch": "No match for “{q}”.",
"sidebar.youPrefix": "you: ",
"sidebar.emptyA": "No conversations yet.",
"sidebar.emptyB": "Start a new chat above.",
"sidebar.rename": "Rename",
"sidebar.topic": "Set topic",
"sidebar.share": "Share publicly",
"sidebar.delete": "Delete",
"sidebar.convOptions": "Conversation options",
"sidebar.admin": "admin",
"sidebar.adminCenter": "Admin Center",
"sidebar.adminCenterTip": "Open the Admin Center",
"sidebar.settingsTip": "Profile & settings",
"sidebar.logout": "Log out",
"chat.expand": "Expand sidebar",
"chat.openNav": "Open chats",
"chat.modelAria": "Model",
"chat.noModels": "No models",
"chat.selectModel": "Select model…",
"chat.you": "You",
"chat.greet": "How can I help, {name}?",
"chat.greetSub": "Pick a model, then type below. Markdown is supported.",
"chat.thinking": "Thinking…",
"chat.placeholder": "Message KisAssistant…",
"chat.send": "Send",
"chat.attachTip": "Attach a file",
"chat.removeAttach": "Remove {name}",
"chat.ocrOpt": "Transcribe image",
"chat.unavailable": "(unavailable)",
"chat.ocrHint":
"JPG, PNG, WEBP — OCR on the server, you review the text first",
"chat.textOpt": "Attach text file",
"chat.textHint": "TXT or Markdown, max 500KB",
"chat.attachNote":
"Uploads go through platform tools only. Images are sent to the model as reviewed text — never as raw images.",
"chat.wiki": "Wiki & docs",
"attach.tooBigImg": "Image too large (max 5MB).",
"attach.tooBigText": "Text file too large (max 500KB).",
"attach.uploadFailed": "Upload failed",
"attach.kindOcr": "image transcription",
"attach.kindText": "text file",
"attach.tpl": "[attached {kind}: {name}]",
"attach.chatError": "**Error:** the model did not respond. Try again.",
"dlg.editConv": "Edit conversation",
"dlg.fTitle": "Title",
"dlg.fTopic": "Topic",
"dlg.topicHint": "A short theme tag, e.g. cooking, rust, travel.",
"dlg.noTopic": "No topic",
"dlg.recoverTitle": "Recover access key",
"dlg.recoverIntro":
"Enter your username. You'll receive a link that issues a fresh key (the old one stops working).",
"dlg.sendLink": "Send reset link",
"dlg.reqFailed": "Request failed",
"dlg.recoverSent":
"If @{user} exists and has a recovery email, a reset link is on its way{fromPart} (valid 60 minutes, single use).",
"dlg.recoverSentFrom": " from {from}",
"dlg.spamNote":
"Check your inbox — and your spam folder, the message may have landed there.",
"dlg.noMailNote":
"Nothing arrives? Then no recovery email is set on your account and the reset is not possible — please contact the site administrator.",
"dlg.registerTitle": "Create an account",
"dlg.regWelcome":
"Welcome, @{user}! Your access key — copy it now, it won't be shown again.",
"dlg.regContinue": "Copy & continue to login",
"dlg.regFailed": "Registration failed",
"dlg.emailOpt": "Email (optional, for key recovery)",
"dlg.shareTitle": "Share publicly",
"dlg.shareCreating": "Creating public link…",
"dlg.shareIntro":
"Anyone with this link can read {title}. No login required.",
"dlg.unshare": "Unshare",
"dlg.reviewOcr": "Review transcription",
"dlg.reviewText": "Review text attachment",
"dlg.transcribing": "Transcribing the image on the server (OCR)…",
"dlg.uploadingTools": "Uploading through platform tools…",
"dlg.ocrExpl":
"The image was transcribed to text on the server — the model receives this text, never the image. Fix any reading mistakes below, then attach.",
"dlg.textExpl":
"The file was uploaded through platform tools. Its text content is attached to your message. Edit below if needed, then attach.",
"dlg.truncWarn":
"Transcription truncated to the server limit — the end is missing.",
"dlg.attachBtn": "Attach to message",
"dlg.chars": "{n} chars",
"settings.title": "Settings",
"settings.searchPh": "Search settings…",
"settings.searchAria": "Search settings",
"settings.clearSearch": "Clear settings search",
"settings.noMatch": "No settings match “{q}”.",
"settings.profile": "Profile",
"settings.appearance": "Appearance",
"settings.features": "Features",
"settings.avatarUpload": "Avatar upload",
"settings.dropIdle": "Drop an image or click to upload",
"settings.dropActive": "Drop it!",
"settings.uploading": "Uploading…",
"settings.avatarHint": "jpg · png · webp — max 5MB — 5 changes per 2h",
"settings.email": "Recovery email",
"settings.emailHint":
"Optional. Used only to send you a fresh access key if you lose it.",
"settings.accent": "Accent color",
"settings.accentHint": "Applied instantly, saved on this device.",
"settings.customColor": "Custom color",
"settings.customAria": "Custom accent color",
"settings.accentAria": "Accent: {label}",
"settings.featHint":
"Turn composer capabilities on or off. Applied instantly, saved on this device.",
"accent.preset.emerald": "Emerald",
"accent.preset.rose": "Rose",
"accent.preset.blue": "Blue",
"accent.preset.violet": "Violet",
"accent.preset.amber": "Yellow",
"accent.preset.orange": "Orange",
"accent.preset.teal": "Teal",
"accent.preset.pink": "Pink",
"feat.thinking.label": "Thinking",
"feat.thinking.hint":
"Ask the model to reason step-by-step before answering.",
"feat.attachments.label": "Attachments",
"feat.attachments.hint":
"Attach images (OCR transcription) and text files to messages.",
"feat.deepSearch.label": "Deep search",
"feat.deepSearch.hint":
"Let the assistant search the web for fresh sources. Coming soon.",
"feat.soon": "(soon)",
"admin.title": "Accounts",
"admin.intro":
"No-KYC accounts: you create a username, hand the access key to its owner once. Keys are stored hashed — regenerate to rotate.",
"admin.keyOnce": "Key for @{user} — shown once, copy it now:",
"admin.newAccount": "New account",
"admin.email": "Email (recovery)",
"admin.createFailed": "Create failed",
"admin.regenFailed": "Regenerate failed",
"admin.deleteFailed": "Delete failed",
"admin.loadFailed": "Load failed",
"admin.searchPh": "Search accounts…",
"admin.searchAria": "Search accounts",
"admin.sortAria": "Sort accounts",
"admin.filterAria": "Filter accounts",
"admin.sortNewest": "Newest first",
"admin.sortOldest": "Oldest first",
"admin.sortAz": "A → Z",
"admin.sortZa": "Z → A",
"admin.filterAll": "All accounts",
"admin.filterWith": "With recovery email",
"admin.filterNo": "No recovery email",
"admin.countMany": "{total} accounts · page {page} of {pages}",
"admin.countOne": "{total} account · page {page} of {pages}",
"admin.noEmail": "no recovery email",
"admin.editProfile": "Edit profile",
"admin.regenKey": "Regenerate key (revokes old sessions)",
"admin.deleteAccount": "Delete account",
"admin.deleteTitle": "Delete @{user}?",
"admin.deleteMsg":
"This permanently removes the account, all its conversations and its shares.",
"admin.prevPage": "Previous page",
"admin.nextPage": "Next page",
"ollama.models": "Ollama models",
"ollama.desc":
"Download models from the Ollama library to your Ollama host, or remove local ones. Admin only.",
"ollama.pulling": "Pulling {name}",
"ollama.customTag": "Custom model tag",
"ollama.customHint": "e.g. llama3.2:3b, or a full library name",
"ollama.pull": "Pull",
"ollama.installed": "Installed ({n})",
"ollama.noneYet": "No local Ollama models yet.",
"ollama.catalog": "Catalog",
"ollama.delTitle": "Delete {name}?",
"ollama.delMsg":
"The model is removed from the Ollama host. Pull it again anytime.",
"ollama.contacting": "Contacting Ollama…",
"ollama.pullingSt": "Pulling…",
"ollama.doneSt": "Done",
"ollama.pullFailed": "Pull failed",
"ollama.delTip": "Delete {name}",
"ollama.sizeAria": "{name} size",
"ollama.cat.llama3_2": "Meta's compact everyday model",
"ollama.cat.llama3_1": "Meta's general-purpose workhorse",
"ollama.cat.qwen2_5": "Alibaba's multilingual all-rounder",
"ollama.cat.gemma2": "Google's efficient open model",
"ollama.cat.mistral": "Mistral 7B — fast and capable",
"ollama.cat.phi3": "Microsoft's small research model",
"ollama.cat.deepseek_r1": "Reasoning model (chain-of-thought)",
"ollama.cat.codellama": "Code completion and chat",
"ollama.cat.llava": "Vision model (image understanding)",
"ollama.cat.nomic_embed_text": "Text embeddings",
"center.title": "Admin Center",
"center.back": "Back to chat",
"center.tabAccounts": "Accounts",
"center.tabFeatures": "Features",
"center.tabMail": "Mail",
"center.locked": "Admins only",
"center.lockedSub": "Log in with the admin account to open the Admin Center.",
"center.regTitle": "Public registration",
"center.regDesc":
"Show the Register button on the login page. Off = admin-created accounts only.",
"center.accTitle": "Access requests (wishlist)",
"center.accDescOn":
"Unavailable while public registration is on — it replaces the wishlist.",
"center.accDescOff":
"Show Request access on the login page. Visitors reserve a username and plead their case; you triage them in the Access tab.",
"center.ocrTitle": "OCR tool",
"center.ocrDesc":
"Server-side image transcription for attachments. Requires the tesseract binary.",
"center.mailTitle": "Key recovery emails",
"center.mailChecking": "Checking…",
"center.mailFrom": "Enabled — sent from {from}",
"center.mailDefault": "Enabled — sent from the configured sender",
"center.mailOff": "Disabled — set SMTP_HOST on the server to enable",
"center.mailNote":
"SMTP credentials live in the server environment (see docs/OPERATIONS.md), never in this UI. Users set their recovery address in profile settings or at account creation.",
"center.on": "On",
"center.off": "Off",
"share.fallback": "Shared chat",
"share.open": "Open KisAssistant",
"share.gone": "This shared chat is gone",
"share.goneSub": "The link is invalid, expired, or the author unshared it.",
"share.back": "Go back",
"share.loading": "Loading shared chat…",
"share.by": "by {name}",
"reset.checking": "Checking link…",
"reset.invalid": "Link invalid",
"reset.invalidSub":
"This reset link is unknown, already used, or expired. Request a new one from the login page.",
"reset.readyTitle": "Reset key for @{user}?",
"reset.readySub":
"This issues a fresh access key immediately. Your old key stops working.",
"reset.issue": "Issue new key",
"reset.issuing": "Issuing…",
"reset.doneTitle": "New key ready",
"reset.doneSub":
"Copy it now — it won't be shown again. Then log in with it.",
"reset.goLogin": "Go to login",
"reset.failed": "Reset failed",
"app.deleteConvTitle": "Delete conversation?",
"app.deleteConvMsg":
"“{title}” and all its messages will be permanently removed.",
} as const;
export type StringKey = keyof typeof en;
type Dict = Record<StringKey, string>;
const es: Dict = {
"common.close": "Cerrar",
"common.cancel": "Cancelar",
"common.save": "Guardar",
"common.done": "Listo",
"common.copy": "Copiar",
"common.copied": "Copiado",
"common.create": "Crear",
"common.delete": "Eliminar",
"common.loading": "Cargando…",
"common.saving": "Guardando…",
"common.username": "Nombre de usuario",
"common.displayName": "Nombre visible",
"common.select": "Elegir…",
"common.theme": "Tema",
"common.themeAuto": "Automático",
"common.themeAutoFull": "Automático (según el sistema)",
"common.light": "Claro",
"common.dark": "Oscuro",
"common.saveFailed": "No se pudo guardar",
"common.backToLogin": "Volver al inicio de sesión",
"loading.tagline": "Preparando tus chats",
"login.welcome": "Bienvenido de nuevo",
"login.subtitle": "Inicia sesión en KisAssistant para continuar",
"login.usernamePh": "Nombre de usuario",
"login.keyPh": "Clave de acceso",
"login.failed": "Error de inicio de sesión",
"login.unlock": "Desbloquear",
"login.newHere": "¿Nuevo por aquí?",
"login.register": "Registrarse",
"login.regTipOn": "Crear una cuenta",
"login.regTipOff": "El registro está desactivado en esta plataforma",
"login.requestAccess": "Solicitar acceso",
"login.forgot": "¿Olvidaste tu clave de acceso?",
"login.perk1": "Privado por diseño — sin cookies ni rastreo",
"login.perk2": "Tus propios modelos, en tiempo real",
"login.adminNotePre": "La clave de admin vive en el .env del servidor",
"login.adminNotePost": "; las claves de usuario las emite el admin.",
"login.totpIntro": "Doble factor para @{user} — escribe el código de 6 dígitos de tu app.",
"login.totpAria": "Código de autenticación",
"login.totpInvalid": "Código inválido",
"login.verify": "Verificar",
"sidebar.newChat": "Nuevo chat",
"sidebar.collapse": "Ocultar barra lateral",
"sidebar.closeDrawer": "Cerrar chats",
"sidebar.searchPh": "Buscar chats…",
"sidebar.searchAria": "Buscar conversaciones",
"sidebar.clearSearch": "Borrar búsqueda",
"sidebar.searching": "Buscando…",
"sidebar.noMatch": "Sin resultados para «{q}».",
"sidebar.youPrefix": "tú: ",
"sidebar.emptyA": "Aún no hay conversaciones.",
"sidebar.emptyB": "Empieza un nuevo chat arriba.",
"sidebar.rename": "Renombrar",
"sidebar.topic": "Definir tema",
"sidebar.share": "Compartir públicamente",
"sidebar.delete": "Eliminar",
"sidebar.convOptions": "Opciones de conversación",
"sidebar.admin": "admin",
"sidebar.adminCenter": "Centro de admin",
"sidebar.adminCenterTip": "Abrir el Centro de admin",
"sidebar.settingsTip": "Perfil y ajustes",
"sidebar.logout": "Cerrar sesión",
"chat.expand": "Mostrar barra lateral",
"chat.openNav": "Abrir chats",
"chat.modelAria": "Modelo",
"chat.noModels": "Sin modelos",
"chat.selectModel": "Elegir modelo…",
"chat.you": "Tú",
"chat.greet": "¿En qué te ayudo, {name}?",
"chat.greetSub": "Elige un modelo y escribe abajo. Markdown soportado.",
"chat.thinking": "Pensando…",
"chat.placeholder": "Escribe a KisAssistant…",
"chat.send": "Enviar",
"chat.attachTip": "Adjuntar un archivo",
"chat.removeAttach": "Quitar {name}",
"chat.ocrOpt": "Transcribir imagen",
"chat.unavailable": "(no disponible)",
"chat.ocrHint": "JPG, PNG, WEBP — OCR en el servidor, revisas el texto antes",
"chat.textOpt": "Adjuntar archivo de texto",
"chat.textHint": "TXT o Markdown, máx. 500KB",
"chat.attachNote":
"Las subidas usan solo herramientas de la plataforma. Las imágenes llegan al modelo como texto revisado — nunca como imágenes.",
"chat.wiki": "Wiki y docs",
"attach.tooBigImg": "Imagen muy grande (máx. 5MB).",
"attach.tooBigText": "Archivo muy grande (máx. 500KB).",
"attach.uploadFailed": "Error al subir",
"attach.kindOcr": "transcripción de imagen",
"attach.kindText": "archivo de texto",
"attach.tpl": "[adjunto {kind}: {name}]",
"attach.chatError": "**Error:** el modelo no respondió. Inténtalo de nuevo.",
"dlg.editConv": "Editar conversación",
"dlg.fTitle": "Título",
"dlg.fTopic": "Tema",
"dlg.topicHint": "Etiqueta corta, p. ej. cocina, rust, viajes.",
"dlg.noTopic": "Sin tema",
"dlg.recoverTitle": "Recuperar clave de acceso",
"dlg.recoverIntro":
"Escribe tu usuario. Recibirás un enlace que emite una clave nueva (la anterior deja de funcionar).",
"dlg.sendLink": "Enviar enlace",
"dlg.reqFailed": "Solicitud fallida",
"dlg.recoverSent":
"Si @{user} existe y tiene email de recuperación, un enlace va en camino{fromPart} (válido 60 minutos, un solo uso).",
"dlg.recoverSentFrom": " desde {from}",
"dlg.spamNote": "Revisa tu bandeja — y el spam, puede haber caído allí.",
"dlg.noMailNote":
"¿No llega nada? Entonces no hay email de recuperación en tu cuenta y no es posible — contacta al administrador del sitio.",
"dlg.registerTitle": "Crear una cuenta",
"dlg.regWelcome":
"¡Bienvenido, @{user}! Tu clave de acceso — cópiala ahora, no se mostrará de nuevo.",
"dlg.regContinue": "Copiar e ir al login",
"dlg.regFailed": "Registro fallido",
"dlg.emailOpt": "Email (opcional, para recuperar la clave)",
"dlg.shareTitle": "Compartir públicamente",
"dlg.shareCreating": "Creando enlace público…",
"dlg.shareIntro": "Cualquiera con este enlace puede leer {title}. Sin login.",
"dlg.unshare": "Dejar de compartir",
"dlg.reviewOcr": "Revisar transcripción",
"dlg.reviewText": "Revisar adjunto de texto",
"dlg.transcribing": "Transcribiendo la imagen en el servidor (OCR)…",
"dlg.uploadingTools": "Subiendo con herramientas de la plataforma…",
"dlg.ocrExpl":
"La imagen se transcribió a texto en el servidor — el modelo recibe este texto, nunca la imagen. Corrige errores abajo y adjunta.",
"dlg.textExpl":
"El archivo se subió con herramientas de la plataforma. Su texto se adjunta a tu mensaje. Edita si quieres y adjunta.",
"dlg.truncWarn":
"Transcripción recortada al límite del servidor — falta el final.",
"dlg.attachBtn": "Adjuntar al mensaje",
"dlg.chars": "{n} caracteres",
"settings.title": "Ajustes",
"settings.searchPh": "Buscar ajustes…",
"settings.searchAria": "Buscar ajustes",
"settings.clearSearch": "Borrar búsqueda",
"settings.noMatch": "Sin ajustes para «{q}».",
"settings.profile": "Perfil",
"settings.appearance": "Apariencia",
"settings.features": "Funciones",
"settings.avatarUpload": "Subir avatar",
"settings.dropIdle": "Suelta una imagen o haz clic para subir",
"settings.dropActive": "¡Suéltala!",
"settings.uploading": "Subiendo…",
"settings.avatarHint": "jpg · png · webp — máx. 5MB — 5 cambios por 2h",
"settings.email": "Email de recuperación",
"settings.emailHint":
"Opcional. Solo para enviarte una clave nueva si la pierdes.",
"settings.accent": "Color de acento",
"settings.accentHint":
"Se aplica al instante, se guarda en este dispositivo.",
"settings.customColor": "Color personalizado",
"settings.customAria": "Color de acento personalizado",
"settings.accentAria": "Acento: {label}",
"settings.featHint":
"Activa funciones del editor. Al instante, en este dispositivo.",
"accent.preset.emerald": "Esmeralda",
"accent.preset.rose": "Rosa",
"accent.preset.blue": "Azul",
"accent.preset.violet": "Violeta",
"accent.preset.amber": "Amarillo",
"accent.preset.orange": "Naranja",
"accent.preset.teal": "Turquesa",
"accent.preset.pink": "Rosa fuerte",
"feat.thinking.label": "Razonamiento",
"feat.thinking.hint":
"Pide al modelo razonar paso a paso antes de responder.",
"feat.attachments.label": "Adjuntos",
"feat.attachments.hint":
"Adjunta imágenes (transcripción OCR) y archivos de texto.",
"feat.deepSearch.label": "Búsqueda profunda",
"feat.deepSearch.hint":
"Deja al asistente buscar fuentes frescas. Próximamente.",
"feat.soon": "(pronto)",
"admin.title": "Cuentas",
"admin.intro":
"Cuentas sin KYC: creas un usuario, entregas la clave una vez. Se guardan con hash — regenera para rotar.",
"admin.keyOnce": "Clave para @{user} — se muestra una vez, cópiala ahora:",
"admin.newAccount": "Nueva cuenta",
"admin.email": "Email (recuperación)",
"admin.createFailed": "No se pudo crear",
"admin.regenFailed": "No se pudo regenerar",
"admin.deleteFailed": "No se pudo eliminar",
"admin.loadFailed": "No se pudo cargar",
"admin.searchPh": "Buscar cuentas…",
"admin.searchAria": "Buscar cuentas",
"admin.sortAria": "Ordenar cuentas",
"admin.filterAria": "Filtrar cuentas",
"admin.sortNewest": "Más nuevas",
"admin.sortOldest": "Más viejas",
"admin.sortAz": "A → Z",
"admin.sortZa": "Z → A",
"admin.filterAll": "Todas las cuentas",
"admin.filterWith": "Con email de recuperación",
"admin.filterNo": "Sin email de recuperación",
"admin.countMany": "{total} cuentas · página {page} de {pages}",
"admin.countOne": "{total} cuenta · página {page} de {pages}",
"admin.noEmail": "sin email de recuperación",
"admin.editProfile": "Editar perfil",
"admin.regenKey": "Regenerar clave (revoca sesiones)",
"admin.deleteAccount": "Eliminar cuenta",
"admin.deleteTitle": "¿Eliminar @{user}?",
"admin.deleteMsg": "Esto elimina la cuenta, sus conversaciones y sus shares.",
"admin.prevPage": "Página anterior",
"admin.nextPage": "Página siguiente",
"ollama.models": "Modelos Ollama",
"ollama.desc":
"Descarga modelos de la librería Ollama a tu host, o elimina los locales. Solo admin.",
"ollama.pulling": "Descargando {name}",
"ollama.customTag": "Etiqueta personalizada",
"ollama.customHint": "p. ej. llama3.2:3b, o un nombre de librería",
"ollama.pull": "Descargar",
"ollama.installed": "Instalados ({n})",
"ollama.noneYet": "Aún no hay modelos Ollama locales.",
"ollama.catalog": "Catálogo",
"ollama.delTitle": "¿Eliminar {name}?",
"ollama.delMsg":
"El modelo se elimina del host Ollama. Descárgalo de nuevo cuando quieras.",
"ollama.contacting": "Contactando Ollama…",
"ollama.pullingSt": "Descargando…",
"ollama.doneSt": "Listo",
"ollama.pullFailed": "Descarga fallida",
"ollama.delTip": "Eliminar {name}",
"ollama.sizeAria": "tamaño de {name}",
"ollama.cat.llama3_2": "Modelo compacto de Meta para el día a día",
"ollama.cat.llama3_1": "El caballo de batalla de Meta",
"ollama.cat.qwen2_5": "Multilingüe versátil de Alibaba",
"ollama.cat.gemma2": "Modelo abierto y eficiente de Google",
"ollama.cat.mistral": "Mistral 7B — rápido y capaz",
"ollama.cat.phi3": "Modelo pequeño de investigación de Microsoft",
"ollama.cat.deepseek_r1": "Modelo de razonamiento (cadena de pensamiento)",
"ollama.cat.codellama": "Autocompletado y chat de código",
"ollama.cat.llava": "Modelo de visión (entiende imágenes)",
"ollama.cat.nomic_embed_text": "Embeddings de texto",
"center.title": "Centro de admin",
"center.back": "Volver al chat",
"center.tabAccounts": "Cuentas",
"center.tabFeatures": "Funciones",
"center.tabMail": "Correo",
"center.locked": "Solo admins",
"center.lockedSub": "Inicia sesión como admin para abrir el Centro de admin.",
"center.regTitle": "Registro público",
"center.regDesc":
"Muestra el botón Registrarse en el login. Off = solo cuentas del admin.",
"center.accTitle": "Solicitudes de acceso (lista)",
"center.accDescOn": "No disponible con registro público — lo reemplaza.",
"center.accDescOff":
"Muestra Solicitar acceso en el login. Triaje en la pestaña Acceso.",
"center.ocrTitle": "Herramienta OCR",
"center.ocrDesc":
"Transcripción de imágenes en el servidor. Requiere tesseract.",
"center.mailTitle": "Emails de recuperación",
"center.mailChecking": "Verificando…",
"center.mailFrom": "Activado — enviados desde {from}",
"center.mailDefault": "Activado — enviados desde el remitente configurado",
"center.mailOff": "Desactivado — configura SMTP_HOST en el servidor",
"center.mailNote":
"Las credenciales SMTP viven en el servidor (ver docs/OPERATIONS.md), nunca aquí. El email se define en el perfil o al crear la cuenta.",
"center.on": "Sí",
"center.off": "No",
"share.fallback": "Chat compartido",
"share.open": "Abrir KisAssistant",
"share.gone": "Este chat compartido ya no existe",
"share.goneSub": "El enlace no vale, expiró o el autor lo retiró.",
"share.back": "Volver",
"share.loading": "Cargando chat compartido…",
"share.by": "por {name}",
"reset.checking": "Verificando enlace…",
"reset.invalid": "Enlace inválido",
"reset.invalidSub":
"Este enlace no existe, ya se usó o expiró. Pide uno nuevo en el login.",
"reset.readyTitle": "¿Nueva clave para @{user}?",
"reset.readySub":
"Emite una clave nueva al instante. La anterior deja de funcionar.",
"reset.issue": "Emitir nueva clave",
"reset.issuing": "Emitiendo…",
"reset.doneTitle": "Nueva clave lista",
"reset.doneSub":
"Cópiala ahora — no se mostrará de nuevo. Inicia sesión con ella.",
"reset.goLogin": "Ir al login",
"reset.failed": "Recuperación fallida",
"app.deleteConvTitle": "¿Eliminar conversación?",
"app.deleteConvMsg": "«{title}» y sus mensajes se eliminarán.",
};
const fr: Dict = {
"common.close": "Fermer",
"common.cancel": "Annuler",
"common.save": "Enregistrer",
"common.done": "Terminé",
"common.copy": "Copier",
"common.copied": "Copié",
"common.create": "Créer",
"common.delete": "Supprimer",
"common.loading": "Chargement…",
"common.saving": "Enregistrement…",
"common.username": "Nom d'utilisateur",
"common.displayName": "Nom affiché",
"common.select": "Choisir…",
"common.theme": "Thème",
"common.themeAuto": "Auto",
"common.themeAutoFull": "Auto (suit le système)",
"common.light": "Clair",
"common.dark": "Sombre",
"common.saveFailed": "Échec de l'enregistrement",
"common.backToLogin": "Retour à la connexion",
"loading.tagline": "Préparation de vos chats",
"login.welcome": "Bon retour",
"login.subtitle": "Connectez-vous à KisAssistant pour continuer",
"login.usernamePh": "Nom d'utilisateur",
"login.keyPh": "Clé d'accès",
"login.failed": "Échec de connexion",
"login.unlock": "Déverrouiller",
"login.newHere": "Nouveau ici ?",
"login.register": "S'inscrire",
"login.regTipOn": "Créer un compte",
"login.regTipOff": "Inscription désactivée sur cette plateforme",
"login.requestAccess": "Demander l'accès",
"login.forgot": "Clé d'accès oubliée ?",
"login.perk1": "Privé par conception — sans cookies ni suivi",
"login.perk2": "Vos propres modèles, en temps réel",
"login.adminNotePre": "La clé admin vit dans le .env du serveur",
"login.adminNotePost": " ; les clés utilisateur sont émises par l'admin.",
"login.totpIntro": "Double facteur pour @{user} — entrez le code à 6 chiffres de votre app.",
"login.totpAria": "Code d'authentification",
"login.totpInvalid": "Code invalide",
"login.verify": "Vérifier",
"sidebar.newChat": "Nouveau chat",
"sidebar.collapse": "Masquer la barre latérale",
"sidebar.closeDrawer": "Fermer les chats",
"sidebar.searchPh": "Rechercher…",
"sidebar.searchAria": "Rechercher des conversations",
"sidebar.clearSearch": "Effacer la recherche",
"sidebar.searching": "Recherche…",
"sidebar.noMatch": "Aucun résultat pour « {q} ».",
"sidebar.youPrefix": "vous : ",
"sidebar.emptyA": "Aucune conversation.",
"sidebar.emptyB": "Démarrez un nouveau chat ci-dessus.",
"sidebar.rename": "Renommer",
"sidebar.topic": "Définir le sujet",
"sidebar.share": "Partager publiquement",
"sidebar.delete": "Supprimer",
"sidebar.convOptions": "Options de conversation",
"sidebar.admin": "admin",
"sidebar.adminCenter": "Centre admin",
"sidebar.adminCenterTip": "Ouvrir le Centre admin",
"sidebar.settingsTip": "Profil et réglages",
"sidebar.logout": "Déconnexion",
"chat.expand": "Afficher la barre latérale",
"chat.openNav": "Ouvrir les chats",
"chat.modelAria": "Modèle",
"chat.noModels": "Aucun modèle",
"chat.selectModel": "Choisir un modèle…",
"chat.you": "Vous",
"chat.greet": "Comment puis-je aider, {name} ?",
"chat.greetSub":
"Choisissez un modèle, puis écrivez ci-dessous. Markdown pris en charge.",
"chat.thinking": "Réflexion…",
"chat.placeholder": "Écrivez à KisAssistant…",
"chat.send": "Envoyer",
"chat.attachTip": "Joindre un fichier",
"chat.removeAttach": "Retirer {name}",
"chat.ocrOpt": "Transcrire une image",
"chat.unavailable": "(indisponible)",
"chat.ocrHint":
"JPG, PNG, WEBP — OCR sur le serveur, vous relisez le texte avant",
"chat.textOpt": "Joindre un fichier texte",
"chat.textHint": "TXT ou Markdown, 500 Ko max",
"chat.attachNote":
"Les envois passent uniquement par les outils de la plateforme. Les images arrivent au modèle comme texte relu — jamais brutes.",
"chat.wiki": "Wiki et docs",
"attach.tooBigImg": "Image trop lourde (5 Mo max).",
"attach.tooBigText": "Fichier trop lourd (500 Ko max).",
"attach.uploadFailed": "Échec de l'envoi",
"attach.kindOcr": "transcription d'image",
"attach.kindText": "fichier texte",
"attach.tpl": "[pièce jointe {kind} : {name}]",
"attach.chatError": "**Erreur :** le modèle n'a pas répondu. Réessayez.",
"dlg.editConv": "Modifier la conversation",
"dlg.fTitle": "Titre",
"dlg.fTopic": "Sujet",
"dlg.topicHint": "Courte étiquette, p. ex. cuisine, rust, voyages.",
"dlg.noTopic": "Sans sujet",
"dlg.recoverTitle": "Récupérer la clé d'accès",
"dlg.recoverIntro":
"Entrez votre nom d'utilisateur. Vous recevrez un lien qui émet une clé neuve (l'ancienne cesse de fonctionner).",
"dlg.sendLink": "Envoyer le lien",
"dlg.reqFailed": "Requête échouée",
"dlg.recoverSent":
"Si @{user} existe avec un email de récupération, un lien est en route{fromPart} (valide 60 minutes, usage unique).",
"dlg.recoverSentFrom": " depuis {from}",
"dlg.spamNote":
"Vérifiez votre boîte — et les spams, le message a pu y atterrir.",
"dlg.noMailNote":
"Rien ne vient ? Alors aucun email de récupération n'est défini et c'est impossible — contactez l'administrateur du site.",
"dlg.registerTitle": "Créer un compte",
"dlg.regWelcome":
"Bienvenue, @{user} ! Votre clé d'accès — copiez-la vite, elle ne sera plus affichée.",
"dlg.regContinue": "Copier et aller au login",
"dlg.regFailed": "Inscription échouée",
"dlg.emailOpt": "Email (optionnel, pour récupérer la clé)",
"dlg.shareTitle": "Partager publiquement",
"dlg.shareCreating": "Création du lien public…",
"dlg.shareIntro": "Quiconque a ce lien peut lire {title}. Sans connexion.",
"dlg.unshare": "Ne plus partager",
"dlg.reviewOcr": "Relire la transcription",
"dlg.reviewText": "Relire la pièce jointe",
"dlg.transcribing": "Transcription de l'image sur le serveur (OCR)…",
"dlg.uploadingTools": "Envoi via les outils de la plateforme…",
"dlg.ocrExpl":
"L'image a été transcrite en texte sur le serveur — le modèle reçoit ce texte, jamais l'image. Corrigez ci-dessous puis joignez.",
"dlg.textExpl":
"Le fichier a été envoyé via les outils de la plateforme. Son texte est joint à votre message. Modifiez si besoin puis joignez.",
"dlg.truncWarn":
"Transcription tronquée à la limite serveur — la fin manque.",
"dlg.attachBtn": "Joindre au message",
"dlg.chars": "{n} caractères",
"settings.title": "Réglages",
"settings.searchPh": "Rechercher…",
"settings.searchAria": "Rechercher dans les réglages",
"settings.clearSearch": "Effacer la recherche",
"settings.noMatch": "Aucun réglage pour « {q} ».",
"settings.profile": "Profil",
"settings.appearance": "Apparence",
"settings.features": "Fonctions",
"settings.avatarUpload": "Envoyer un avatar",
"settings.dropIdle": "Déposez une image ou cliquez pour envoyer",
"settings.dropActive": "Lâchez !",
"settings.uploading": "Envoi…",
"settings.avatarHint": "jpg · png · webp — 5 Mo max — 5 changements par 2 h",
"settings.email": "Email de récupération",
"settings.emailHint": "Optionnel. Sert uniquement à vous renvoyer une clé.",
"settings.accent": "Couleur d'accent",
"settings.accentHint": "Appliquée aussitôt, gardée sur cet appareil.",
"settings.customColor": "Couleur perso",
"settings.customAria": "Couleur d'accent personnalisée",
"settings.accentAria": "Accent : {label}",
"settings.featHint":
"Activez les fonctions du composer. Aussitôt, sur cet appareil.",
"accent.preset.emerald": "Émeraude",
"accent.preset.rose": "Rose",
"accent.preset.blue": "Bleu",
"accent.preset.violet": "Violet",
"accent.preset.amber": "Jaune",
"accent.preset.orange": "Orange",
"accent.preset.teal": "Sarcelle",
"accent.preset.pink": "Rose vif",
"feat.thinking.label": "Réflexion",
"feat.thinking.hint": "Demande au modèle de raisonner étape par étape.",
"feat.attachments.label": "Pièces jointes",
"feat.attachments.hint":
"Joindre images (transcription OCR) et fichiers texte.",
"feat.deepSearch.label": "Recherche profonde",
"feat.deepSearch.hint":
"Laisse l'assistant chercher des sources fraîches. Bientôt.",
"feat.soon": "(bientôt)",
"admin.title": "Comptes",
"admin.intro":
"Comptes sans KYC : vous créez un pseudo, remettez la clé une fois. Stockées hachées — régénérez pour tourner.",
"admin.keyOnce": "Clé pour @{user} — affichée une fois, copiez-la :",
"admin.newAccount": "Nouveau compte",
"admin.email": "Email (récupération)",
"admin.createFailed": "Création impossible",
"admin.regenFailed": "Régénération impossible",
"admin.deleteFailed": "Suppression impossible",
"admin.loadFailed": "Chargement impossible",
"admin.searchPh": "Rechercher des comptes…",
"admin.searchAria": "Rechercher des comptes",
"admin.sortAria": "Trier les comptes",
"admin.filterAria": "Filtrer les comptes",
"admin.sortNewest": "Plus récents",
"admin.sortOldest": "Plus anciens",
"admin.sortAz": "A → Z",
"admin.sortZa": "Z → A",
"admin.filterAll": "Tous les comptes",
"admin.filterWith": "Avec email de récupération",
"admin.filterNo": "Sans email de récupération",
"admin.countMany": "{total} comptes · page {page} sur {pages}",
"admin.countOne": "{total} compte · page {page} sur {pages}",
"admin.noEmail": "sans email de récupération",
"admin.editProfile": "Modifier le profil",
"admin.regenKey": "Régénérer la clé (révoque les sessions)",
"admin.deleteAccount": "Supprimer le compte",
"admin.deleteTitle": "Supprimer @{user} ?",
"admin.deleteMsg":
"Supprime définitivement le compte, ses conversations et ses partages.",
"admin.prevPage": "Page précédente",
"admin.nextPage": "Page suivante",
"ollama.models": "Modèles Ollama",
"ollama.desc":
"Téléchargez des modèles Ollama sur votre hôte, ou retirez les locaux. Admin uniquement.",
"ollama.pulling": "Téléchargement de {name}",
"ollama.customTag": "Tag personnalisé",
"ollama.customHint": "p. ex. llama3.2:3b, ou un nom de bibliothèque",
"ollama.pull": "Télécharger",
"ollama.installed": "Installés ({n})",
"ollama.noneYet": "Aucun modèle Ollama local.",
"ollama.catalog": "Catalogue",
"ollama.delTitle": "Supprimer {name} ?",
"ollama.delMsg":
"Le modèle est retiré de l'hôte Ollama. Retéléchargez-le à tout moment.",
"ollama.contacting": "Contact d'Ollama…",
"ollama.pullingSt": "Téléchargement…",
"ollama.doneSt": "Terminé",
"ollama.pullFailed": "Échec du téléchargement",
"ollama.delTip": "Supprimer {name}",
"ollama.sizeAria": "taille de {name}",
"ollama.cat.llama3_2": "Le compact de Meta pour tous les jours",
"ollama.cat.llama3_1": "Le bourreau de travail de Meta",
"ollama.cat.qwen2_5": "Polyglotte polyvalent d'Alibaba",
"ollama.cat.gemma2": "Le modèle ouvert et efficace de Google",
"ollama.cat.mistral": "Mistral 7B — rapide et capable",
"ollama.cat.phi3": "Le petit modèle de recherche Microsoft",
"ollama.cat.deepseek_r1": "Modèle de raisonnement (chaîne de pensée)",
"ollama.cat.codellama": "Complétion et chat de code",
"ollama.cat.llava": "Modèle de vision (comprend les images)",
"ollama.cat.nomic_embed_text": "Embeddings de texte",
"center.title": "Centre admin",
"center.back": "Retour au chat",
"center.tabAccounts": "Comptes",
"center.tabFeatures": "Fonctions",
"center.tabMail": "Mail",
"center.locked": "Admins uniquement",
"center.lockedSub": "Connectez-vous en admin pour ouvrir le Centre admin.",
"center.regTitle": "Inscription publique",
"center.regDesc":
"Affiche le bouton S'inscrire au login. Off = comptes admin uniquement.",
"center.accTitle": "Demandes d'accès (liste)",
"center.accDescOn": "Indispo avec l'inscription publique — elle la remplace.",
"center.accDescOff":
"Affiche Demander l'accès au login. Réservez un pseudo, plaidez votre cas ; tri dans l'onglet Accès.",
"center.ocrTitle": "Outil OCR",
"center.ocrDesc": "Transcription d'images côté serveur. Requiert tesseract.",
"center.mailTitle": "Emails de récupération",
"center.mailChecking": "Vérification…",
"center.mailFrom": "Activé — envoyés depuis {from}",
"center.mailDefault": "Activé — envoyés depuis l'expéditeur configuré",
"center.mailOff": "Désactivé — définissez SMTP_HOST sur le serveur",
"center.mailNote":
"Les identifiants SMTP vivent sur le serveur (voir docs/OPERATIONS.md), jamais ici. L'email se règle dans le profil ou à la création.",
"center.on": "Oui",
"center.off": "Non",
"share.fallback": "Chat partagé",
"share.open": "Ouvrir KisAssistant",
"share.gone": "Ce chat partagé n'existe plus",
"share.goneSub": "Le lien est invalide, expiré, ou l'auteur l'a retiré.",
"share.back": "Retour",
"share.loading": "Chargement du chat partagé…",
"share.by": "par {name}",
"reset.checking": "Vérification du lien…",
"reset.invalid": "Lien invalide",
"reset.invalidSub":
"Ce lien est inconnu, déjà utilisé ou expiré. Redemandez-en un au login.",
"reset.readyTitle": "Nouvelle clé pour @{user} ?",
"reset.readySub":
"Émet aussitôt une clé neuve. L'ancienne cesse de fonctionner.",
"reset.issue": "Émettre une clé",
"reset.issuing": "Émission…",
"reset.doneTitle": "Nouvelle clé prête",
"reset.doneSub":
"Copiez-la vite — plus affichée ensuite. Connectez-vous avec.",
"reset.goLogin": "Aller au login",
"reset.failed": "Récupération échouée",
"app.deleteConvTitle": "Supprimer la conversation ?",
"app.deleteConvMsg": "« {title} » et ses messages seront supprimés.",
};
const ru: Dict = {
"common.close": "Закрыть",
"common.cancel": "Отмена",
"common.save": "Сохранить",
"common.done": "Готово",
"common.copy": "Копировать",
"common.copied": "Скопировано",
"common.create": "Создать",
"common.delete": "Удалить",
"common.loading": "Загрузка…",
"common.saving": "Сохранение…",
"common.username": "Имя пользователя",
"common.displayName": "Отображаемое имя",
"common.select": "Выбрать…",
"common.theme": "Тема",
"common.themeAuto": "Авто",
"common.themeAutoFull": "Авто (как в системе)",
"common.light": "Светлая",
"common.dark": "Тёмная",
"common.saveFailed": "Не удалось сохранить",
"common.backToLogin": "Назад ко входу",
"loading.tagline": "Готовим ваши чаты",
"login.welcome": "С возвращением",
"login.subtitle": "Войдите в KisAssistant, чтобы продолжить",
"login.usernamePh": "Имя пользователя",
"login.keyPh": "Ключ доступа",
"login.failed": "Ошибка входа",
"login.unlock": "Разблокировать",
"login.newHere": "Впервые здесь?",
"login.register": "Регистрация",
"login.regTipOn": "Создать аккаунт",
"login.regTipOff": "Регистрация на этой платформе отключена",
"login.requestAccess": "Запросить доступ",
"login.forgot": "Забыли ключ доступа?",
"login.perk1": "Приватность по умолчанию — без cookies и трекинга",
"login.perk2": "Ваши модели, в реальном времени",
"login.adminNotePre": "Ключ админа живёт в .env сервера",
"login.adminNotePost": "; ключи пользователей выдаёт админ.",
"login.totpIntro": "Двухфакторка для @{user} — введите 6-значный код из приложения.",
"login.totpAria": "Код аутентификатора",
"login.totpInvalid": "Неверный код",
"login.verify": "Проверить",
"sidebar.newChat": "Новый чат",
"sidebar.collapse": "Скрыть панель",
"sidebar.closeDrawer": "Закрыть чаты",
"sidebar.searchPh": "Поиск чатов…",
"sidebar.searchAria": "Поиск разговоров",
"sidebar.clearSearch": "Очистить поиск",
"sidebar.searching": "Поиск…",
"sidebar.noMatch": "По запросу «{q}» ничего нет.",
"sidebar.youPrefix": "вы: ",
"sidebar.emptyA": "Пока нет разговоров.",
"sidebar.emptyB": "Начните новый чат выше.",
"sidebar.rename": "Переименовать",
"sidebar.topic": "Задать тему",
"sidebar.share": "Опубликовать ссылку",
"sidebar.delete": "Удалить",
"sidebar.convOptions": "Действия с разговором",
"sidebar.admin": "админ",
"sidebar.adminCenter": "Админ-центр",
"sidebar.adminCenterTip": "Открыть Админ-центр",
"sidebar.settingsTip": "Профиль и настройки",
"sidebar.logout": "Выйти",
"chat.expand": "Показать панель",
"chat.openNav": "Открыть чаты",
"chat.modelAria": "Модель",
"chat.noModels": "Нет моделей",
"chat.selectModel": "Выбрать модель…",
"chat.you": "Вы",
"chat.greet": "Чем помочь, {name}?",
"chat.greetSub": "Выберите модель и пишите ниже. Поддерживается Markdown.",
"chat.thinking": "Думаю…",
"chat.placeholder": "Сообщение KisAssistant…",
"chat.send": "Отправить",
"chat.attachTip": "Прикрепить файл",
"chat.removeAttach": "Убрать {name}",
"chat.ocrOpt": "Распознать изображение",
"chat.unavailable": "(недоступно)",
"chat.ocrHint": "JPG, PNG, WEBP — OCR на сервере, текст вы проверите",
"chat.textOpt": "Прикрепить текстовый файл",
"chat.textHint": "TXT или Markdown, до 500 КБ",
"chat.attachNote":
"Загрузка только через инструменты платформы. Изображения уходят модели проверенным текстом — не картинками.",
"chat.wiki": "Вики и доки",
"attach.tooBigImg": "Изображение слишком большое (макс. 5 МБ).",
"attach.tooBigText": "Файл слишком большой (макс. 500 КБ).",
"attach.uploadFailed": "Ошибка загрузки",
"attach.kindOcr": "распознанное изображение",
"attach.kindText": "текстовый файл",
"attach.tpl": "[приложено {kind}: {name}]",
"attach.chatError": "**Ошибка:** модель не ответила. Попробуйте ещё.",
"dlg.editConv": "Изменить разговор",
"dlg.fTitle": "Название",
"dlg.fTopic": "Тема",
"dlg.topicHint": "Короткий тег, напр. кухня, rust, поездки.",
"dlg.noTopic": "Без темы",
"dlg.recoverTitle": "Восстановить ключ",
"dlg.recoverIntro":
"Введите имя пользователя. Вы получите ссылку на новый ключ (старый перестанет работать).",
"dlg.sendLink": "Отправить ссылку",
"dlg.reqFailed": "Запрос не удался",
"dlg.recoverSent":
"Если @{user} существует и у него есть email, ссылка уже летит{fromPart} (действует 60 минут, одноразовая).",
"dlg.recoverSentFrom": " от {from}",
"dlg.spamNote": "Проверьте входящие — и спам, письмо могло попасть туда.",
"dlg.noMailNote":
"Ничего нет? Значит, email не указан и сброс невозможен — обратитесь к администратору сайта.",
"dlg.registerTitle": "Создать аккаунт",
"dlg.regWelcome":
"Добро пожаловать, @{user}! Ваш ключ — скопируйте сейчас, больше не покажем.",
"dlg.regContinue": "Скопировать и ко входу",
"dlg.regFailed": "Ошибка регистрации",
"dlg.emailOpt": "Email (необязательно, для восстановления)",
"dlg.shareTitle": "Опубликовать ссылку",
"dlg.shareCreating": "Создаём публичную ссылку…",
"dlg.shareIntro": "Каждый с этой ссылкой прочитает {title}. Вход не нужен.",
"dlg.unshare": "Закрыть доступ",
"dlg.reviewOcr": "Проверить распознавание",
"dlg.reviewText": "Проверить вложение",
"dlg.transcribing": "Распознаём изображение на сервере (OCR)…",
"dlg.uploadingTools": "Загружаем через инструменты платформы…",
"dlg.ocrExpl":
"Изображение распознано в текст на сервере — модель получит текст, не картинку. Исправьте ошибки ниже и прикрепите.",
"dlg.textExpl":
"Файл загружен через инструменты платформы. Его текст прикрепится к сообщению. Правьте и прикрепляйте.",
"dlg.truncWarn": "Распознавание обрезано лимитом сервера — конца нет.",
"dlg.attachBtn": "Прикрепить к сообщению",
"dlg.chars": "{n} символов",
"settings.title": "Настройки",
"settings.searchPh": "Поиск настроек…",
"settings.searchAria": "Поиск настроек",
"settings.clearSearch": "Очистить поиск",
"settings.noMatch": "Настроек по «{q}» нет.",
"settings.profile": "Профиль",
"settings.appearance": "Внешний вид",
"settings.features": "Функции",
"settings.avatarUpload": "Загрузить аватар",
"settings.dropIdle": "Перетащите картинку или нажмите для загрузки",
"settings.dropActive": "Отпускайте!",
"settings.uploading": "Загрузка…",
"settings.avatarHint": "jpg · png · webp — до 5 МБ — 5 смен за 2 ч",
"settings.email": "Email для восстановления",
"settings.emailHint": "Необязательно. Только для отправки нового ключа.",
"settings.accent": "Акцентный цвет",
"settings.accentHint": "Применяется сразу, хранится на устройстве.",
"settings.customColor": "Свой цвет",
"settings.customAria": "Свой акцентный цвет",
"settings.accentAria": "Акцент: {label}",
"settings.featHint":
"Включайте возможности редактора. Сразу, на этом устройстве.",
"accent.preset.emerald": "Изумруд",
"accent.preset.rose": "Роза",
"accent.preset.blue": "Синий",
"accent.preset.violet": "Фиолет",
"accent.preset.amber": "Жёлтый",
"accent.preset.orange": "Оранжевый",
"accent.preset.teal": "Бирюза",
"accent.preset.pink": "Розовый",
"feat.thinking.label": "Размышление",
"feat.thinking.hint": "Просить модель рассуждать шаг за шагом.",
"feat.attachments.label": "Вложения",
"feat.attachments.hint": "Картинки (OCR) и текстовые файлы к сообщениям.",
"feat.deepSearch.label": "Глубокий поиск",
"feat.deepSearch.hint": "Поиск свежих источников. Скоро.",
"feat.soon": "(скоро)",
"admin.title": "Аккаунты",
"admin.intro":
"Аккаунты без KYC: создаёте имя, ключ отдаёте один раз. Хранится хэш — смена ротирует.",
"admin.keyOnce": "Ключ @{user} — показан раз, копируйте:",
"admin.newAccount": "Новый аккаунт",
"admin.email": "Email (восстановление)",
"admin.createFailed": "Не создалось",
"admin.regenFailed": "Не сменилось",
"admin.deleteFailed": "Не удалилось",
"admin.loadFailed": "Не загрузилось",
"admin.searchPh": "Поиск аккаунтов…",
"admin.searchAria": "Поиск аккаунтов",
"admin.sortAria": "Сортировка",
"admin.filterAria": "Фильтр",
"admin.sortNewest": "Сначала новые",
"admin.sortOldest": "Сначала старые",
"admin.sortAz": "А → Я",
"admin.sortZa": "Я → А",
"admin.filterAll": "Все аккаунты",
"admin.filterWith": "С email",
"admin.filterNo": "Без email",
"admin.countMany": "{total} аккаунтов · стр. {page} из {pages}",
"admin.countOne": "{total} аккаунт · стр. {page} из {pages}",
"admin.noEmail": "без email",
"admin.editProfile": "Изменить профиль",
"admin.regenKey": "Сменить ключ (убьёт сессии)",
"admin.deleteAccount": "Удалить аккаунт",
"admin.deleteTitle": "Удалить @{user}?",
"admin.deleteMsg": "Аккаунт, разговоры и ссылки удалятся навсегда.",
"admin.prevPage": "Назад",
"admin.nextPage": "Вперёд",
"ollama.models": "Модели Ollama",
"ollama.desc":
"Качайте модели на Ollama-хост или удаляйте локальные. Только админ.",
"ollama.pulling": "Качаем {name}",
"ollama.customTag": "Свой тег",
"ollama.customHint": "напр. llama3.2:3b или имя из библиотеки",
"ollama.pull": "Скачать",
"ollama.installed": "Стоит ({n})",
"ollama.noneYet": "Локальных моделей пока нет.",
"ollama.catalog": "Каталог",
"ollama.delTitle": "Удалить {name}?",
"ollama.delMsg": "Модель уйдёт с хоста. Скачайте заново когда угодно.",
"ollama.contacting": "Связь с Ollama…",
"ollama.pullingSt": "Качаем…",
"ollama.doneSt": "Готово",
"ollama.pullFailed": "Не скачалось",
"ollama.delTip": "Удалить {name}",
"ollama.sizeAria": "размер {name}",
"ollama.cat.llama3_2": "Компактная повседневная модель Meta",
"ollama.cat.llama3_1": "Рабочая лошадка Meta",
"ollama.cat.qwen2_5": "Многоязычный универсал Alibaba",
"ollama.cat.gemma2": "Эффективная открытая модель Google",
"ollama.cat.mistral": "Mistral 7B — быстрый и способный",
"ollama.cat.phi3": "Маленькая исследовательская модель Microsoft",
"ollama.cat.deepseek_r1": "Рассуждающая модель (цепочка мыслей)",
"ollama.cat.codellama": "Код и чат про код",
"ollama.cat.llava": "Зрение (понимает картинки)",
"ollama.cat.nomic_embed_text": "Текстовые эмбеддинги",
"center.title": "Админ-центр",
"center.back": "Назад в чат",
"center.tabAccounts": "Аккаунты",
"center.tabFeatures": "Функции",
"center.tabMail": "Почта",
"center.locked": "Только админы",
"center.lockedSub": "Войдите как админ, чтобы открыть центр.",
"center.regTitle": "Открытая регистрация",
"center.regDesc": "Кнопка регистрации на входе. Выкл = только админ создаёт.",
"center.accTitle": "Заявки (лист ожидания)",
"center.accDescOn": "Недоступно при открытой регистрации — она заменяет.",
"center.accDescOff":
"Кнопка доступа на входе. Гости просят имя; разбор во вкладке.",
"center.ocrTitle": "Инструмент OCR",
"center.ocrDesc": "Распознавание картинок на сервере. Нужен tesseract.",
"center.mailTitle": "Письма восстановления",
"center.mailChecking": "Проверяем…",
"center.mailFrom": "Включено — шлём от {from}",
"center.mailDefault": "Включено — шлём с настроенного адреса",
"center.mailOff": "Выключено — задайте SMTP_HOST",
"center.mailNote":
"SMTP-доступ живёт в окружении сервера (см. docs/OPERATIONS.md), не здесь. Email задаётся в профиле или при создании.",
"center.on": "Вкл",
"center.off": "Выкл",
"share.fallback": "Общий чат",
"share.open": "Открыть KisAssistant",
"share.gone": "Этот общий чат исчез",
"share.goneSub": "Ссылка неверна, истекла или автор закрыл её.",
"share.back": "Назад",
"share.loading": "Загружаем общий чат…",
"share.by": "от {name}",
"reset.checking": "Проверяем ссылку…",
"reset.invalid": "Ссылка неверна",
"reset.invalidSub":
"Ссылка неизвестна, использована или истекла. Запросите новую на входе.",
"reset.readyTitle": "Новый ключ для @{user}?",
"reset.readySub": "Сразу выдадим свежий ключ. Старый перестанет работать.",
"reset.issue": "Выдать ключ",
"reset.issuing": "Выдаём…",
"reset.doneTitle": "Ключ готов",
"reset.doneSub": "Скопируйте сейчас — больше не покажем. Войдите с ним.",
"reset.goLogin": "Ко входу",
"reset.failed": "Не вышло",
"app.deleteConvTitle": "Удалить разговор?",
"app.deleteConvMsg": "«{title}» и сообщения удалятся.",
"settings.language": "Язык",
"settings.languageHint": "Язык интерфейса. Сразу, на этом устройстве.",
"settings.security": "Безопасность",
"sec.accessKey": "Ключ доступа",
"sec.accessKeyHint":
"Смените ключ. Старые сессии отзовутся сразу — войдёте с новым.",
"sec.rotate": "Сменить мой ключ",
"sec.rotateConfirm": "Нажмите ещё раз для подтверждения",
"sec.rotationFailed": "Не сменилось",
"sec.totp": "Двухфакторка (TOTP)",
"sec.totpChecking": "Проверяем…",
"sec.totpAddHint":
"Добавьте 6-значный код из приложения поверх ключа.",
"sec.totpEnable": "Включить двухфакторку",
"sec.totpSecretHint":
"Добавьте секрет в приложение (или откройте otpauth-ссылку) и введите код:",
"sec.totpConfirm": "Подтвердить",
"sec.totpDisableLabel": "Отключить текущим кодом",
"sec.totpDisable": "Выключить 2FA",
"sec.setupFailed": "Не настроилось",
"sec.passkeys": "Пасскеи",
"sec.passkeysHint":
"WebAuthn / системные ключи — нужен RP на сервере.",
"sec.danger": "Опасная зона",
"sec.dangerHint":
"Навсегда удалит аккаунт с чатами, ссылками и сессиями. Без возврата.",
"sec.deleteMe": "Удалить мой аккаунт",
"sec.deleteFailed": "Не удалилось",
"sec.newKeyTitle": "Новый ключ доступа",
"sec.newKeyHint":
"Скопируйте сейчас — больше не покажем. Старые сессии отозваны.",
"sec.newKeyDone": "Готово — выйти",
"sec.deleteTitle": "Удалить ваш аккаунт?",
"sec.deleteMsg":
"Навсегда удалит аккаунт с разговорами, ссылками и сессиями. Без возврата.",
"sec.deleteForever": "Удалить навсегда",
"sec.avatarTooBig": "Файл слишком большой (макс. 5 МБ).",
"sec.avatarType": "Только картинки jpg, png или webp.",
"center.tabAccess": "Доступ",
"access.status.pending": "Ожидает",
"access.status.reviewing": "На проверке",
"access.status.accepted": "Принята",
"access.status.refused": "Отклонена",
"access.filter.all": "все",
"access.filter.pending": "ожидают",
"access.filter.reviewing": "на проверке",
"access.filter.accepted": "приняты",
"access.filter.refused": "отклонены",
"access.refresh": "Обновить",
"access.refreshAria": "Обновить заявки",
"access.empty": "Заявок нет. Лист ожидания пуст.",
"access.emptyFiltered": "С таким статусом ничего нет.",
"access.msgOne": "1 сообщение",
"access.msgMany": "{n} сообщений",
"access.accountCreated": "Аккаунт создан — ключ показан раз, копируйте:",
"access.replyPh": "Ответить (уйдёт по email)…",
"access.replyAria": "Ответить заявителю",
"access.reasonLabel": "Причина (уйдёт с решением)",
"access.reasonPh": "Необязательная причина…",
"access.btnReviewing": "На проверку",
"access.btnAccept": "Принять",
"access.btnRefuse": "Отклонить",
"access.closedNote": "Тикет закрыт ({status}).",
"access.reasonWord": "Причина",
"access.youAdmin": "Вы (админ)",
"access.sendFailed": "Не отправилось",
"access.updateFailed": "Не обновилось",
"review.title": "Заявка на доступ",
"review.loadingTicket": "Загружаем тикет…",
"review.notFound": "Тикет не найден",
"review.notFoundSub":
"Ссылка неизвестна. Проверьте URL из письма или подайте заявку со входа.",
"review.adminNote": "Заметка админа:",
"review.acceptedHint":
"Аккаунт создан — проверьте письмо с ключом и входите.",
"review.closedHint":
"Тикет закрыт. Письма не читаются — при нужде подайте новую заявку.",
"review.replyPh": "Изложите свой случай…",
"review.replyAria": "Ответить в тикет",
"review.mailHint":
"Письма — в одну сторону: отвечайте здесь, не по email.",
"review.status.pending": "Ждёт проверки",
"review.status.reviewing": "На проверке",
"review.status.accepted": "Принята",
"review.status.refused": "Отклонена",
"review.admin": "Админ",
"review.you": "Вы",
"review.requestTitle": "Запросить доступ",
"review.requestIntro":
"Займите имя и расскажите, зачем вам сюда. Админ разберёт каждую заявку — следите за тикетом и почтой.",
"review.emailHint": "Обязательно — статусы и ответы придут сюда.",
"review.whyLabel": "Почему вам дать доступ?",
"review.whyHint": "10+ символов. Это откроет тикет.",
"review.whyPh": "Я хочу…",
"review.sendRequest": "Отправить заявку",
"review.received": "Заявка получена",
"review.reserved":
"@{user} занято вами, пока админ проверяет. Письмо с тикетом уже летит.",
"review.copyLink": "Копировать ссылку",
"review.openTicket": "Открыть мой тикет",
"admin.editUserTitle": "Править @{user}",
"admin.avatarUrl": "URL аватара",
};
const it: Dict = {
"common.close": "Chiudi",
"common.cancel": "Annulla",
"common.save": "Salva",
"common.done": "Fatto",
"common.copy": "Copia",
"common.copied": "Copiato",
"common.create": "Crea",
"common.delete": "Elimina",
"common.loading": "Caricamento…",
"common.saving": "Salvataggio…",
"common.username": "Nome utente",
"common.displayName": "Nome visualizzato",
"common.select": "Scegli…",
"common.theme": "Tema",
"common.themeAuto": "Auto",
"common.themeAutoFull": "Auto (segue il sistema)",
"common.light": "Chiaro",
"common.dark": "Scuro",
"common.saveFailed": "Salvataggio non riuscito",
"common.backToLogin": "Torna al login",
"loading.tagline": "Preparo le tue chat",
"login.welcome": "Bentornato",
"login.subtitle": "Accedi a KisAssistant per continuare",
"login.usernamePh": "Nome utente",
"login.keyPh": "Chiave di accesso",
"login.failed": "Accesso non riuscito",
"login.unlock": "Sblocca",
"login.newHere": "Nuovo qui?",
"login.register": "Registrati",
"login.regTipOn": "Crea un account",
"login.regTipOff": "Registrazione disattivata su questa piattaforma",
"login.requestAccess": "Richiedi accesso",
"login.forgot": "Chiave dimenticata?",
"login.perk1": "Privato per progettazione — niente cookie né traccianti",
"login.perk2": "I tuoi modelli, in tempo reale",
"login.adminNotePre": "La chiave admin vive nel .env del server",
"login.adminNotePost": "; le chiavi utente le emette l'admin.",
"login.totpIntro": "Doppio fattore per @{user} — inserisci il codice a 6 cifre dall'app.",
"login.totpAria": "Codice autenticatore",
"login.totpInvalid": "Codice non valido",
"login.verify": "Verifica",
"sidebar.newChat": "Nuova chat",
"sidebar.collapse": "Nascondi barra laterale",
"sidebar.closeDrawer": "Chiudi chat",
"sidebar.searchPh": "Cerca chat…",
"sidebar.searchAria": "Cerca conversazioni",
"sidebar.clearSearch": "Cancella ricerca",
"sidebar.searching": "Ricerca…",
"sidebar.noMatch": "Nessun risultato per «{q}».",
"sidebar.youPrefix": "tu: ",
"sidebar.emptyA": "Nessuna conversazione.",
"sidebar.emptyB": "Inizia una nuova chat qui sopra.",
"sidebar.rename": "Rinomina",
"sidebar.topic": "Imposta argomento",
"sidebar.share": "Condividi pubblicamente",
"sidebar.delete": "Elimina",
"sidebar.convOptions": "Opzioni conversazione",
"sidebar.admin": "admin",
"sidebar.adminCenter": "Centro admin",
"sidebar.adminCenterTip": "Apri il Centro admin",
"sidebar.settingsTip": "Profilo e impostazioni",
"sidebar.logout": "Esci",
"chat.expand": "Mostra barra laterale",
"chat.openNav": "Apri chat",
"chat.modelAria": "Modello",
"chat.noModels": "Nessun modello",
"chat.selectModel": "Scegli modello…",
"chat.you": "Tu",
"chat.greet": "Come posso aiutare, {name}?",
"chat.greetSub": "Scegli un modello e scrivi qui sotto. Markdown supportato.",
"chat.thinking": "Penso…",
"chat.placeholder": "Scrivi a KisAssistant…",
"chat.send": "Invia",
"chat.attachTip": "Allega un file",
"chat.removeAttach": "Rimuovi {name}",
"chat.ocrOpt": "Trascrivi immagine",
"chat.unavailable": "(non disponibile)",
"chat.ocrHint": "JPG, PNG, WEBP — OCR sul server, rivedi il testo prima",
"chat.textOpt": "Allega file di testo",
"chat.textHint": "TXT o Markdown, max 500KB",
"chat.attachNote":
"Upload solo con gli strumenti della piattaforma. Le immagini arrivano al modello come testo rivisto — mai grezze.",
"chat.wiki": "Wiki e docs",
"attach.tooBigImg": "Immagine troppo grande (max 5MB).",
"attach.tooBigText": "File troppo grande (max 500KB).",
"attach.uploadFailed": "Upload non riuscito",
"attach.kindOcr": "trascrizione immagine",
"attach.kindText": "file di testo",
"attach.tpl": "[allegato {kind}: {name}]",
"attach.chatError": "**Errore:** il modello non ha risposto. Riprova.",
"dlg.editConv": "Modifica conversazione",
"dlg.fTitle": "Titolo",
"dlg.fTopic": "Argomento",
"dlg.topicHint": "Breve etichetta, es. cucina, rust, viaggi.",
"dlg.noTopic": "Nessun argomento",
"dlg.recoverTitle": "Recupera chiave di accesso",
"dlg.recoverIntro":
"Inserisci il tuo nome utente. Riceverai un link che emette una chiave nuova (la vecchia smette di funzionare).",
"dlg.sendLink": "Invia link",
"dlg.reqFailed": "Richiesta fallita",
"dlg.recoverSent":
"Se @{user} esiste con email di recupero, un link è in viaggio{fromPart} (valido 60 minuti, monouso).",
"dlg.recoverSentFrom": " da {from}",
"dlg.spamNote": "Controlla la posta — e lo spam, potrebbe essere finito lì.",
"dlg.noMailNote":
"Nulla in arrivo? Allora nessuna email è impostata e non si può — contatta l'amministratore del sito.",
"dlg.registerTitle": "Crea un account",
"dlg.regWelcome":
"Benvenuto, @{user}! La tua chiave — copiala ora, non sarà più mostrata.",
"dlg.regContinue": "Copia e vai al login",
"dlg.regFailed": "Registrazione fallita",
"dlg.emailOpt": "Email (opzionale, per recupero chiave)",
"dlg.shareTitle": "Condividi pubblicamente",
"dlg.shareCreating": "Creazione link pubblico…",
"dlg.shareIntro":
"Chiunque abbia questo link può leggere {title}. Senza login.",
"dlg.unshare": "Rimuovi condivisione",
"dlg.reviewOcr": "Rivedi trascrizione",
"dlg.reviewText": "Rivedi allegato",
"dlg.transcribing": "Trascrizione immagine sul server (OCR)…",
"dlg.uploadingTools": "Upload con gli strumenti…",
"dlg.ocrExpl":
"L'immagine è stata trascritta in testo sul server — il modello riceve questo testo, mai l'immagine. Correggi sotto e allega.",
"dlg.textExpl":
"Il file è stato caricato con gli strumenti. Il suo testo sarà allegato al messaggio. Modifica e allega.",
"dlg.truncWarn": "Trascrizione troncata al limite server — manca la fine.",
"dlg.attachBtn": "Allega al messaggio",
"dlg.chars": "{n} caratteri",
"settings.title": "Impostazioni",
"settings.searchPh": "Cerca impostazioni…",
"settings.searchAria": "Cerca impostazioni",
"settings.clearSearch": "Cancella ricerca",
"settings.noMatch": "Nessuna impostazione per «{q}».",
"settings.profile": "Profilo",
"settings.appearance": "Aspetto",
"settings.features": "Funzioni",
"settings.avatarUpload": "Carica avatar",
"settings.dropIdle": "Trascina un'immagine o clicca per caricare",
"settings.dropActive": "Lascia!",
"settings.uploading": "Caricamento…",
"settings.avatarHint": "jpg · png · webp — max 5MB — 5 cambi per 2h",
"settings.email": "Email di recupero",
"settings.emailHint": "Opzionale. Solo per inviarti una chiave nuova.",
"settings.accent": "Colore accento",
"settings.accentHint": "Subito attivo, salvato su questo dispositivo.",
"settings.customColor": "Colore custom",
"settings.customAria": "Colore accento personalizzato",
"settings.accentAria": "Accento: {label}",
"settings.featHint":
"Attiva funzioni del composer. Subito, su questo dispositivo.",
"accent.preset.emerald": "Smeraldo",
"accent.preset.rose": "Rosa",
"accent.preset.blue": "Blu",
"accent.preset.violet": "Viola",
"accent.preset.amber": "Giallo",
"accent.preset.orange": "Arancione",
"accent.preset.teal": "Teal",
"accent.preset.pink": "Rosa shocking",
"feat.thinking.label": "Ragionamento",
"feat.thinking.hint": "Chiedi al modello di ragionare passo passo.",
"feat.attachments.label": "Allegati",
"feat.attachments.hint":
"Allega immagini (trascrizione OCR) e file di testo.",
"feat.deepSearch.label": "Ricerca profonda",
"feat.deepSearch.hint": "Cerca fonti fresche sul web. Prossimamente.",
"feat.soon": "(presto)",
"admin.title": "Account",
"admin.intro":
"Account senza KYC: crei un nome, consegni la chiave una volta. Hash salvati — rigenera per ruotare.",
"admin.keyOnce": "Chiave per @{user} — mostrata una volta, copiala ora:",
"admin.newAccount": "Nuovo account",
"admin.email": "Email (recupero)",
"admin.createFailed": "Creazione fallita",
"admin.regenFailed": "Rigenerazione fallita",
"admin.deleteFailed": "Eliminazione fallita",
"admin.loadFailed": "Caricamento fallito",
"admin.searchPh": "Cerca account…",
"admin.searchAria": "Cerca account",
"admin.sortAria": "Ordina account",
"admin.filterAria": "Filtra account",
"admin.sortNewest": "Più recenti",
"admin.sortOldest": "Meno recenti",
"admin.sortAz": "A → Z",
"admin.sortZa": "Z → A",
"admin.filterAll": "Tutti gli account",
"admin.filterWith": "Con email di recupero",
"admin.filterNo": "Senza email di recupero",
"admin.countMany": "{total} account · pagina {page} di {pages}",
"admin.countOne": "{total} account · pagina {page} di {pages}",
"admin.noEmail": "senza email di recupero",
"admin.editProfile": "Modifica profilo",
"admin.regenKey": "Rigenera chiave (revoca sessioni)",
"admin.deleteAccount": "Elimina account",
"admin.deleteTitle": "Eliminare @{user}?",
"admin.deleteMsg":
"Rimuove per sempre account, conversazioni e condivisioni.",
"admin.prevPage": "Pagina precedente",
"admin.nextPage": "Pagina successiva",
"ollama.models": "Modelli Ollama",
"ollama.desc":
"Scarica modelli sull'host Ollama o rimuovi i locali. Solo admin.",
"ollama.pulling": "Scarico {name}",
"ollama.customTag": "Tag personalizzato",
"ollama.customHint": "es. llama3.2:3b, o un nome di libreria",
"ollama.pull": "Scarica",
"ollama.installed": "Installati ({n})",
"ollama.noneYet": "Ancora nessun modello locale.",
"ollama.catalog": "Catalogo",
"ollama.delTitle": "Eliminare {name}?",
"ollama.delMsg": "Il modello lascia l'host Ollama. Riscaricalo quando vuoi.",
"ollama.contacting": "Contatto Ollama…",
"ollama.pullingSt": "Scarico…",
"ollama.doneSt": "Fatto",
"ollama.pullFailed": "Download fallito",
"ollama.delTip": "Elimina {name}",
"ollama.sizeAria": "taglia di {name}",
"ollama.cat.llama3_2": "Il compatto Meta per tutti i giorni",
"ollama.cat.llama3_1": "Il cavallo di battaglia Meta",
"ollama.cat.qwen2_5": "Multilingua Alibaba tuttofare",
"ollama.cat.gemma2": "L'open efficiente di Google",
"ollama.cat.mistral": "Mistral 7B — veloce e capace",
"ollama.cat.phi3": "Il piccolo modello Microsoft",
"ollama.cat.deepseek_r1": "Modello di ragionamento (catena di pensiero)",
"ollama.cat.codellama": "Completamento e chat di codice",
"ollama.cat.llava": "Visione (capisce le immagini)",
"ollama.cat.nomic_embed_text": "Embedding di testo",
"center.title": "Centro admin",
"center.back": "Torna alla chat",
"center.tabAccounts": "Account",
"center.tabFeatures": "Funzioni",
"center.tabMail": "Mail",
"center.locked": "Solo admin",
"center.lockedSub": "Accedi come admin per aprire il Centro.",
"center.regTitle": "Registrazione pubblica",
"center.regDesc": "Mostra Registrati al login. Off = solo account admin.",
"center.accTitle": "Richieste di accesso (lista)",
"center.accDescOn":
"Non disponibile con registrazione pubblica — la sostituisce.",
"center.accDescOff":
"Mostra Richiedi accesso al login. Valuta nella scheda Accesso.",
"center.ocrTitle": "Strumento OCR",
"center.ocrDesc": "Trascrizione immagini sul server. Serve tesseract.",
"center.mailTitle": "Email di recupero",
"center.mailChecking": "Verifica…",
"center.mailFrom": "Attivo — inviate da {from}",
"center.mailDefault": "Attivo — inviate dal mittente configurato",
"center.mailOff": "Disattivo — imposta SMTP_HOST sul server",
"center.mailNote":
"Le credenziali SMTP vivono sul server (vedi docs/OPERATIONS.md), mai qui. L'email si imposta nel profilo o alla creazione.",
"center.on": "Sì",
"center.off": "No",
"share.fallback": "Chat condivisa",
"share.open": "Apri KisAssistant",
"share.gone": "Questa chat condivisa non c'è più",
"share.goneSub": "Il link non vale, è scaduto o l'autore l'ha tolto.",
"share.back": "Indietro",
"share.loading": "Carico chat condivisa…",
"share.by": "di {name}",
"reset.checking": "Verifica link…",
"reset.invalid": "Link non valido",
"reset.invalidSub":
"Link sconosciuto, usato o scaduto. Richiedine uno al login.",
"reset.readyTitle": "Nuova chiave per @{user}?",
"reset.readySub":
"Emette subito una chiave nuova. La vecchia smette di funzionare.",
"reset.issue": "Emetti nuova chiave",
"reset.issuing": "Emissione…",
"reset.doneTitle": "Nuova chiave pronta",
"reset.doneSub": "Copiala ora — non sarà più mostrata. Accedi con essa.",
"reset.goLogin": "Vai al login",
"reset.failed": "Recupero fallito",
"app.deleteConvTitle": "Eliminare conversazione?",
"app.deleteConvMsg": "«{title}» e i suoi messaggi saranno eliminati.",
"settings.language": "Lingua",
"settings.languageHint":
"Lingua dell'interfaccia. Subito, su questo dispositivo.",
"settings.security": "Sicurezza",
"sec.accessKey": "Chiave di accesso",
"sec.accessKeyHint":
"Ruota la tua chiave. Le vecchie sessioni sono revocate subito — rientri con la nuova.",
"sec.rotate": "Ruota la mia chiave",
"sec.rotateConfirm": "Clicca di nuovo per confermare",
"sec.rotationFailed": "Rotazione fallita",
"sec.totp": "Doppio fattore (TOTP)",
"sec.totpChecking": "Verifica…",
"sec.totpAddHint":
"Aggiungi un codice a 6 cifre dall'app oltre alla chiave.",
"sec.totpEnable": "Attiva doppio fattore",
"sec.totpSecretHint":
"Aggiungi questo segreto all'app (o apri il link otpauth) e inserisci un codice:",
"sec.totpConfirm": "Conferma",
"sec.totpDisableLabel": "Disattiva con un codice attuale",
"sec.totpDisable": "Disattiva 2FA",
"sec.setupFailed": "Configurazione fallita",
"sec.passkeys": "Passkey",
"sec.passkeysHint":
"WebAuthn / autenticatori di sistema — serve RP sul server.",
"sec.danger": "Zona pericolosa",
"sec.dangerHint":
"Elimina account con chat, condivisioni e sessioni. Irreversibile.",
"sec.deleteMe": "Elimina il mio account",
"sec.deleteFailed": "Eliminazione fallita",
"sec.newKeyTitle": "Nuova chiave di accesso",
"sec.newKeyHint":
"Copiala ora — non sarà più mostrata. Le vecchie sessioni sono revocate.",
"sec.newKeyDone": "Fatto — esci",
"sec.deleteTitle": "Eliminare il tuo account?",
"sec.deleteMsg":
"Rimuove per sempre account con conversazioni, condivisioni e sessioni. Irreversibile.",
"sec.deleteForever": "Elimina per sempre",
"sec.avatarTooBig": "File troppo grande (max 5MB).",
"sec.avatarType": "Solo immagini jpg, png o webp.",
"center.tabAccess": "Accesso",
"access.status.pending": "In attesa",
"access.status.reviewing": "In revisione",
"access.status.accepted": "Accettata",
"access.status.refused": "Rifiutata",
"access.filter.all": "tutti",
"access.filter.pending": "in attesa",
"access.filter.reviewing": "in revisione",
"access.filter.accepted": "accettati",
"access.filter.refused": "rifiutati",
"access.refresh": "Aggiorna",
"access.refreshAria": "Aggiorna richieste",
"access.empty": "Nessuna richiesta. La lista è vuota.",
"access.emptyFiltered": "Nulla con questo stato.",
"access.msgOne": "1 messaggio",
"access.msgMany": "{n} messaggi",
"access.accountCreated": "Account creato — chiave unica, copiala ora:",
"access.replyPh": "Rispondi (arriva via email)…",
"access.replyAria": "Rispondi al richiedente",
"access.reasonLabel": "Motivo (inviato con la decisione)",
"access.reasonPh": "Motivo opzionale…",
"access.btnReviewing": "In revisione",
"access.btnAccept": "Accetta",
"access.btnRefuse": "Rifiuta",
"access.closedNote": "Ticket chiuso ({status}).",
"access.reasonWord": "Motivo",
"access.youAdmin": "Tu (admin)",
"access.sendFailed": "Invio fallito",
"access.updateFailed": "Aggiornamento fallito",
"review.title": "Richiesta di accesso",
"review.loadingTicket": "Carico ticket…",
"review.notFound": "Ticket non trovato",
"review.notFoundSub":
"Link sconosciuto. Controlla l'URL della tua email o richiedi accesso dal login.",
"review.adminNote": "Nota admin:",
"review.acceptedHint":
"Account creato — controlla l'email con la chiave e accedi.",
"review.closedHint":
"Ticket chiuso. Le email non si leggono — se serve, richiedi di nuovo.",
"review.replyPh": "Perora la tua causa…",
"review.replyAria": "Rispondi al tuo ticket",
"review.mailHint":
"Le email sono a senso unico — rispondi qui, non via email.",
"review.status.pending": "In attesa di revisione",
"review.status.reviewing": "In revisione",
"review.status.accepted": "Accettata",
"review.status.refused": "Rifiutata",
"review.admin": "Admin",
"review.you": "Tu",
"review.requestTitle": "Richiedi accesso",
"review.requestIntro":
"Riserva un nome e spiega perché vuoi entrare. Un admin valuta ogni richiesta — segui il filo sul tuo ticket e via email.",
"review.emailHint": "Obbligatoria — avvisi e risposte arrivano qui.",
"review.whyLabel": "Perché darti accesso?",
"review.whyHint": "10+ caratteri. Questo apre il tuo ticket.",
"review.whyPh": "Voglio…",
"review.sendRequest": "Invia richiesta",
"review.received": "Richiesta ricevuta",
"review.reserved":
"@{user} è riservato mentre un admin valuta. Un'email col tuo ticket è in viaggio.",
"review.copyLink": "Copia link",
"review.openTicket": "Apri il mio ticket",
"admin.editUserTitle": "Modifica @{user}",
"admin.avatarUrl": "URL avatar",
};
const pt: Dict = {
"common.close": "Fechar",
"common.cancel": "Cancelar",
"common.save": "Guardar",
"common.done": "Pronto",
"common.copy": "Copiar",
"common.copied": "Copiado",
"common.create": "Criar",
"common.delete": "Eliminar",
"common.loading": "A carregar…",
"common.saving": "A guardar…",
"common.username": "Nome de utilizador",
"common.displayName": "Nome visível",
"common.select": "Escolher…",
"common.theme": "Tema",
"common.themeAuto": "Auto",
"common.themeAutoFull": "Auto (segue o sistema)",
"common.light": "Claro",
"common.dark": "Escuro",
"common.saveFailed": "Falha ao guardar",
"common.backToLogin": "Voltar ao login",
"loading.tagline": "A preparar os teus chats",
"login.welcome": "Bem-vindo de volta",
"login.subtitle": "Inicia sessão no KisAssistant para continuar",
"login.usernamePh": "Nome de utilizador",
"login.keyPh": "Chave de acesso",
"login.failed": "Falha no login",
"login.unlock": "Desbloquear",
"login.newHere": "Novo por aqui?",
"login.register": "Registar",
"login.regTipOn": "Criar uma conta",
"login.regTipOff": "Registo desativado nesta plataforma",
"login.requestAccess": "Pedir acesso",
"login.forgot": "Esqueceste a chave?",
"login.perk1": "Privado por design — sem cookies nem rastreio",
"login.perk2": "Os teus modelos, em tempo real",
"login.adminNotePre": "A chave admin vive no .env do servidor",
"login.adminNotePost": "; as chaves são emitidas pelo admin.",
"login.totpIntro": "Dois fatores para @{user} — escreve o código de 6 dígitos da app.",
"login.totpAria": "Código do autenticador",
"login.totpInvalid": "Código inválido",
"login.verify": "Verificar",
"sidebar.newChat": "Novo chat",
"sidebar.collapse": "Ocultar barra lateral",
"sidebar.closeDrawer": "Fechar chats",
"sidebar.searchPh": "Pesquisar chats…",
"sidebar.searchAria": "Pesquisar conversas",
"sidebar.clearSearch": "Limpar pesquisa",
"sidebar.searching": "A pesquisar…",
"sidebar.noMatch": "Sem resultados para «{q}».",
"sidebar.youPrefix": "tu: ",
"sidebar.emptyA": "Ainda sem conversas.",
"sidebar.emptyB": "Começa um novo chat acima.",
"sidebar.rename": "Renomear",
"sidebar.topic": "Definir tópico",
"sidebar.share": "Partilhar publicamente",
"sidebar.delete": "Eliminar",
"sidebar.convOptions": "Opções da conversa",
"sidebar.admin": "admin",
"sidebar.adminCenter": "Centro admin",
"sidebar.adminCenterTip": "Abrir o Centro admin",
"sidebar.settingsTip": "Perfil e definições",
"sidebar.logout": "Sair",
"chat.expand": "Mostrar barra lateral",
"chat.openNav": "Abrir chats",
"chat.modelAria": "Modelo",
"chat.noModels": "Sem modelos",
"chat.selectModel": "Escolher modelo…",
"chat.you": "Tu",
"chat.greet": "Como posso ajudar, {name}?",
"chat.greetSub": "Escolhe um modelo e escreve abaixo. Markdown suportado.",
"chat.thinking": "A pensar…",
"chat.placeholder": "Escreve ao KisAssistant…",
"chat.send": "Enviar",
"chat.attachTip": "Anexar ficheiro",
"chat.removeAttach": "Remover {name}",
"chat.ocrOpt": "Transcrever imagem",
"chat.unavailable": "(indisponível)",
"chat.ocrHint": "JPG, PNG, WEBP — OCR no servidor, revês o texto antes",
"chat.textOpt": "Anexar ficheiro de texto",
"chat.textHint": "TXT ou Markdown, máx. 500KB",
"chat.attachNote":
"Uploads só via ferramentas da plataforma. Imagens chegam ao modelo como texto revisto — nunca brutas.",
"chat.wiki": "Wiki e docs",
"attach.tooBigImg": "Imagem muito grande (máx. 5MB).",
"attach.tooBigText": "Ficheiro muito grande (máx. 500KB).",
"attach.uploadFailed": "Falha no upload",
"attach.kindOcr": "transcrição de imagem",
"attach.kindText": "ficheiro de texto",
"attach.tpl": "[anexo {kind}: {name}]",
"attach.chatError": "**Erro:** o modelo não respondeu. Tenta de novo.",
"dlg.editConv": "Editar conversa",
"dlg.fTitle": "Título",
"dlg.fTopic": "Tópico",
"dlg.topicHint": "Etiqueta curta, ex. cozinha, rust, viagens.",
"dlg.noTopic": "Sem tópico",
"dlg.recoverTitle": "Recuperar chave",
"dlg.recoverIntro":
"Escreve o teu username. Receberás um link que emite uma chave nova (a antiga deixa de funcionar).",
"dlg.sendLink": "Enviar link",
"dlg.reqFailed": "Pedido falhou",
"dlg.recoverSent":
"Se @{user} existe com email de recuperação, um link vai a caminho{fromPart} (válido 60 minutos, uso único).",
"dlg.recoverSentFrom": " de {from}",
"dlg.spamNote": "Vê o inbox — e o spam, pode ter caído lá.",
"dlg.noMailNote":
"Nada chega? Então não há email definido e não é possível — contacta o admin do site.",
"dlg.registerTitle": "Criar uma conta",
"dlg.regWelcome":
"Bem-vindo, @{user}! A tua chave — copia agora, não será mostrada de novo.",
"dlg.regContinue": "Copiar e ir ao login",
"dlg.regFailed": "Registo falhou",
"dlg.emailOpt": "Email (opcional, para recuperar a chave)",
"dlg.shareTitle": "Partilhar publicamente",
"dlg.shareCreating": "A criar link público…",
"dlg.shareIntro": "Quem tiver este link pode ler {title}. Sem login.",
"dlg.unshare": "Remover partilha",
"dlg.reviewOcr": "Rever transcrição",
"dlg.reviewText": "Rever anexo",
"dlg.transcribing": "A transcrever imagem no servidor (OCR)…",
"dlg.uploadingTools": "Upload via ferramentas…",
"dlg.ocrExpl":
"A imagem foi transcrita em texto no servidor — o modelo recebe este texto, nunca a imagem. Corrige abaixo e anexa.",
"dlg.textExpl":
"O ficheiro foi enviado via ferramentas. O texto será anexado à mensagem. Edita e anexa.",
"dlg.truncWarn": "Transcrição cortada no limite — falta o fim.",
"dlg.attachBtn": "Anexar à mensagem",
"dlg.chars": "{n} caracteres",
"settings.title": "Definições",
"settings.searchPh": "Pesquisar definições…",
"settings.searchAria": "Pesquisar definições",
"settings.clearSearch": "Limpar pesquisa",
"settings.noMatch": "Sem definições para «{q}».",
"settings.profile": "Perfil",
"settings.appearance": "Aparência",
"settings.features": "Funções",
"settings.avatarUpload": "Enviar avatar",
"settings.dropIdle": "Larga uma imagem ou clica para enviar",
"settings.dropActive": "Larga!",
"settings.uploading": "A enviar…",
"settings.avatarHint": "jpg · png · webp — máx. 5MB — 5 mudanças por 2h",
"settings.email": "Email de recuperação",
"settings.emailHint": "Opcional. Só para te enviar uma chave nova.",
"settings.accent": "Cor de destaque",
"settings.accentHint": "Aplicada na hora, guardada neste dispositivo.",
"settings.customColor": "Cor custom",
"settings.customAria": "Cor de destaque personalizada",
"settings.accentAria": "Destaque: {label}",
"settings.featHint": "Liga funções do editor. Na hora, neste dispositivo.",
"accent.preset.emerald": "Esmeralda",
"accent.preset.rose": "Rosa",
"accent.preset.blue": "Azul",
"accent.preset.violet": "Violeta",
"accent.preset.amber": "Amarelo",
"accent.preset.orange": "Laranja",
"accent.preset.teal": "Turquesa",
"accent.preset.pink": "Rosa choque",
"feat.thinking.label": "Raciocínio",
"feat.thinking.hint": "Pede ao modelo para raciocinar passo a passo.",
"feat.attachments.label": "Anexos",
"feat.attachments.hint": "Anexa imagens (transcrição OCR) e textos.",
"feat.deepSearch.label": "Pesquisa profunda",
"feat.deepSearch.hint": "Pesquisa fontes frescas na web. Em breve.",
"feat.soon": "(breve)",
"admin.title": "Contas",
"admin.intro":
"Contas sem KYC: crias um nome, entregas a chave uma vez. Hash guardado — regenera para rodar.",
"admin.keyOnce": "Chave para @{user} — mostrada uma vez, copia agora:",
"admin.newAccount": "Nova conta",
"admin.email": "Email (recuperação)",
"admin.createFailed": "Falha ao criar",
"admin.regenFailed": "Falha ao regenerar",
"admin.deleteFailed": "Falha ao eliminar",
"admin.loadFailed": "Falha ao carregar",
"admin.searchPh": "Pesquisar contas…",
"admin.searchAria": "Pesquisar contas",
"admin.sortAria": "Ordenar contas",
"admin.filterAria": "Filtrar contas",
"admin.sortNewest": "Mais novas",
"admin.sortOldest": "Mais antigas",
"admin.sortAz": "A → Z",
"admin.sortZa": "Z → A",
"admin.filterAll": "Todas as contas",
"admin.filterWith": "Com email",
"admin.filterNo": "Sem email",
"admin.countMany": "{total} contas · página {page} de {pages}",
"admin.countOne": "{total} conta · página {page} de {pages}",
"admin.noEmail": "sem email",
"admin.editProfile": "Editar perfil",
"admin.regenKey": "Regenerar chave (revoga sessões)",
"admin.deleteAccount": "Eliminar conta",
"admin.deleteTitle": "Eliminar @{user}?",
"admin.deleteMsg": "Remove para sempre conta, conversas e partilhas.",
"admin.prevPage": "Página anterior",
"admin.nextPage": "Página seguinte",
"ollama.models": "Modelos Ollama",
"ollama.desc": "Saca modelos para o host Ollama ou remove locais. Só admin.",
"ollama.pulling": "A sacar {name}",
"ollama.customTag": "Tag custom",
"ollama.customHint": "ex. llama3.2:3b, ou nome da biblioteca",
"ollama.pull": "Sacar",
"ollama.installed": "Instalados ({n})",
"ollama.noneYet": "Sem modelos locais ainda.",
"ollama.catalog": "Catálogo",
"ollama.delTitle": "Eliminar {name}?",
"ollama.delMsg": "O modelo sai do host. Saca de novo quando quiseres.",
"ollama.contacting": "A contactar Ollama…",
"ollama.pullingSt": "A sacar…",
"ollama.doneSt": "Pronto",
"ollama.pullFailed": "Falha no download",
"ollama.delTip": "Eliminar {name}",
"ollama.sizeAria": "tamanho de {name}",
"ollama.cat.llama3_2": "Compacto Meta para o dia a dia",
"ollama.cat.llama3_1": "O workhorse da Meta",
"ollama.cat.qwen2_5": "Multilingue Alibaba versátil",
"ollama.cat.gemma2": "Aberto e eficiente do Google",
"ollama.cat.mistral": "Mistral 7B — rápido e capaz",
"ollama.cat.phi3": "Pequeno modelo Microsoft",
"ollama.cat.deepseek_r1": "Modelo de raciocínio (cadeia de pensamento)",
"ollama.cat.codellama": "Código e chat de código",
"ollama.cat.llava": "Visão (percebe imagens)",
"ollama.cat.nomic_embed_text": "Embeddings de texto",
"center.title": "Centro admin",
"center.back": "Voltar ao chat",
"center.tabAccounts": "Contas",
"center.tabFeatures": "Funções",
"center.tabMail": "Mail",
"center.locked": "Só admins",
"center.lockedSub": "Entra como admin para abrir o Centro.",
"center.regTitle": "Registo público",
"center.regDesc": "Mostra Registar no login. Off = só contas do admin.",
"center.accTitle": "Pedidos de acesso (lista)",
"center.accDescOn": "Indisponível com registo público — substitui.",
"center.accDescOff": "Mostra Pedir acesso no login. Triagem na aba Acesso.",
"center.ocrTitle": "Ferramenta OCR",
"center.ocrDesc": "Transcrição no servidor. Precisa tesseract.",
"center.mailTitle": "Emails de recuperação",
"center.mailChecking": "A verificar…",
"center.mailFrom": "Ativo — enviados de {from}",
"center.mailDefault": "Ativo — enviados do remetente",
"center.mailOff": "Desligado — define SMTP_HOST",
"center.mailNote":
"Credenciais SMTP vivem no servidor (ver docs/OPERATIONS.md), nunca aqui. Email define-se no perfil ou criação.",
"center.on": "Sim",
"center.off": "Não",
"share.fallback": "Chat partilhado",
"share.open": "Abrir KisAssistant",
"share.gone": "Este chat partilhado sumiu",
"share.goneSub": "Link inválido, expirado ou removido pelo autor.",
"share.back": "Voltar",
"share.loading": "A carregar chat…",
"share.by": "por {name}",
"reset.checking": "A verificar link…",
"reset.invalid": "Link inválido",
"reset.invalidSub":
"Link desconhecido, usado ou expirado. Pede outro no login.",
"reset.readyTitle": "Nova chave para @{user}?",
"reset.readySub": "Emite já uma chave nova. A antiga morre.",
"reset.issue": "Emitir nova chave",
"reset.issuing": "A emitir…",
"reset.doneTitle": "Nova chave pronta",
"reset.doneSub": "Copia agora — não será mostrada de novo. Entra com ela.",
"reset.goLogin": "Ir ao login",
"reset.failed": "Falha na recuperação",
"app.deleteConvTitle": "Eliminar conversa?",
"app.deleteConvMsg": "«{title}» e mensagens serão eliminados.",
"settings.language": "Idioma",
"settings.languageHint": "Idioma da interface. Na hora, neste dispositivo.",
"settings.security": "Segurança",
"sec.accessKey": "Chave de acesso",
"sec.accessKeyHint":
"Roda a tua chave. Sessões antigas morrem na hora — entras com a nova.",
"sec.rotate": "Rodar a minha chave",
"sec.rotateConfirm": "Clica de novo para confirmar",
"sec.rotationFailed": "Falha ao rodar",
"sec.totp": "Dois fatores (TOTP)",
"sec.totpChecking": "A verificar…",
"sec.totpAddHint":
"Adiciona um código de 6 dígitos da app além da chave.",
"sec.totpEnable": "Ativar dois fatores",
"sec.totpSecretHint":
"Adiciona este segredo à app (ou abre o link otpauth) e escreve um código:",
"sec.totpConfirm": "Confirmar",
"sec.totpDisableLabel": "Desligar com um código atual",
"sec.totpDisable": "Desligar 2FA",
"sec.setupFailed": "Falha ao configurar",
"sec.passkeys": "Passkeys",
"sec.passkeysHint":
"WebAuthn / autenticadores do sistema — precisa RP no servidor.",
"sec.danger": "Zona perigosa",
"sec.dangerHint":
"Elimina a conta com chats, partilhas e sessões. Sem volta.",
"sec.deleteMe": "Eliminar a minha conta",
"sec.deleteFailed": "Falha ao eliminar",
"sec.newKeyTitle": "Nova chave de acesso",
"sec.newKeyHint":
"Copia agora — não será mostrada de novo. Sessões antigas morrem.",
"sec.newKeyDone": "Pronto — sair",
"sec.deleteTitle": "Eliminar a tua conta?",
"sec.deleteMsg":
"Remove para sempre a conta com conversas, partilhas e sessões. Sem volta.",
"sec.deleteForever": "Eliminar para sempre",
"sec.avatarTooBig": "Ficheiro muito grande (máx. 5MB).",
"sec.avatarType": "Só imagens jpg, png ou webp.",
"center.tabAccess": "Acesso",
"access.status.pending": "Pendente",
"access.status.reviewing": "Em revisão",
"access.status.accepted": "Aceite",
"access.status.refused": "Recusado",
"access.filter.all": "todos",
"access.filter.pending": "pendentes",
"access.filter.reviewing": "em revisão",
"access.filter.accepted": "aceites",
"access.filter.refused": "recusados",
"access.refresh": "Atualizar",
"access.refreshAria": "Atualizar pedidos",
"access.empty": "Sem pedidos. A lista está vazia.",
"access.emptyFiltered": "Nada com este estado.",
"access.msgOne": "1 mensagem",
"access.msgMany": "{n} mensagens",
"access.accountCreated": "Conta criada — chave única, copia agora:",
"access.replyPh": "Responder (chega por email)…",
"access.replyAria": "Responder ao requerente",
"access.reasonLabel": "Motivo (enviado com a decisão)",
"access.reasonPh": "Motivo opcional…",
"access.btnReviewing": "Em revisão",
"access.btnAccept": "Aceitar",
"access.btnRefuse": "Recusar",
"access.closedNote": "Ticket fechado ({status}).",
"access.reasonWord": "Motivo",
"access.youAdmin": "Tu (admin)",
"access.sendFailed": "Falha ao enviar",
"access.updateFailed": "Falha ao atualizar",
"review.title": "Pedido de acesso",
"review.loadingTicket": "A carregar ticket…",
"review.notFound": "Ticket não encontrado",
"review.notFoundSub":
"Link desconhecido. Vê o URL do teu email ou pede acesso no login.",
"review.adminNote": "Nota do admin:",
"review.acceptedHint":
"Conta criada — vê o email com a chave e entra.",
"review.closedHint":
"Ticket fechado. Emails não são lidos — se preciso, pede de novo.",
"review.replyPh": "Defende o teu caso…",
"review.replyAria": "Responder ao teu ticket",
"review.mailHint":
"Emails são de sentido único — responde aqui, não por email.",
"review.status.pending": "Pendente de revisão",
"review.status.reviewing": "Em revisão",
"review.status.accepted": "Aceite",
"review.status.refused": "Recusado",
"review.admin": "Admin",
"review.you": "Tu",
"review.requestTitle": "Pedir acesso",
"review.requestIntro":
"Reserva um nome e diz porque queres entrar. Um admin revê cada pedido — segues o fio no teu ticket e por email.",
"review.emailHint": "Obrigatório — avisos e respostas chegam aqui.",
"review.whyLabel": "Porque dar-te acesso?",
"review.whyHint": "10+ caracteres. Isto abre o teu ticket.",
"review.whyPh": "Quero…",
"review.sendRequest": "Enviar pedido",
"review.received": "Pedido recebido",
"review.reserved":
"@{user} fica reservado enquanto um admin revê. Um email com o teu ticket vai a caminho.",
"review.copyLink": "Copiar link",
"review.openTicket": "Abrir o meu ticket",
"admin.editUserTitle": "Editar @{user}",
"admin.avatarUrl": "URL do avatar",
};
const dicts: Record<LangId, Dict> = { en, es, fr, ru, it, pt };
const STORE_KEY = "kisassistant_lang";
function detectLang(): LangId {
try {
const saved = localStorage.getItem(STORE_KEY);
if (
saved === "en" ||
saved === "es" ||
saved === "fr" ||
saved === "ru" ||
saved === "it" ||
saved === "pt"
) {
return saved;
}
} catch {
// storage unavailable — fall through to browser detection
}
const nav = (navigator.language || "en").toLowerCase().split("-")[0];
if (
nav === "es" ||
nav === "fr" ||
nav === "ru" ||
nav === "it" ||
nav === "pt"
)
return nav;
return "en";
}
let current: LangId = detectLang();
if (typeof document !== "undefined") document.documentElement.lang = current;
const listeners = new Set<() => void>();
export function getLang(): LangId {
return current;
}
/** Persist the language (per browser) and re-render every subscriber. */
export function setLang(lang: LangId): void {
if (current === lang) return;
current = lang;
try {
localStorage.setItem(STORE_KEY, lang);
} catch {
// ignore
}
if (typeof document !== "undefined") document.documentElement.lang = lang;
listeners.forEach((fn) => fn());
}
/** Global translate function: t("login.welcome"), t("chat.greet", { name }). */
export function t(
key: StringKey,
vars?: Record<string, string | number>,
): string {
let s: string = dicts[current][key] ?? dicts.en[key] ?? key;
if (vars) {
for (const [k, v] of Object.entries(vars))
s = s.replaceAll(`{${k}}`, String(v));
}
return s;
}
/** Reactive hook — components using translated strings must call it so they re-render on language switch. */
export function useT(): { t: typeof t; lang: LangId } {
useSyncExternalStore(
(fn) => {
listeners.add(fn);
return () => {
listeners.delete(fn);
};
},
() => current,
);
return { t, lang: current };
}