diff --git a/apps/geolibre-desktop/src/components/layout/ShareProjectDialog.tsx b/apps/geolibre-desktop/src/components/layout/ShareProjectDialog.tsx index 19b728d1d..56ed79dd3 100644 --- a/apps/geolibre-desktop/src/components/layout/ShareProjectDialog.tsx +++ b/apps/geolibre-desktop/src/components/layout/ShareProjectDialog.tsx @@ -36,7 +36,9 @@ interface ShareProjectDialogProps { * Lazily serialize the current project (under the given title) when the user * confirms the upload. */ - getProject: (title: string) => Promise<{ content: string; filename: string }>; + getProject: ( + title: string, + ) => Promise<{ content: string; filename: string; redactedCount?: number }>; } /** @@ -72,6 +74,7 @@ export function ShareProjectDialog({ const [errorCode, setErrorCode] = useState(null); const [result, setResult] = useState(null); const [copied, setCopied] = useState(false); + const [redactedCount, setRedactedCount] = useState(0); const abortRef = useRef(null); const copyTimeoutRef = useRef(null); @@ -88,6 +91,7 @@ export function ShareProjectDialog({ setErrorCode(null); setResult(null); setCopied(false); + setRedactedCount(0); } else { abortRef.current?.abort(); abortRef.current = null; @@ -117,7 +121,7 @@ export function ShareProjectDialog({ const controller = new AbortController(); abortRef.current = controller; try { - const { content, filename } = await getProject(title.trim()); + const { content, filename, redactedCount: removed = 0 } = await getProject(title.trim()); const uploaded = await uploadProjectToShare({ token: shareToken, filename, @@ -125,6 +129,7 @@ export function ShareProjectDialog({ visibility, signal: controller.signal, }); + setRedactedCount(removed); setResult(uploaded); } catch (err) { if (err instanceof DOMException && err.name === "AbortError") return; @@ -234,6 +239,11 @@ export function ShareProjectDialog({ ) : result ? (
+ {redactedCount > 0 ? ( +

+ {t("share.credentialsRemoved", { count: redactedCount })} +

+ ) : null}

{t("share.liveAt")}

diff --git a/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx b/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx index 16d95c87e..653fc626c 100644 --- a/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx +++ b/apps/geolibre-desktop/src/components/layout/TopToolbar.tsx @@ -1,4 +1,9 @@ -import { DEFAULT_PROJECT_NAME, useAppStore } from "@geolibre/core"; +import { + DEFAULT_PROJECT_NAME, + redactProjectCredentials, + serializeProject, + useAppStore, +} from "@geolibre/core"; import { DEFAULT_BUILT_IN_CONTROL_VISIBILITY, type MapController } from "@geolibre/map"; import { closeDuckDBLayerPanel, @@ -1996,7 +2001,8 @@ export function TopToolbar({ getProject={async (title) => { // Shared projects are opened on another machine where the local files // don't exist, so always embed the vector data (never file references). - const { content, defaultProjectName } = await projectFiles.buildEmbeddedProject(title); + const { project, defaultProjectName } = await projectFiles.buildEmbeddedProject(title); + const redacted = redactProjectCredentials(project); // Strip path separators, control chars, and other characters that are // illegal in filenames so the server gets a predictable name. const safeName = defaultProjectName.replace( @@ -2006,7 +2012,11 @@ export function TopToolbar({ /[\u0000-\u001f\u007f/\\:*?"<>|]/g, "_", ); - return { content, filename: `${safeName}.geolibre.json` }; + return { + content: serializeProject(redacted.project), + filename: `${safeName}.geolibre.json`, + redactedCount: redacted.redactedCount, + }; }} /> { - if (!open) projectFiles.resolveEnvStripPrompt("cancel"); + if (!open) projectFiles.resolveCredentialStripPrompt("cancel"); }} > @@ -206,18 +206,24 @@ export function ProjectFileDialogs({ projectFiles }: ProjectFileDialogsProps) { {t("settings.env.stripPromptTitle")} {t("settings.env.stripPromptDesc", { - count: projectFiles.envStripPrompt?.count ?? 0, + count: projectFiles.credentialStripPrompt?.count ?? 0, })}
- - -
diff --git a/apps/geolibre-desktop/src/hooks/embedHost.ts b/apps/geolibre-desktop/src/hooks/embedHost.ts index 0d0a2844e..f8f55c6ac 100644 --- a/apps/geolibre-desktop/src/hooks/embedHost.ts +++ b/apps/geolibre-desktop/src/hooks/embedHost.ts @@ -19,8 +19,10 @@ import { EMBED_ORIGIN_WILDCARD, isEmbedOriginAllowed, readEmbedOrigins } from ". * not throw cross-origin, so it can't be used for this). A random cross-origin * page that iframes a deployed app therefore never auto-activates the bridge. * The explicit `?embed=1` opt-in, however, trusts whatever the framing parent - * is — the bridge - * broadcasts full project state to it. Because the legitimate hosts (the Jupyter + * is — the bridge broadcasts project state to it, redacted unless that host + * asks for full fidelity with `trustedWidget` (see `useEmbedBridge.ts`, where + * that flag is documented as self-declared and therefore not a boundary of its + * own). Because the legitimate hosts (the Jupyter * widget, Colab's proxy) have arbitrary, unknowable origins, an origin allowlist * is not viable here; instead the deployment constraint is: an `?embed=1` * export must only be served from a trusted context, never a public URL. A diff --git a/apps/geolibre-desktop/src/hooks/useCollaboration.ts b/apps/geolibre-desktop/src/hooks/useCollaboration.ts index 17d97ab1d..7032da7fd 100644 --- a/apps/geolibre-desktop/src/hooks/useCollaboration.ts +++ b/apps/geolibre-desktop/src/hooks/useCollaboration.ts @@ -13,7 +13,7 @@ import type { RefObject } from "react"; import type { MapController } from "@geolibre/map"; import type { Map as MapLibreMap } from "maplibre-gl"; import i18n from "../i18n"; -import { buildProjectSnapshot } from "../lib/build-project-snapshot"; +import { buildProjectEgressSnapshot } from "../lib/build-project-snapshot"; import { projectChanged } from "../lib/project-broadcast-changed"; import { CollabConnection, @@ -76,7 +76,7 @@ export function useCollaboration( const sendSnapshot = (): void => { if (!canEdit() || syncPausedRef.current) return; - const project = buildProjectSnapshot(mapControllerRef); + const project = buildProjectEgressSnapshot(mapControllerRef); const content = serializeProject(project); if (content === lastContentRef.current) return; lastContentRef.current = content; @@ -105,7 +105,7 @@ export function useCollaboration( clearHistory(); scheduleRestore(); } - lastContentRef.current = serializeProject(buildProjectSnapshot(mapControllerRef)); + lastContentRef.current = serializeProject(buildProjectEgressSnapshot(mapControllerRef)); }; const handleMessage = (message: ServerMessage): void => { diff --git a/apps/geolibre-desktop/src/hooks/useEmbedBridge.ts b/apps/geolibre-desktop/src/hooks/useEmbedBridge.ts index e1a9dbb94..91f021196 100644 --- a/apps/geolibre-desktop/src/hooks/useEmbedBridge.ts +++ b/apps/geolibre-desktop/src/hooks/useEmbedBridge.ts @@ -1,7 +1,7 @@ import { parseProject, serializeProject, useAppStore, type GeoLibreProject } from "@geolibre/core"; import { type RefObject, useEffect } from "react"; import type { MapController } from "@geolibre/map"; -import { buildProjectSnapshot } from "../lib/build-project-snapshot"; +import { buildProjectEgressSnapshot, buildProjectSnapshot } from "../lib/build-project-snapshot"; import { getEmbedHost, isEmbedded } from "./embedHost"; // How long to wait after the last store change before posting a fresh project @@ -13,6 +13,8 @@ interface LoadProjectMessage { type: "geolibre:load-project"; project: GeoLibreProject | string; seq?: number; + /** Set only by the co-located anywidget host, which retains local credentials. */ + trustedWidget?: boolean; } interface RequestStateMessage { @@ -36,13 +38,23 @@ type InboundMessage = LoadProjectMessage | RequestStateMessage; * received from the app back into the iframe. Outside an embedding host the * hook is an inert no-op. * - * Trust model: the embedding host is fully trusted and receives the entire - * project state. Project snapshots are not broadcast until the host sends its - * first message (which is also when the bridge learns its origin and scopes - * subsequent posts to it); only the version-only `geolibre:ready` ping precedes - * the handshake and is the single message sent to `"*"`. Any page that frames - * the app (not just the Jupyter widget) therefore becomes that trusted host, so - * `?embed=1` standalone exports should only be served from a trusted context. + * Trust model: snapshots are redacted by default, so a framing page sees project + * structure without credentials. The co-located anywidget host opts back into + * full fidelity with `trustedWidget` on its load message, because Python's + * `self.project` trait is the same object `to_project(keep_credentials=True)` + * returns and would otherwise lose credentials on the next pan. + * + * That flag is self-declared by the host, so it is a fidelity switch, not a + * security boundary: any page that frames the app can set it and get an + * unredacted snapshot back. It narrows accidental exposure to hosts that never + * ask, not deliberate exposure — the boundary remains the framing context + * itself, which is why `?embed=1` standalone exports should only be served from + * a trusted one. + * + * Project snapshots are not broadcast until the host sends its first message + * (which is also when the bridge learns its origin and scopes subsequent posts + * to it); only the version-only `geolibre:ready` ping precedes the handshake and + * is the single message sent to `"*"`. * * @param mapControllerRef - Ref to the live map controller, read so the emitted * snapshot captures the current camera (pan/zoom) rather than only the store. @@ -62,8 +74,12 @@ export function useEmbedBridge(mapControllerRef: RefObject // correlate a snapshot with the load that triggered it. let lastLoadedSeq = 0; let lastPostedContent: string | null = null; + let trustedWidget = false; - const buildProject = (): GeoLibreProject => buildProjectSnapshot(mapControllerRef); + const buildProject = (): GeoLibreProject => + trustedWidget + ? buildProjectSnapshot(mapControllerRef) + : buildProjectEgressSnapshot(mapControllerRef); const postState = () => { if (disposed) return; @@ -103,6 +119,7 @@ export function useEmbedBridge(mapControllerRef: RefObject }; const applyLoad = (message: LoadProjectMessage) => { + trustedWidget = message.trustedWidget === true; // Advance the seq before parsing so a later snapshot carries the right // correlation id even when the load fails. Reset (not retain) when a load // omits seq, so a snapshot never echoes a stale, unrelated sequence number. diff --git a/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts b/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts index 2a5d53902..15227ad35 100644 --- a/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts +++ b/apps/geolibre-desktop/src/hooks/useProjectFileActions.ts @@ -2,6 +2,7 @@ import { DEFAULT_PROJECT_NAME, detachProjectCopy, projectFromStore, + redactProjectCredentials, serializeProject, useAppStore, type GeoLibreLayer, @@ -50,8 +51,8 @@ import { import { importArcgisProject, type ArcgisProjectImportWarning } from "../lib/arcgis-project-import"; import type { MapControllerRef } from "../components/layout/toolbar/constants"; -/** A pending "strip env vars before saving?" prompt. */ -export interface EnvStripPrompt { +/** A pending "strip credentials before saving?" prompt. */ +export interface CredentialStripPrompt { count: number; resolve: (choice: "strip" | "keep" | "cancel") => void; } @@ -220,7 +221,9 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) { const [projectUrl, setProjectUrl] = useState(""); const [projectUrlError, setProjectUrlError] = useState(null); const [projectUrlLoading, setProjectUrlLoading] = useState(false); - const [envStripPrompt, setEnvStripPrompt] = useState(null); + const [credentialStripPrompt, setCredentialStripPrompt] = useState( + null, + ); const [embedVectorDataPrompt, setEmbedVectorDataPrompt] = useState( null, ); @@ -659,17 +662,18 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) { }; }; - // Ask whether to strip environment variables before writing the file. The - // promise resolves when the user picks an option in the dialog. - const askStripEnvVars = (count: number) => + // Ask whether to strip credentials (environment variables, geocoder keys, + // layer tokens) before writing the file. The promise resolves when the user + // picks an option in the dialog. + const askStripCredentials = (count: number) => new Promise<"strip" | "keep" | "cancel">((resolve) => { - setEnvStripPrompt({ count, resolve }); + setCredentialStripPrompt({ count, resolve }); }); - const resolveEnvStripPrompt = (choice: "strip" | "keep" | "cancel") => { + const resolveCredentialStripPrompt = (choice: "strip" | "keep" | "cancel") => { // Resolve outside the state updater (updaters must be side-effect free). - envStripPrompt?.resolve(choice); - setEnvStripPrompt(null); + credentialStripPrompt?.resolve(choice); + setCredentialStripPrompt(null); }; // Ask whether to embed local vector layers' data in the saved file. Resolves @@ -835,20 +839,16 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) { undefined, layersForSave.layers, ); - // Env vars (possibly API keys) are serialized in plain text. If any are set, - // offer to strip them from the saved file before writing. + // Credentials are serialized in plain text for a local project that needs + // them. Make keeping them an explicit choice and use the same central + // redaction pass as every external egress. let contentToSave = content; - const envVarCount = (project.preferences.environmentVariables ?? []).filter((variable) => - variable.key.trim(), - ).length; - if (envVarCount > 0) { - const choice = await askStripEnvVars(envVarCount); + const redacted = redactProjectCredentials(project); + if (redacted.redactedPaths.length > 0) { + const choice = await askStripCredentials(redacted.redactedCount); if (choice === "cancel") return false; if (choice === "strip") { - contentToSave = serializeProject({ - ...project, - preferences: { ...project.preferences, environmentVariables: [] }, - }); + contentToSave = serializeProject(redacted.project); } } // Projects opened from a URL have no writable path, so both Save and @@ -947,18 +947,15 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) { if (chosen === null) return false; defaultName = ensureHtmlFileName(chosen, slug); } - // Only now embed local vector data (self-contained, like Share) and strip - // env vars (secrets serve no purpose in a static viewer): this can be - // costly on a project with many local layers, so it runs after the user + // Only now embed local vector data (self-contained, like Share): this can + // be costly on a project with many local layers, so it runs after the user // has committed to the export rather than before the prompt. Reuse the - // name snapshot so the title matches the slug computed above. + // name snapshot so the title matches the slug computed above. Credentials + // serve no purpose in a static viewer and are removed inside + // buildProjectHtml, which runs the central redaction pass. const { project, defaultProjectName } = await buildEmbeddedProject(projectName); - const safeProject = { - ...project, - preferences: { ...project.preferences, environmentVariables: [] }, - }; const html = buildProjectHtml({ - project: safeProject, + project, title: defaultProjectName, }); // Returns null when the user cancels the save dialog; report that as a @@ -1024,8 +1021,8 @@ export function useProjectFileActions(mapControllerRef: MapControllerRef) { setSaveTemplateDialogOpen, handleDuplicate, handleSaveAsTemplate: () => setSaveTemplateDialogOpen(true), - envStripPrompt, - resolveEnvStripPrompt, + credentialStripPrompt, + resolveCredentialStripPrompt, embedVectorDataPrompt, resolveEmbedVectorDataPrompt, saveNamePrompt, diff --git a/apps/geolibre-desktop/src/i18n/locales/ar.json b/apps/geolibre-desktop/src/i18n/locales/ar.json index c3681ffb6..40fa6ace7 100644 --- a/apps/geolibre-desktop/src/i18n/locales/ar.json +++ b/apps/geolibre-desktop/src/i18n/locales/ar.json @@ -1312,7 +1312,8 @@ "sharing": "جارٍ المشاركة…", "errorFallback": "تعذّرت مشاركة المشروع.", "usernameRequired": "عيّن اسم مستخدم في حسابك على {{shareHost}} قبل المشاركة. افتح إعدادات حسابك لاختيار اسم، ثم حاول مرة أخرى.", - "openAccountSettings": "فتح إعدادات الحساب" + "openAccountSettings": "فتح إعدادات الحساب", + "credentialsRemoved": "لم يتم تضمين {{count}} من حقول بيانات الاعتماد. يجب على المستلمين تقديم بيانات اعتمادهم أو استخدام مرجع عبر وسيط." }, "gallery": { "title": "معرض المشاريع", @@ -1986,9 +1987,9 @@ "removeAria": "إزالة {{name}}", "errorNamePattern": "يجب أن تبدأ أسماء متغيرات البيئة بحرف أو شرطة سفلية وأن تحتوي على أحرف وأرقام وشرطات سفلية فقط.", "errorDuplicate": "متغير البيئة «{{name}}» مكرر.", - "stripPromptTitle": "هل تريد إزالة متغيرات البيئة؟", - "stripPromptDesc": "يحتوي هذا المشروع على {{count}} من متغيرات البيئة (قد تتضمن مفاتيح API). وهي مخزنة كنص عادي في ملف المشروع وقد تنكشف إذا شاركته. هل تريد إزالتها من الملف المحفوظ؟ ستبقيها الإعدادات على هذا الجهاز في كلتا الحالتين.", - "stripButton": "إزالة من الملف", + "stripPromptTitle": "هل تريد إزالة بيانات الاعتماد؟", + "stripPromptDesc": "يحتوي هذا المشروع على {{count}} من الحقول التي قد تحمل بيانات اعتماد، مثل مفاتيح API أو ترويسات الطلب أو إعدادات الملحقات. هل تريد إزالتها من الملف المحفوظ؟ ستظل مساحة العمل الحالية محتفظة بها في كلتا الحالتين.", + "stripButton": "إزالة بيانات الاعتماد", "keepButton": "الإبقاء في الملف" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/de.json b/apps/geolibre-desktop/src/i18n/locales/de.json index d4632308d..9859150e5 100644 --- a/apps/geolibre-desktop/src/i18n/locales/de.json +++ b/apps/geolibre-desktop/src/i18n/locales/de.json @@ -1172,7 +1172,8 @@ "sharing": "Wird geteilt…", "errorFallback": "Das Projekt konnte nicht geteilt werden.", "usernameRequired": "Legen Sie einen Benutzernamen für Ihr {{shareHost}}-Konto fest, bevor Sie teilen. Öffnen Sie Ihre Kontoeinstellungen, um einen auszuwählen, und versuchen Sie es erneut.", - "openAccountSettings": "Kontoeinstellungen öffnen" + "openAccountSettings": "Kontoeinstellungen öffnen", + "credentialsRemoved": "{{count}} Anmeldedatenfeld(er) wurden nicht einbezogen. Empfänger müssen eigene Anmeldedaten angeben oder eine vermittelte Referenz verwenden." }, "gallery": { "title": "Projektgalerie", @@ -1819,9 +1820,9 @@ "removeAria": "{{name}} entfernen", "errorNamePattern": "Namen von Umgebungsvariablen müssen mit einem Buchstaben oder Unterstrich beginnen und dürfen nur Buchstaben, Zahlen und Unterstriche enthalten.", "errorDuplicate": "Die Umgebungsvariable „{{name}}“ ist doppelt vorhanden.", - "stripPromptTitle": "Umgebungsvariablen entfernen?", - "stripPromptDesc": "Dieses Projekt enthält {{count}} Umgebungsvariable(n) (die API-Schlüssel enthalten können). Sie werden im Klartext in der Projektdatei gespeichert und könnten beim Teilen offengelegt werden. Aus der gespeicherten Datei entfernen? Ihre Einstellungen behalten sie in jedem Fall auf diesem Gerät.", - "stripButton": "Aus Datei entfernen", + "stripPromptTitle": "Anmeldedaten entfernen?", + "stripPromptDesc": "Dieses Projekt enthält {{count}} Feld(er) mit Anmeldedaten, etwa API-Schlüssel, Anfrage-Header oder Plugin-Einstellungen. Aus der gespeicherten Datei entfernen? Der aktuelle Arbeitsbereich behält sie in jedem Fall.", + "stripButton": "Anmeldedaten entfernen", "keepButton": "In Datei behalten" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/en.json b/apps/geolibre-desktop/src/i18n/locales/en.json index 352762b03..693ddf518 100644 --- a/apps/geolibre-desktop/src/i18n/locales/en.json +++ b/apps/geolibre-desktop/src/i18n/locales/en.json @@ -1158,6 +1158,7 @@ "step2Description": "Paste the token into Settings → Environment Variables on this device.", "configureToken": "Configure local token", "liveAt": "Your project is live at:", + "credentialsRemoved": "{{count}} credential field(s) were not included. Recipients must provide their own credentials or use a brokered reference.", "copyLink": "Copy link", "open": "Open", "done": "Done", @@ -1819,9 +1820,9 @@ "removeAria": "Remove {{name}}", "errorNamePattern": "Environment variable names must start with a letter or underscore and contain only letters, numbers, and underscores.", "errorDuplicate": "Environment variable \"{{name}}\" is duplicated.", - "stripPromptTitle": "Strip environment variables?", - "stripPromptDesc": "This project has {{count}} environment variable(s) (which may include API keys). They are stored in plain text in the project file and could be exposed if you share it. Remove them from the saved file? Your Settings keep them on this device either way.", - "stripButton": "Strip from file", + "stripPromptTitle": "Strip credentials?", + "stripPromptDesc": "This project has {{count}} credential-bearing field(s), such as API keys, request headers, or plugin settings. Remove them from the saved file? The current workspace keeps them either way.", + "stripButton": "Strip credentials", "keepButton": "Keep in file" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/es.json b/apps/geolibre-desktop/src/i18n/locales/es.json index b0a89cb84..8578c1bab 100644 --- a/apps/geolibre-desktop/src/i18n/locales/es.json +++ b/apps/geolibre-desktop/src/i18n/locales/es.json @@ -1172,7 +1172,8 @@ "sharing": "Compartiendo…", "errorFallback": "No se pudo compartir el proyecto.", "usernameRequired": "Configure un nombre de usuario en su cuenta de {{shareHost}} antes de compartir. Abra la configuración de su cuenta para elegir uno y vuelva a intentarlo.", - "openAccountSettings": "Abrir configuración de la cuenta" + "openAccountSettings": "Abrir configuración de la cuenta", + "credentialsRemoved": "No se incluyeron {{count}} campo(s) de credenciales. Los destinatarios deben proporcionar sus propias credenciales o usar una referencia intermediada." }, "gallery": { "title": "Galería de proyectos", @@ -1819,9 +1820,9 @@ "removeAria": "Quitar {{name}}", "errorNamePattern": "Los nombres de las variables de entorno deben comenzar con una letra o un guion bajo y contener solo letras, números y guiones bajos.", "errorDuplicate": "La variable de entorno «{{name}}» está duplicada.", - "stripPromptTitle": "¿Eliminar variables de entorno?", - "stripPromptDesc": "Este proyecto tiene {{count}} variable(s) de entorno (que pueden incluir claves de API). Se almacenan en texto sin formato en el archivo del proyecto y podrían quedar expuestas si lo comparte. ¿Eliminarlas del archivo guardado? Su configuración las conserva en este dispositivo de todos modos.", - "stripButton": "Eliminar del archivo", + "stripPromptTitle": "¿Eliminar credenciales?", + "stripPromptDesc": "Este proyecto tiene {{count}} campo(s) que contienen credenciales, como claves de API, encabezados de solicitud o ajustes de complementos. ¿Deseas eliminarlos del archivo guardado? El espacio de trabajo actual los conservará en cualquier caso.", + "stripButton": "Eliminar credenciales", "keepButton": "Mantener en el archivo" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/fr.json b/apps/geolibre-desktop/src/i18n/locales/fr.json index 1abba099c..e34ae0dec 100644 --- a/apps/geolibre-desktop/src/i18n/locales/fr.json +++ b/apps/geolibre-desktop/src/i18n/locales/fr.json @@ -1172,7 +1172,8 @@ "sharing": "Partage en cours…", "errorFallback": "Impossible de partager le projet.", "usernameRequired": "Définissez un nom d'utilisateur sur votre compte {{shareHost}} avant de partager. Ouvrez les paramètres de votre compte pour en choisir un, puis réessayez.", - "openAccountSettings": "Ouvrir les paramètres du compte" + "openAccountSettings": "Ouvrir les paramètres du compte", + "credentialsRemoved": "{{count}} champ(s) d’identifiants n’ont pas été inclus. Les destinataires doivent fournir leurs propres identifiants ou utiliser une référence gérée par un courtier." }, "gallery": { "title": "Galerie de projets", @@ -1819,9 +1820,9 @@ "removeAria": "Supprimer {{name}}", "errorNamePattern": "Les noms de variables d'environnement doivent commencer par une lettre ou un tiret bas et ne contenir que des lettres, des chiffres et des tirets bas.", "errorDuplicate": "La variable d'environnement « {{name}} » est en double.", - "stripPromptTitle": "Retirer les variables d'environnement ?", - "stripPromptDesc": "Ce projet contient {{count}} variable(s) d'environnement (qui peuvent inclure des clés API). Elles sont stockées en texte brut dans le fichier de projet et pourraient être exposées si vous le partagez. Les retirer du fichier enregistré ? Vos Paramètres les conservent sur cet appareil dans tous les cas.", - "stripButton": "Retirer du fichier", + "stripPromptTitle": "Retirer les identifiants ?", + "stripPromptDesc": "Ce projet contient {{count}} champ(s) pouvant contenir des identifiants, comme des clés API, des en-têtes de requête ou des paramètres de plugins. Les retirer du fichier enregistré ? L’espace de travail actuel les conserve dans tous les cas.", + "stripButton": "Retirer les identifiants", "keepButton": "Conserver dans le fichier" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/hi.json b/apps/geolibre-desktop/src/i18n/locales/hi.json index 6f6bda65f..c6c2b4981 100644 --- a/apps/geolibre-desktop/src/i18n/locales/hi.json +++ b/apps/geolibre-desktop/src/i18n/locales/hi.json @@ -1172,7 +1172,8 @@ "sharing": "साझा किया जा रहा है…", "errorFallback": "प्रोजेक्ट साझा नहीं किया जा सका।", "usernameRequired": "साझा करने से पहले अपने {{shareHost}} खाते पर एक उपयोगकर्ता नाम सेट करें। एक चुनने के लिए अपनी खाता सेटिंग्स खोलें, फिर पुनः प्रयास करें।", - "openAccountSettings": "खाता सेटिंग्स खोलें" + "openAccountSettings": "खाता सेटिंग्स खोलें", + "credentialsRemoved": "{{count}} क्रेडेंशियल फ़ील्ड शामिल नहीं किए गए। प्राप्तकर्ताओं को अपने क्रेडेंशियल देने होंगे या ब्रोकर किए गए संदर्भ का उपयोग करना होगा।" }, "gallery": { "title": "प्रोजेक्ट गैलरी", @@ -1819,9 +1820,9 @@ "removeAria": "{{name}} हटाएँ", "errorNamePattern": "एनवायरनमेंट वेरिएबल नाम किसी अक्षर या अंडरस्कोर से शुरू होने चाहिए और उनमें केवल अक्षर, संख्याएँ और अंडरस्कोर हो सकते हैं।", "errorDuplicate": "एनवायरनमेंट वेरिएबल \"{{name}}\" डुप्लिकेट है।", - "stripPromptTitle": "एनवायरनमेंट वेरिएबल हटाएँ?", - "stripPromptDesc": "इस प्रोजेक्ट में {{count}} एनवायरनमेंट वेरिएबल हैं (जिनमें API कुंजियाँ शामिल हो सकती हैं)। वे प्रोजेक्ट फ़ाइल में सादे टेक्स्ट में संग्रहीत हैं और साझा करने पर उजागर हो सकते हैं। क्या उन्हें सहेजी गई फ़ाइल से हटाना है? आपकी Settings उन्हें इस डिवाइस पर वैसे भी रखेंगी।", - "stripButton": "फ़ाइल से हटाएँ", + "stripPromptTitle": "क्रेडेंशियल हटाएँ?", + "stripPromptDesc": "इस प्रोजेक्ट में {{count}} क्रेडेंशियल वाले फ़ील्ड हैं, जैसे API कुंजियाँ, अनुरोध हेडर या प्लगइन सेटिंग। इन्हें सहेजी गई फ़ाइल से हटाएँ? मौजूदा कार्यस्थान इन्हें दोनों स्थितियों में रखेगा।", + "stripButton": "क्रेडेंशियल हटाएँ", "keepButton": "फ़ाइल में रखें" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/id.json b/apps/geolibre-desktop/src/i18n/locales/id.json index aa376c6eb..910af0db2 100644 --- a/apps/geolibre-desktop/src/i18n/locales/id.json +++ b/apps/geolibre-desktop/src/i18n/locales/id.json @@ -1137,7 +1137,8 @@ "sharing": "Membagikan…", "errorFallback": "Tidak dapat membagikan proyek.", "usernameRequired": "Atur nama pengguna pada akun {{shareHost}} Anda sebelum membagikan. Buka pengaturan akun Anda untuk memilihnya, lalu coba lagi.", - "openAccountSettings": "Buka pengaturan akun" + "openAccountSettings": "Buka pengaturan akun", + "credentialsRemoved": "{{count}} bidang kredensial tidak disertakan. Penerima harus memberikan kredensial sendiri atau menggunakan referensi yang diperantarai." }, "gallery": { "title": "Galeri proyek", @@ -1777,9 +1778,9 @@ "removeAria": "Hapus {{name}}", "errorNamePattern": "Nama variabel lingkungan harus dimulai dengan huruf atau garis bawah dan hanya berisi huruf, angka, dan garis bawah.", "errorDuplicate": "Variabel lingkungan \"{{name}}\" duplikat.", - "stripPromptTitle": "Hapus variabel lingkungan?", - "stripPromptDesc": "Proyek ini memiliki {{count}} variabel lingkungan (yang mungkin termasuk kunci API). Nilai ini disimpan dalam teks biasa pada file proyek dan dapat terekspos jika Anda membagikannya. Hapus dari file yang disimpan? Pengaturan Anda tetap menyimpannya di perangkat ini dalam kedua kasus.", - "stripButton": "Hapus dari file", + "stripPromptTitle": "Hapus kredensial?", + "stripPromptDesc": "Proyek ini memiliki {{count}} bidang yang memuat kredensial, seperti kunci API, header permintaan, atau pengaturan plugin. Hapus dari file yang disimpan? Ruang kerja saat ini tetap menyimpannya.", + "stripButton": "Hapus kredensial", "keepButton": "Simpan dalam file" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/it.json b/apps/geolibre-desktop/src/i18n/locales/it.json index abe3750af..e351cf80f 100644 --- a/apps/geolibre-desktop/src/i18n/locales/it.json +++ b/apps/geolibre-desktop/src/i18n/locales/it.json @@ -1172,7 +1172,8 @@ "sharing": "Condivisione in corso…", "errorFallback": "Impossibile condividere il progetto.", "usernameRequired": "Imposta un nome utente sul tuo account {{shareHost}} prima di condividere. Apri le impostazioni account per sceglierne uno, quindi riprova.", - "openAccountSettings": "Apri impostazioni account" + "openAccountSettings": "Apri impostazioni account", + "credentialsRemoved": "{{count}} campo/i di credenziali non sono stati inclusi. I destinatari devono fornire le proprie credenziali o usare un riferimento mediato." }, "gallery": { "title": "Galleria progetti", @@ -1819,9 +1820,9 @@ "removeAria": "Rimuovi {{name}}", "errorNamePattern": "I nomi delle variabili d'ambiente devono iniziare con una lettera o un trattino basso e contenere solo lettere, numeri e trattini bassi.", "errorDuplicate": "La variabile d'ambiente \"{{name}}\" è duplicata.", - "stripPromptTitle": "Rimuovere le variabili d'ambiente?", - "stripPromptDesc": "Questo progetto ha {{count}} variabile/i d'ambiente (che potrebbero includere chiavi API). Sono memorizzate in chiaro nel file di progetto e potrebbero essere esposte se lo condividi. Rimuoverle dal file salvato? Le tue Impostazioni le conservano comunque su questo dispositivo.", - "stripButton": "Rimuovi dal file", + "stripPromptTitle": "Rimuovere le credenziali?", + "stripPromptDesc": "Questo progetto contiene {{count}} campo/i con credenziali, come chiavi API, intestazioni delle richieste o impostazioni dei plugin. Rimuoverli dal file salvato? L’area di lavoro corrente li conserva comunque.", + "stripButton": "Rimuovi credenziali", "keepButton": "Mantieni nel file" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/ja.json b/apps/geolibre-desktop/src/i18n/locales/ja.json index 8b7f17a53..13fd0dba0 100644 --- a/apps/geolibre-desktop/src/i18n/locales/ja.json +++ b/apps/geolibre-desktop/src/i18n/locales/ja.json @@ -1137,7 +1137,8 @@ "sharing": "共有中…", "errorFallback": "プロジェクトを共有できませんでした。", "usernameRequired": "共有する前に {{shareHost}} アカウントでユーザー名を設定してください。アカウント設定を開いて選択し、もう一度お試しください。", - "openAccountSettings": "アカウント設定を開く" + "openAccountSettings": "アカウント設定を開く", + "credentialsRemoved": "{{count}} 個の認証情報フィールドは含まれていません。受信者は自分の認証情報を指定するか、ブローカー参照を使用する必要があります。" }, "gallery": { "title": "プロジェクトギャラリー", @@ -1777,9 +1778,9 @@ "removeAria": "{{name}} を削除", "errorNamePattern": "環境変数名は英字またはアンダースコアで始まり、英字・数字・アンダースコアのみを使用できます。", "errorDuplicate": "環境変数「{{name}}」が重複しています。", - "stripPromptTitle": "環境変数を削除しますか?", - "stripPromptDesc": "このプロジェクトには {{count}} 件の環境変数があります(APIキーが含まれる場合があります)。これらはプロジェクトファイルにプレーンテキストで保存されており、共有すると露出する可能性があります。保存するファイルから削除しますか?いずれの場合も、このデバイスの設定には保持されます。", - "stripButton": "ファイルから削除", + "stripPromptTitle": "認証情報を削除しますか?", + "stripPromptDesc": "このプロジェクトには、API キー、リクエストヘッダー、プラグイン設定など、認証情報を含むフィールドが {{count}} 個あります。保存ファイルから削除しますか? 現在のワークスペースにはどちらの場合も保持されます。", + "stripButton": "認証情報を削除", "keepButton": "ファイルに保持" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/ka.json b/apps/geolibre-desktop/src/i18n/locales/ka.json index f861a03c3..6a03c9f57 100644 --- a/apps/geolibre-desktop/src/i18n/locales/ka.json +++ b/apps/geolibre-desktop/src/i18n/locales/ka.json @@ -1172,7 +1172,8 @@ "sharing": "მიმდინარეობს გაზიარება…", "errorFallback": "პროექტის გაზიარება ვერ მოხერხდა.", "usernameRequired": "გაზიარებამდე დააყენეთ მომხმარებლის სახელი თქვენს {{shareHost}} ანგარიშზე. გახსენით ანგარიშის პარამეტრები ასარჩევად, შემდეგ სცადეთ ხელახლა.", - "openAccountSettings": "ანგარიშის პარამეტრების გახსნა" + "openAccountSettings": "ანგარიშის პარამეტრების გახსნა", + "credentialsRemoved": "ავტორიზაციის მონაცემების შემცველი {{count}} ველი არ ჩაერთო. მიმღებებმა საკუთარი მონაცემები უნდა მიუთითონ ან შუამავლური მითითება გამოიყენონ." }, "gallery": { "title": "პროექტების გალერეა", @@ -1819,9 +1820,9 @@ "removeAria": "{{name}}-ის მოშორება", "errorNamePattern": "გარემოს ცვლადების სახელები უნდა იწყებოდეს ასოთი ან ქვედა ხაზით და შეიცავდეს მხოლოდ ასოებს, ციფრებსა და ქვედა ხაზებს.", "errorDuplicate": "გარემოს ცვლადი „{{name}}“ დუბლირებულია.", - "stripPromptTitle": "წავშალოთ გარემოს ცვლადები?", - "stripPromptDesc": "ამ პროექტს აქვს {{count}} გარემოს ცვლადი (რაც შეიძლება მოიცავდეს API-გასაღებებს). ისინი ინახება ღია ტექსტად პროექტის ფაილში და შეიძლება გამჟღავნდეს გაზიარებისას. მოვაშოროთ ისინი შენახული ფაილიდან? თქვენი პარამეტრები ორივე შემთხვევაში ინახავს მათ ამ მოწყობილობაზე.", - "stripButton": "ფაილიდან წაშლა", + "stripPromptTitle": "წავშალოთ ავტორიზაციის მონაცემები?", + "stripPromptDesc": "ეს პროექტი შეიცავს ავტორიზაციის მონაცემების მქონე {{count}} ველს, როგორიცაა API გასაღებები, მოთხოვნის სათაურები ან დანამატის პარამეტრები. წავშალოთ ისინი შენახული ფაილიდან? მიმდინარე სამუშაო სივრცე მათ ნებისმიერ შემთხვევაში შეინარჩუნებს.", + "stripButton": "ავტორიზაციის მონაცემების წაშლა", "keepButton": "ფაილში დატოვება" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/ko.json b/apps/geolibre-desktop/src/i18n/locales/ko.json index e0f1885bc..3e088f0ba 100644 --- a/apps/geolibre-desktop/src/i18n/locales/ko.json +++ b/apps/geolibre-desktop/src/i18n/locales/ko.json @@ -1137,7 +1137,8 @@ "sharing": "공유 중…", "errorFallback": "프로젝트를 공유할 수 없습니다.", "usernameRequired": "공유하기 전에 {{shareHost}} 계정에 사용자 이름을 설정하세요. 계정 설정을 열어 사용자 이름을 선택한 다음 다시 시도하세요.", - "openAccountSettings": "계정 설정 열기" + "openAccountSettings": "계정 설정 열기", + "credentialsRemoved": "자격 증명 필드 {{count}}개가 포함되지 않았습니다. 수신자는 자신의 자격 증명을 제공하거나 브로커 참조를 사용해야 합니다." }, "gallery": { "title": "프로젝트 갤러리", @@ -1777,9 +1778,9 @@ "removeAria": "{{name}} 제거", "errorNamePattern": "환경 변수 이름은 문자나 밑줄로 시작해야 하며 문자, 숫자, 밑줄만 포함할 수 있습니다.", "errorDuplicate": "환경 변수 \"{{name}}\"이(가) 중복되었습니다.", - "stripPromptTitle": "환경 변수를 제거하시겠습니까?", - "stripPromptDesc": "이 프로젝트에는 {{count}}개의 환경 변수가 있습니다(API 키가 포함될 수 있음). 이 변수들은 프로젝트 파일에 평문으로 저장되며 공유 시 노출될 수 있습니다. 저장된 파일에서 제거하시겠습니까? 어느 쪽을 선택하든 이 기기의 설정에는 그대로 유지됩니다.", - "stripButton": "파일에서 제거", + "stripPromptTitle": "자격 증명을 제거하시겠습니까?", + "stripPromptDesc": "이 프로젝트에는 API 키, 요청 헤더 또는 플러그인 설정과 같은 자격 증명 포함 필드가 {{count}}개 있습니다. 저장 파일에서 제거하시겠습니까? 현재 작업 공간에는 어느 경우든 유지됩니다.", + "stripButton": "자격 증명 제거", "keepButton": "파일에 유지" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/nl.json b/apps/geolibre-desktop/src/i18n/locales/nl.json index 2e8bdf9cb..ff7080f3c 100644 --- a/apps/geolibre-desktop/src/i18n/locales/nl.json +++ b/apps/geolibre-desktop/src/i18n/locales/nl.json @@ -1172,7 +1172,8 @@ "sharing": "Bezig met delen…", "errorFallback": "Kan het project niet delen.", "usernameRequired": "Stel een gebruikersnaam in voor uw {{shareHost}}-account voordat u deelt. Open uw accountinstellingen om er een te kiezen en probeer het opnieuw.", - "openAccountSettings": "Accountinstellingen openen" + "openAccountSettings": "Accountinstellingen openen", + "credentialsRemoved": "{{count}} veld(en) met aanmeldgegevens zijn niet opgenomen. Ontvangers moeten hun eigen aanmeldgegevens opgeven of een bemiddelde verwijzing gebruiken." }, "gallery": { "title": "Projectgalerij", @@ -1819,9 +1820,9 @@ "removeAria": "{{name}} verwijderen", "errorNamePattern": "Namen van omgevingsvariabelen moeten beginnen met een letter of underscore en mogen alleen letters, cijfers en underscores bevatten.", "errorDuplicate": "Omgevingsvariabele \"{{name}}\" komt dubbel voor.", - "stripPromptTitle": "Omgevingsvariabelen verwijderen?", - "stripPromptDesc": "Dit project heeft {{count}} omgevingsvariabele(n) (mogelijk inclusief API-sleutels). Deze worden als platte tekst opgeslagen in het projectbestand en kunnen zichtbaar worden wanneer u het deelt. Wilt u ze uit het opgeslagen bestand verwijderen? Uw Instellingen bewaren ze in beide gevallen op dit apparaat.", - "stripButton": "Uit bestand verwijderen", + "stripPromptTitle": "Aanmeldgegevens verwijderen?", + "stripPromptDesc": "Dit project bevat {{count}} veld(en) met aanmeldgegevens, zoals API-sleutels, aanvraagheaders of plugininstellingen. Verwijderen uit het opgeslagen bestand? De huidige werkruimte behoudt ze in beide gevallen.", + "stripButton": "Aanmeldgegevens verwijderen", "keepButton": "In bestand behouden" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/pt.json b/apps/geolibre-desktop/src/i18n/locales/pt.json index bfc531254..e6812924a 100644 --- a/apps/geolibre-desktop/src/i18n/locales/pt.json +++ b/apps/geolibre-desktop/src/i18n/locales/pt.json @@ -1172,7 +1172,8 @@ "sharing": "Compartilhando…", "errorFallback": "Não foi possível compartilhar o projeto.", "usernameRequired": "Defina um nome de usuário na sua conta {{shareHost}} antes de compartilhar. Abra as configurações da sua conta para escolher um e tente novamente.", - "openAccountSettings": "Abrir configurações da conta" + "openAccountSettings": "Abrir configurações da conta", + "credentialsRemoved": "{{count}} campo(s) de credenciais não foram incluídos. Os destinatários devem fornecer as próprias credenciais ou usar uma referência intermediada." }, "gallery": { "title": "Galeria de projetos", @@ -1819,9 +1820,9 @@ "removeAria": "Remover {{name}}", "errorNamePattern": "Os nomes das variáveis de ambiente devem começar com uma letra ou sublinhado e conter apenas letras, números e sublinhados.", "errorDuplicate": "A variável de ambiente \"{{name}}\" está duplicada.", - "stripPromptTitle": "Remover variáveis de ambiente?", - "stripPromptDesc": "Este projeto tem {{count}} variável(is) de ambiente (que podem incluir chaves de API). Elas são armazenadas em texto simples no arquivo do projeto e podem ser expostas se você o compartilhar. Removê-las do arquivo salvo? Suas Configurações as mantêm neste dispositivo de qualquer forma.", - "stripButton": "Remover do arquivo", + "stripPromptTitle": "Remover credenciais?", + "stripPromptDesc": "Este projeto tem {{count}} campo(s) com credenciais, como chaves de API, cabeçalhos de solicitação ou configurações de plugins. Removê-los do arquivo salvo? O espaço de trabalho atual os mantém de qualquer forma.", + "stripButton": "Remover credenciais", "keepButton": "Manter no arquivo" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/ru.json b/apps/geolibre-desktop/src/i18n/locales/ru.json index 4954e24ee..2680e5414 100644 --- a/apps/geolibre-desktop/src/i18n/locales/ru.json +++ b/apps/geolibre-desktop/src/i18n/locales/ru.json @@ -1242,7 +1242,8 @@ "sharing": "Публикация…", "errorFallback": "Не удалось поделиться проектом.", "usernameRequired": "Задайте имя пользователя в учётной записи {{shareHost}} перед публикацией. Откройте настройки учётной записи, чтобы выбрать имя, затем повторите попытку.", - "openAccountSettings": "Открыть настройки учётной записи" + "openAccountSettings": "Открыть настройки учётной записи", + "credentialsRemoved": "{{count}} полей с учётными данными не включены. Получатели должны предоставить собственные учётные данные или использовать брокерскую ссылку." }, "gallery": { "title": "Галерея проектов", @@ -1903,9 +1904,9 @@ "removeAria": "Удалить {{name}}", "errorNamePattern": "Имена переменных среды должны начинаться с буквы или символа подчёркивания и содержать только буквы, цифры и символы подчёркивания.", "errorDuplicate": "Переменная среды «{{name}}» продублирована.", - "stripPromptTitle": "Удалить переменные среды?", - "stripPromptDesc": "В этом проекте {{count}} переменных среды (которые могут включать API-ключи). Они хранятся в открытом тексте в файле проекта и могут быть раскрыты при его передаче. Удалить их из сохраняемого файла? Настройки в любом случае сохранят их на этом устройстве.", - "stripButton": "Удалить из файла", + "stripPromptTitle": "Удалить учётные данные?", + "stripPromptDesc": "Проект содержит {{count}} полей с учётными данными, например ключи API, заголовки запросов или настройки плагинов. Удалить их из сохраняемого файла? В текущем рабочем пространстве они сохранятся в любом случае.", + "stripButton": "Удалить учётные данные", "keepButton": "Оставить в файле" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/th.json b/apps/geolibre-desktop/src/i18n/locales/th.json index cb0ce6a72..2947f7ec8 100644 --- a/apps/geolibre-desktop/src/i18n/locales/th.json +++ b/apps/geolibre-desktop/src/i18n/locales/th.json @@ -1137,7 +1137,8 @@ "sharing": "กำลังแชร์…", "errorFallback": "ไม่สามารถแชร์โปรเจกต์ได้", "usernameRequired": "กรุณาตั้งชื่อผู้ใช้ในบัญชี {{shareHost}} ของคุณก่อนแชร์ เปิดการตั้งค่าบัญชีเพื่อเลือกชื่อผู้ใช้แล้วลองใหม่", - "openAccountSettings": "เปิดการตั้งค่าบัญชี" + "openAccountSettings": "เปิดการตั้งค่าบัญชี", + "credentialsRemoved": "ไม่ได้รวมช่องข้อมูลประจำตัว {{count}} ช่อง ผู้รับต้องระบุข้อมูลประจำตัวของตนเองหรือใช้การอ้างอิงผ่านนายหน้า" }, "gallery": { "title": "แกลเลอรีโปรเจกต์", @@ -1777,9 +1778,9 @@ "removeAria": "นำ {{name}} ออก", "errorNamePattern": "ชื่อตัวแปรสภาพแวดล้อมต้องขึ้นต้นด้วยตัวอักษรหรือขีดล่าง และประกอบด้วยตัวอักษร ตัวเลข และขีดล่างเท่านั้น", "errorDuplicate": "ตัวแปรสภาพแวดล้อม \"{{name}}\" ซ้ำกัน", - "stripPromptTitle": "ตัดตัวแปรสภาพแวดล้อมออกหรือไม่?", - "stripPromptDesc": "โปรเจกต์นี้มีตัวแปรสภาพแวดล้อม {{count}} รายการ (ซึ่งอาจรวมถึงคีย์ API) ตัวแปรเหล่านี้ถูกเก็บเป็นข้อความธรรมดาในไฟล์โปรเจกต์ และอาจถูกเปิดเผยหากคุณแชร์ไฟล์ ต้องการนำออกจากไฟล์ที่บันทึกหรือไม่? ไม่ว่าเลือกแบบใด การตั้งค่าของคุณจะยังเก็บตัวแปรเหล่านี้ไว้บนอุปกรณ์นี้", - "stripButton": "ตัดออกจากไฟล์", + "stripPromptTitle": "ตัดข้อมูลประจำตัวออกหรือไม่?", + "stripPromptDesc": "โปรเจกต์นี้มีช่องที่เก็บข้อมูลประจำตัว {{count}} ช่อง เช่น คีย์ API ส่วนหัวคำขอ หรือการตั้งค่าปลั๊กอิน ต้องการนำออกจากไฟล์ที่บันทึกหรือไม่? พื้นที่ทำงานปัจจุบันจะยังคงเก็บข้อมูลเหล่านี้ไว้", + "stripButton": "ตัดข้อมูลประจำตัวออก", "keepButton": "เก็บไว้ในไฟล์" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/tr.json b/apps/geolibre-desktop/src/i18n/locales/tr.json index 3fbccbb8b..3ba0ed3cc 100644 --- a/apps/geolibre-desktop/src/i18n/locales/tr.json +++ b/apps/geolibre-desktop/src/i18n/locales/tr.json @@ -1172,7 +1172,8 @@ "sharing": "Paylaşılıyor…", "errorFallback": "Proje paylaşılamadı.", "usernameRequired": "Paylaşmadan önce {{shareHost}} hesabınızda bir kullanıcı adı belirleyin. Bir tane seçmek için hesap ayarlarınızı açın, ardından tekrar deneyin.", - "openAccountSettings": "Hesap ayarlarını aç" + "openAccountSettings": "Hesap ayarlarını aç", + "credentialsRemoved": "{{count}} kimlik bilgisi alanı dahil edilmedi. Alıcılar kendi kimlik bilgilerini sağlamalı veya aracılı bir referans kullanmalıdır." }, "gallery": { "title": "Proje galerisi", @@ -1819,9 +1820,9 @@ "removeAria": "{{name}} öğesini kaldır", "errorNamePattern": "Ortam değişkeni adları bir harf veya alt çizgi ile başlamalı; yalnızca harf, rakam ve alt çizgi içermelidir.", "errorDuplicate": "\"{{name}}\" ortam değişkeni yinelendi.", - "stripPromptTitle": "Ortam değişkenleri kaldırılsın mı?", - "stripPromptDesc": "Bu projede {{count}} ortam değişkeni var (API anahtarları içerebilir). Bunlar proje dosyasında düz metin olarak saklanır ve paylaşırsanız açığa çıkabilir. Kaydedilen dosyadan kaldırılsınlar mı? Ayarlarınız bunları her durumda bu cihazda tutar.", - "stripButton": "Dosyadan kaldır", + "stripPromptTitle": "Kimlik bilgileri kaldırılsın mı?", + "stripPromptDesc": "Bu projede API anahtarları, istek üstbilgileri veya eklenti ayarları gibi kimlik bilgisi taşıyan {{count}} alan var. Kaydedilen dosyadan kaldırılsın mı? Geçerli çalışma alanı her iki durumda da bunları korur.", + "stripButton": "Kimlik bilgilerini kaldır", "keepButton": "Dosyada tut" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/i18n/locales/zh.json b/apps/geolibre-desktop/src/i18n/locales/zh.json index 6c6c7d688..f23660a6f 100644 --- a/apps/geolibre-desktop/src/i18n/locales/zh.json +++ b/apps/geolibre-desktop/src/i18n/locales/zh.json @@ -1137,7 +1137,8 @@ "sharing": "正在共享…", "errorFallback": "无法共享该项目。", "usernameRequired": "共享前请在您的 {{shareHost}} 账户上设置用户名。请打开账户设置进行选择,然后重试。", - "openAccountSettings": "打开账户设置" + "openAccountSettings": "打开账户设置", + "credentialsRemoved": "未包含 {{count}} 个凭据字段。接收者需要提供自己的凭据或使用代理引用。" }, "gallery": { "title": "项目图库", @@ -1777,9 +1778,9 @@ "removeAria": "移除 {{name}}", "errorNamePattern": "环境变量名称必须以字母或下划线开头,且只能包含字母、数字和下划线。", "errorDuplicate": "环境变量“{{name}}”重复。", - "stripPromptTitle": "是否从文件中移除环境变量?", - "stripPromptDesc": "此项目包含 {{count}} 个环境变量(其中可能包含 API 密钥)。它们以纯文本形式存储在项目文件中,共享时可能会被暴露。是否从已保存的文件中移除它们?无论如何,您的“设置”都会在本设备上保留它们。", - "stripButton": "从文件中移除", + "stripPromptTitle": "是否移除凭据?", + "stripPromptDesc": "此项目包含 {{count}} 个可能存有凭据的字段,例如 API 密钥、请求标头或插件设置。是否从保存的文件中移除?无论如何,当前工作区都会保留这些凭据。", + "stripButton": "移除凭据", "keepButton": "保留在文件中" }, "geocoding": { diff --git a/apps/geolibre-desktop/src/lib/build-project-snapshot.ts b/apps/geolibre-desktop/src/lib/build-project-snapshot.ts index 378144724..f0a257e01 100644 --- a/apps/geolibre-desktop/src/lib/build-project-snapshot.ts +++ b/apps/geolibre-desktop/src/lib/build-project-snapshot.ts @@ -1,4 +1,9 @@ -import { projectFromStore, useAppStore, type GeoLibreProject } from "@geolibre/core"; +import { + projectFromStore, + redactCredentials, + useAppStore, + type GeoLibreProject, +} from "@geolibre/core"; import type { RefObject } from "react"; import type { MapController } from "@geolibre/map"; import { getPluginManager } from "../hooks/usePlugins"; @@ -48,3 +53,14 @@ export function buildProjectSnapshot( metadata: state.metadata, }); } + +/** + * Build the public wire form used by collaboration and embed hosts. + * Keeping this boundary shared prevents either transport from accidentally + * reverting to the credential-bearing local snapshot. + */ +export function buildProjectEgressSnapshot( + mapControllerRef: RefObject, +): GeoLibreProject { + return redactCredentials(buildProjectSnapshot(mapControllerRef)); +} diff --git a/apps/geolibre-desktop/src/lib/html-export.ts b/apps/geolibre-desktop/src/lib/html-export.ts index 7802bacca..5e8669965 100644 --- a/apps/geolibre-desktop/src/lib/html-export.ts +++ b/apps/geolibre-desktop/src/lib/html-export.ts @@ -1,7 +1,7 @@ // Standalone "Export as interactive HTML" builder; the in-app counterpart of the // Python widget's `Map.to_html()`. See `docs/python.md` and `embedHost.ts`. -import type { GeoLibreProject } from "@geolibre/core"; +import { redactCredentials, type GeoLibreProject } from "@geolibre/core"; // Hosted viewer used as the default embed target (matches Python's default). export const DEFAULT_VIEWER_BASE_URL = "https://web.geolibre.app/"; @@ -100,7 +100,7 @@ export function buildProjectHtml(options: BuildProjectHtmlOptions): string { } const iframeSrc = withViewerFlags(appUrl); // Escape "<" so a property value can't break out of the JSON