diff --git a/.dockerignore b/.dockerignore index a5acced7..6528bce7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ node_modules .git -.gitignore dist -data +/data +.env* +.nx diff --git a/.env.example b/.env.example index 084b871b..6d537708 100644 --- a/.env.example +++ b/.env.example @@ -43,4 +43,13 @@ POSTMARK_TOKEN= # for custom drawio server DRAWIO_URL= -DISABLE_TELEMETRY=false \ No newline at end of file +DISABLE_TELEMETRY=false + +# Enable debug logging in production (default: false) +DEBUG_MODE=false + +# Log database queries +DEBUG_DB=false + +# Log http requests +LOG_HTTP=false diff --git a/Dockerfile b/Dockerfile index 02a35ffc..d665e254 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,22 @@ -FROM node:22-alpine AS base +FROM node:22-slim AS base LABEL org.opencontainers.image.source="https://github.com/docmost/docmost" +RUN npm install -g pnpm@10.4.0 + FROM base AS builder WORKDIR /app COPY . . -RUN npm install -g pnpm@10.4.0 RUN pnpm install --frozen-lockfile RUN pnpm build FROM base AS installer -RUN apk add --no-cache curl bash +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl bash \ + && rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -29,12 +32,11 @@ COPY --from=builder /app/packages/editor-ext/package.json /app/packages/editor-e # Copy root package files COPY --from=builder /app/package.json /app/package.json COPY --from=builder /app/pnpm*.yaml /app/ +COPY --from=builder /app/.npmrc /app/.npmrc # Copy patches COPY --from=builder /app/patches /app/patches -RUN npm install -g pnpm@10.4.0 - RUN chown -R node:node /app USER node diff --git a/apps/client/index.html b/apps/client/index.html index c96058cb..28679e40 100644 --- a/apps/client/index.html +++ b/apps/client/index.html @@ -2,10 +2,18 @@ - - - + + + Docmost + + + + + + + + diff --git a/apps/client/package.json b/apps/client/package.json index 49448701..751bfa43 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -1,7 +1,7 @@ { "name": "client", "private": true, - "version": "0.20.4", + "version": "0.25.0-beta.1", "scripts": { "dev": "vite", "build": "tsc && vite build", @@ -10,50 +10,51 @@ "format": "prettier --write \"src/**/*.tsx\" \"src/**/*.ts\"" }, "dependencies": { - "@casl/ability": "^6.7.2", "@casl/react": "^4.0.0", "@docmost/editor-ext": "workspace:*", "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1", - "@excalidraw/excalidraw": "0.18.0-864353b", - "@mantine/core": "^7.17.0", - "@mantine/form": "^7.17.0", - "@mantine/hooks": "^7.17.0", - "@mantine/modals": "^7.17.0", - "@mantine/notifications": "^7.17.0", - "@mantine/spotlight": "^7.17.0", - "@tabler/icons-react": "^3.34.0", - "@tanstack/react-query": "^5.80.6", - "@tiptap/extension-character-count": "^2.14.0", - "axios": "^1.9.0", + "@excalidraw/excalidraw": "0.18.0-c158187", + "@mantine/core": "^8.3.12", + "@mantine/dates": "^8.3.12", + "@mantine/form": "^8.3.12", + "@mantine/hooks": "^8.3.12", + "@mantine/modals": "^8.3.12", + "@mantine/notifications": "^8.3.12", + "@mantine/spotlight": "^8.3.12", + "@tabler/icons-react": "^3.36.1", + "@tanstack/react-query": "^5.90.17", + "alfaaz": "^1.1.0", + "axios": "^1.13.2", "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "file-saver": "^2.0.5", "highlightjs-sap-abap": "^0.3.0", - "i18next": "^23.14.0", - "i18next-http-backend": "^2.6.1", - "jotai": "^2.12.5", + "i18next": "^23.16.8", + "i18next-http-backend": "^2.7.3", + "jotai": "^2.16.2", "jotai-optics": "^0.4.0", "js-cookie": "^3.0.5", "jwt-decode": "^4.0.0", - "katex": "0.16.22", + "katex": "0.16.27", "lowlight": "^3.3.0", - "mermaid": "^11.6.0", + "mantine-form-zod-resolver": "^1.3.0", + "mermaid": "^11.12.2", "mitt": "^3.0.1", + "posthog-js": "^1.255.1", "react": "^18.3.1", "react-arborist": "3.4.0", - "react-clear-modal": "^2.0.15", + "react-clear-modal": "^2.0.17", "react-dom": "^18.3.1", - "react-drawio": "^1.0.1", + "react-drawio": "^1.0.7", "react-error-boundary": "^4.1.2", "react-helmet-async": "^2.0.5", "react-i18next": "^15.0.1", - "react-router-dom": "^7.0.1", - "semver": "^7.7.2", - "socket.io-client": "^4.8.1", - "tippy.js": "^6.3.7", + "react-router-dom": "^7.12.0", + "semver": "^7.7.3", + "socket.io-client": "^4.8.3", "tiptap-extension-global-drag-handle": "^0.1.18", - "zod": "^3.25.56" + "zod": "^3.25.76" }, "devDependencies": { "@eslint/js": "^9.16.0", @@ -61,10 +62,10 @@ "@types/file-saver": "^2.0.7", "@types/js-cookie": "^3.0.6", "@types/katex": "^0.16.7", - "@types/node": "22.10.0", + "@types/node": "22.19.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-react": "^5.1.1", "eslint": "^9.15.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.1.0", @@ -77,6 +78,6 @@ "prettier": "^3.4.1", "typescript": "^5.7.2", "typescript-eslint": "^8.17.0", - "vite": "^6.3.5" + "vite": "^7.2.4" } } diff --git a/apps/client/public/favicon-16x16.png b/apps/client/public/favicon-16x16.png deleted file mode 100644 index 6298fe8a..00000000 Binary files a/apps/client/public/favicon-16x16.png and /dev/null differ diff --git a/apps/client/public/favicon-32x32.png b/apps/client/public/favicon-32x32.png deleted file mode 100644 index 40d6a30e..00000000 Binary files a/apps/client/public/favicon-32x32.png and /dev/null differ diff --git a/apps/client/public/icons/app-icon-192x192.png b/apps/client/public/icons/app-icon-192x192.png new file mode 100644 index 00000000..46bce9e5 Binary files /dev/null and b/apps/client/public/icons/app-icon-192x192.png differ diff --git a/apps/client/public/icons/app-icon-512x512.png b/apps/client/public/icons/app-icon-512x512.png new file mode 100644 index 00000000..65b91ed0 Binary files /dev/null and b/apps/client/public/icons/app-icon-512x512.png differ diff --git a/apps/client/public/icons/favicon-16x16.png b/apps/client/public/icons/favicon-16x16.png new file mode 100644 index 00000000..c8d2d56f Binary files /dev/null and b/apps/client/public/icons/favicon-16x16.png differ diff --git a/apps/client/public/icons/favicon-32x32.png b/apps/client/public/icons/favicon-32x32.png new file mode 100644 index 00000000..3ccc0fb0 Binary files /dev/null and b/apps/client/public/icons/favicon-32x32.png differ diff --git a/apps/client/public/locales/de-DE/translation.json b/apps/client/public/locales/de-DE/translation.json index df563db7..93c6f265 100644 --- a/apps/client/public/locales/de-DE/translation.json +++ b/apps/client/public/locales/de-DE/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Wählen Sie Ihre bevorzugte Benutzersprache.", "Choose your preferred page width.": "Wählen Sie Ihre bevorzugte Seitenbreite.", "Confirm": "Bestätigen", + "Copy as Markdown": "Als Markdown kopieren", "Copy link": "Link kopieren", "Create": "Erstellen", "Create group": "Gruppe erstellen", @@ -42,7 +43,7 @@ "Delete group": "Gruppe löschen", "Are you sure you want to delete this page? This will delete its children and page history. This action is irreversible.": "Sind Sie sicher, dass Sie diese Seite löschen möchten? Dadurch werden ihre Unterseiten und die Seitengeschichte gelöscht. Diese Aktion ist unwiderruflich.", "Description": "Beschreibung", - "Details": "Einzelheiten", + "Details": "Details", "e.g ACME": "z.B. ACME", "e.g ACME Inc": "z.B. ACME Inc.", "e.g Developers": "z.B. Entwickler", @@ -53,6 +54,7 @@ "e.g Space for product team": "z.B. Bereich für das Produktteam", "e.g Space for sales team to collaborate": "z.B. Bereich für das Vertriebsteam zur Zusammenarbeit", "Edit": "Bearbeiten", + "Read": "Lesen", "Edit group": "Gruppe bearbeiten", "Email": "E-Mail", "Enter a strong password": "Geben Sie ein starkes Passwort ein", @@ -104,7 +106,7 @@ "Member": "Mitglied", "members": "Mitglieder", "Members": "Mitglieder", - "My preferences": "Meine Vorlieben", + "My preferences": "Meine Voreinstellungen", "My Profile": "Mein Profil", "My profile": "Mein Profil", "Name": "Name", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Kommentar erfolgreich gelöscht", "Failed to delete comment": "Löschen des Kommentars fehlgeschlagen", "Comment resolved successfully": "Kommentar erfolgreich gelöst", + "Comment re-opened successfully": "Kommentar erfolgreich wieder geöffnet", + "Comment unresolved successfully": "Kommentar erfolgreich ungelöst", "Failed to resolve comment": "Lösen des Kommentars fehlgeschlagen", + "Resolve comment": "Kommentar lösen", + "Unresolve comment": "Kommentar nicht lösen", + "Resolve Comment Thread": "Kommentarthread lösen", + "Unresolve Comment Thread": "Kommentarthread nicht lösen", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Sind Sie sicher, dass Sie diesen Kommentarthread lösen möchten? Dies wird als abgeschlossen markiert.", + "Are you sure you want to unresolve this comment thread?": "Sind Sie sicher, dass Sie diesen Kommentarthread nicht lösen möchten?", + "Resolved": "Gelöst", + "No active comments.": "Keine aktiven Kommentare.", + "No resolved comments.": "Keine gelösten Kommentare.", "Revoke invitation": "Einladung widerrufen", "Revoke": "Widerrufen", "Don't": "Nicht", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Jeder mit diesem Link kann dem Arbeitsbereich beitreten.", "Invite link": "Einladungslink", "Copy": "Kopieren", + "Copy to space": "In Raum kopieren", "Copied": "Kopiert", + "Duplicate": "Duplizieren", "Select a user": "Benutzer auswählen", "Select a group": "Gruppe auswählen", "Export all pages and attachments in this space.": "Alle Seiten und Anhänge in diesem Bereich exportieren.", @@ -239,6 +254,7 @@ "Export failed:": "Export fehlgeschlagen:", "export error": "Exportfehler", "Export page": "Seite exportieren", + "Export successful": "Export erfolgreich", "Export space": "Bereich exportieren", "Export {{type}}": "Exportiere {{type}}", "File exceeds the {{limit}} attachment limit": "Datei überschreitet das Anhängelimit von {{limit}}", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Laden Sie ein beliebiges Bild von Ihrem Gerät hoch.", "Upload any video from your device.": "Laden Sie ein beliebiges Video von Ihrem Gerät hoch.", "Upload any file from your device.": "Laden Sie eine beliebige Datei von Ihrem Gerät hoch.", + "Uploading {{name}}": "Lade {{name}} hoch", + "Uploading file": "Datei wird hochgeladen", "Table": "Tabelle", "Insert a table.": "Tabelle einfügen.", "Insert collapsible block.": "Einklappbaren Block einfügen.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Zeichenzahl: {{characterCount}}", "New update": "Neues Update", "{{latestVersion}} is available": "{{latestVersion}} ist verfügbar", + "Default page edit mode": "Standard-Seitenbearbeitungsmodus", + "Choose your preferred page edit mode. Avoid accidental edits.": "Wählen Sie Ihren bevorzugten Seitenbearbeitungsmodus. Vermeiden Sie versehentliche Bearbeitungen.", + "Reading": "Lesen", "Delete member": "Mitglied löschen", "Member deleted successfully": "Mitglied erfolgreich gelöscht", "Are you sure you want to delete this workspace member? This action is irreversible.": "Sind Sie sicher, dass Sie dieses Arbeitsbereichsmitglied löschen möchten? Diese Aktion ist unwiderruflich.", @@ -386,5 +407,171 @@ "Failed to share page": "Fehler beim Teilen der Seite", "Copy page": "Seite kopieren", "Copy page to a different space.": "Seite in einen anderen Bereich kopieren.", - "Page copied successfully": "Seite erfolgreich kopiert" + "Page copied successfully": "Seite erfolgreich kopiert", + "Page duplicated successfully": "Seite erfolgreich dupliziert", + "Find": "Finden", + "Not found": "Nicht gefunden", + "Previous Match (Shift+Enter)": "Vorheriger Treffer (Shift+Enter)", + "Next match (Enter)": "Nächster Treffer (Enter)", + "Match case (Alt+C)": "Groß-/Kleinschreibung beachten (Alt+C)", + "Replace": "Ersetzen", + "Close (Escape)": "Schließen (Escape)", + "Replace (Enter)": "Ersetzen (Enter)", + "Replace all (Ctrl+Alt+Enter)": "Alle ersetzen (Ctrl+Alt+Enter)", + "Replace all": "Alle ersetzen", + "View all spaces": "Alle Räume anzeigen", + "Error": "Fehler", + "Failed to disable MFA": "Deaktivierung der MFA fehlgeschlagen", + "Disable two-factor authentication": "Zwei-Faktor-Authentifizierung deaktivieren", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Die Deaktivierung der Zwei-Faktor-Authentifizierung macht Ihr Konto weniger sicher. Sie benötigen nur Ihr Passwort, um sich anzumelden.", + "Please enter your password to disable two-factor authentication:": "Bitte geben Sie Ihr Passwort ein, um die Zwei-Faktor-Authentifizierung zu deaktivieren:", + "Two-factor authentication has been enabled": "Zwei-Faktor-Authentifizierung wurde aktiviert", + "Two-factor authentication has been disabled": "Zwei-Faktor-Authentifizierung wurde deaktiviert", + "2-step verification": "2-Schritt-Verifizierung", + "Protect your account with an additional verification layer when signing in.": "Schützen Sie Ihr Konto mit einer zusätzlichen Verifizierungsschicht beim Anmelden.", + "Two-factor authentication is active on your account.": "Die Zwei-Faktor-Authentifizierung ist auf Ihrem Konto aktiv.", + "Add 2FA method": "2FA-Methode hinzufügen", + "Backup codes": "Sicherungscodes", + "Disable": "Deaktivieren", + "Invalid verification code": "Ungültiger Bestätigungscode", + "New backup codes have been generated": "Neue Sicherungscodes wurden generiert", + "Failed to regenerate backup codes": "Fehler beim Generieren neuer Sicherungscodes", + "About backup codes": "Über Sicherungscodes", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Sicherungscodes können verwendet werden, um auf Ihr Konto zuzugreifen, wenn Sie den Zugang zu Ihrer Authenticator-App verlieren. Jeder Code kann nur einmal verwendet werden.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Sie können jederzeit neue Sicherungscodes generieren. Dies wird alle vorhandenen Codes ungültig machen.", + "Confirm password": "Passwort bestätigen", + "Generate new backup codes": "Neue Sicherungscodes generieren", + "Save your new backup codes": "Speichern Sie Ihre neuen Sicherungscodes", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Speichern Sie diese Codes an einem sicheren Ort. Ihre alten Sicherungscodes sind nicht mehr gültig.", + "Your new backup codes": "Ihre neuen Sicherungscodes", + "I've saved my backup codes": "Ich habe meine Sicherungscodes gespeichert", + "Failed to setup MFA": "Fehler beim Einrichten der MFA", + "Setup & Verify": "Einrichten & Überprüfen", + "Add to authenticator": "Zum Authenticator hinzufügen", + "1. Scan this QR code with your authenticator app": "1. Scannen Sie diesen QR-Code mit Ihrer Authenticator-App", + "Can't scan the code?": "Code kann nicht gescannt werden?", + "Enter this code manually in your authenticator app:": "Geben Sie diesen Code manuell in Ihrer Authenticator-App ein:", + "2. Enter the 6-digit code from your authenticator": "2. Geben Sie den 6-stelligen Code aus Ihrem Authenticator ein", + "Verify and enable": "Überprüfen und aktivieren", + "Failed to generate QR code. Please try again.": "Fehler beim Generieren des QR-Codes. Bitte versuchen Sie es erneut.", + "Backup": "Sicherung", + "Save codes": "Codes speichern", + "Save your backup codes": "Speichern Sie Ihre Sicherungscodes", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Diese Codes können verwendet werden, um auf Ihr Konto zuzugreifen, wenn Sie den Zugang zu Ihrer Authenticator-App verlieren. Jeder Code kann nur einmal verwendet werden.", + "Print": "Drucken", + "Two-factor authentication has been set up. Please log in again.": "Zwei-Faktor-Authentifizierung wurde eingerichtet. Bitte melden Sie sich erneut an.", + "Two-Factor authentication required": "Zwei-Faktor-Authentifizierung erforderlich", + "Your workspace requires two-factor authentication for all users": "Ihr Arbeitsbereich erfordert die Zwei-Faktor-Authentifizierung für alle Benutzer", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Um weiterhin auf Ihren Arbeitsbereich zuzugreifen, müssen Sie die Zwei-Faktor-Authentifizierung einrichten. Dies fügt Ihrem Konto eine zusätzliche Sicherheitsebene hinzu.", + "Set up two-factor authentication": "Zwei-Faktor-Authentifizierung einrichten", + "Cancel and logout": "Abbrechen und abmelden", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Ihr Arbeitsbereich erfordert eine Zwei-Faktor-Authentifizierung. Bitte richten Sie diese ein, um fortzufahren.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Dadurch wird Ihrem Konto eine zusätzliche Sicherheitsebene hinzugefügt, indem ein Bestätigungscode von Ihrer Authenticator-App verlangt wird.", + "Password is required": "Passwort erforderlich", + "Password must be at least 8 characters": "Passwort muss mindestens 8 Zeichen lang sein", + "Please enter a 6-digit code": "Bitte geben Sie einen 6-stelligen Code ein", + "Code must be exactly 6 digits": "Code muss genau 6-stellig sein", + "Enter the 6-digit code found in your authenticator app": "Geben Sie den 6-stelligen Code ein, der in Ihrer Authenticator-App zu finden ist", + "Need help authenticating?": "Brauchen Sie Hilfe bei der Authentifizierung?", + "MFA QR Code": "MFA QR-Code", + "Account created successfully. Please log in to set up two-factor authentication.": "Konto erfolgreich erstellt. Bitte melden Sie sich an, um die Zwei-Faktor-Authentifizierung einzurichten.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an und führen Sie die Zwei-Faktor-Authentifizierung durch.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an, um die Zwei-Faktor-Authentifizierung einzurichten.", + "Password reset was successful. Please log in with your new password.": "Passwort erfolgreich zurückgesetzt. Bitte melden Sie sich mit Ihrem neuen Passwort an.", + "Two-factor authentication": "Zwei-Faktor-Authentifizierung", + "Use authenticator app instead": "Stattdessen Authenticator-App verwenden", + "Verify backup code": "Sicherungscode überprüfen", + "Use backup code": "Sicherungscode verwenden", + "Enter one of your backup codes": "Geben Sie einen Ihrer Sicherungscodes ein", + "Backup code": "Sicherungscode", + "Enter one of your backup codes. Each backup code can only be used once.": "Geben Sie einen Ihrer Sicherungscodes ein. Jeder Sicherungscode kann nur einmal verwendet werden.", + "Verify": "Überprüfen", + "Trash": "Papierkorb", + "Pages in trash will be permanently deleted after 30 days.": "Seiten im Papierkorb werden nach 30 Tagen endgültig gelöscht.", + "Deleted": "Gelöscht", + "No pages in trash": "Keine Seiten im Papierkorb", + "Permanently delete page?": "Seite endgültig löschen?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Sind Sie sicher, dass Sie '{{title}}' endgültig löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "Restore '{{title}}' and its sub-pages?": "'{{title}}' und seine Unterseiten wiederherstellen?", + "Move to trash": "In den Papierkorb verschieben", + "Move this page to trash?": "Diese Seite in den Papierkorb verschieben?", + "Restore page": "Seite wiederherstellen", + "Page moved to trash": "Seite in den Papierkorb verschoben", + "Page restored successfully": "Seite erfolgreich wiederhergestellt", + "Deleted by": "Gelöscht von", + "Deleted at": "Gelöscht am", + "Preview": "Vorschau", + "Subpages": "Unterseiten", + "Failed to load subpages": "Fehler beim Laden von Unterseiten", + "No subpages": "Keine Unterseiten", + "Subpages (Child pages)": "Unterseiten (Untergeordnete Seiten)", + "List all subpages of the current page": "Alle Unterseiten der aktuellen Seite auflisten", + "Attachments": "Anhänge", + "All spaces": "Alle Bereiche", + "Unknown": "Unbekannt", + "Find a space": "Einen Bereich finden", + "Search in all your spaces": "In all deinen Bereichen suchen", + "Type": "Art", + "Enterprise": "Unternehmen", + "Download attachment": "Anhang herunterladen", + "Allowed email domains": "Erlaubte E-Mail-Domains", + "Only users with email addresses from these domains can signup via SSO.": "Nur Benutzer mit E-Mail-Adressen aus diesen Domains können sich über SSO registrieren.", + "Enter valid domain names separated by comma or space": "Geben Sie gültige Domainnamen ein, durch Kommas oder Leerzeichen getrennt", + "Enforce two-factor authentication": "Erzwingen der Zwei-Faktor-Authentifizierung", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Sobald es erzwungen wird, müssen alle Mitglieder die Zwei-Faktor-Authentifizierung aktivieren, um auf den Arbeitsbereich zugreifen zu können.", + "Toggle MFA enforcement": "Umschalten der MFA-Erzwingung", + "Display name": "Anzeigename", + "Allow signup": "Registrierung erlauben", + "Enabled": "Aktiviert", + "Advanced Settings": "Erweiterte Einstellungen", + "Enable TLS/SSL": "TLS/SSL aktivieren", + "Use secure connection to LDAP server": "Sichere Verbindung zum LDAP-Server verwenden", + "Group sync": "Gruppensynchronisation", + "No SSO providers found.": "Keine SSO-Anbieter gefunden.", + "Delete SSO provider": "SSO-Anbieter löschen", + "Are you sure you want to delete this SSO provider?": "Sind Sie sicher, dass Sie diesen SSO-Anbieter löschen möchten?", + "Action": "Aktion", + "{{ssoProviderType}} configuration": "{{ssoProviderType}}-Konfiguration", + "Icon": "Icon", + "Upload image": "Bild hochladen", + "Remove image": "Bild entfernen", + "Failed to remove image": "Fehler beim Entfernen des Bildes", + "Image exceeds 10MB limit.": "Bild überschreitet das Limit von 10 MB.", + "Image removed successfully": "Bild erfolgreich entfernt", + "API key": "API-Schlüssel", + "API key created successfully": "API-Schlüssel erfolgreich erstellt", + "API keys": "API-Schlüssel", + "API management": "API-Verwaltung", + "Are you sure you want to revoke this API key": "Sind Sie sicher, dass Sie diesen API-Schlüssel widerrufen möchten?", + "Create API Key": "API-Schlüssel erstellen", + "Custom expiration date": "Benutzerdefiniertes Ablaufdatum", + "Enter a descriptive token name": "Geben Sie einen beschreibenden Token-Namen ein", + "Expiration": "Ablauf", + "Expired": "Abgelaufen", + "Expires": "Läuft ab", + "I've saved my API key": "Ich habe meinen API-Schlüssel gespeichert", + "Last use": "Zuletzt verwendet", + "No API keys found": "Keine API-Schlüssel gefunden", + "No expiration": "Kein Ablauf", + "Revoke API key": "API-Schlüssel widerrufen", + "Revoked successfully": "Erfolgreich widerrufen", + "Select expiration date": "Ablaufdatum wählen", + "This action cannot be undone. Any applications using this API key will stop working.": "Diese Aktion kann nicht rückgängig gemacht werden. Alle Anwendungen, die diesen API-Schlüssel verwenden, werden nicht mehr funktionieren.", + "Update API key": "API-Schlüssel aktualisieren", + "Manage API keys for all users in the workspace": "Verwalten Sie API-Schlüssel für alle Benutzer im Arbeitsbereich", + "AI settings": "KI-Einstellungen", + "AI search": "KI-Suche", + "AI Answer": "KI-Antwort", + "Ask AI": "KI fragen", + "AI is thinking...": "Die KI überlegt...", + "Ask a question...": "Fragen stellen...", + "AI-powered search (Ask AI)": "KI-gestützte Suche (KI fragen)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Die KI-Suche verwendet Vektor-Einbettungen, um semantische Suchfunktionen in Ihrem Arbeitsbereich bereitzustellen.", + "Toggle AI search": "KI-Suche umschalten", + "Sources": "Quellen", + "Ask AI not available for attachments": "KI fragen nicht für Anhänge verfügbar", + "No answer available": "Keine Antwort verfügbar", + "Background color": "Hintergrundfarbe", + "Highlight color": "Hervorhebungsfarbe", + "Remove color": "Farbe entfernen" } diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 6b500cf0..c0578d2b 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Choose your preferred interface language.", "Choose your preferred page width.": "Choose your preferred page width.", "Confirm": "Confirm", + "Copy as Markdown": "Copy as Markdown", "Copy link": "Copy link", "Create": "Create", "Create group": "Create group", @@ -53,6 +54,7 @@ "e.g Space for product team": "e.g Space for product team", "e.g Space for sales team to collaborate": "e.g Space for sales team to collaborate", "Edit": "Edit", + "Read": "Read", "Edit group": "Edit group", "Email": "Email", "Enter a strong password": "Enter a strong password", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Comment deleted successfully", "Failed to delete comment": "Failed to delete comment", "Comment resolved successfully": "Comment resolved successfully", + "Comment re-opened successfully": "Comment re-opened successfully", + "Comment unresolved successfully": "Comment unresolved successfully", "Failed to resolve comment": "Failed to resolve comment", + "Resolve comment": "Resolve comment", + "Unresolve comment": "Unresolve comment", + "Resolve Comment Thread": "Resolve Comment Thread", + "Unresolve Comment Thread": "Unresolve Comment Thread", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Are you sure you want to resolve this comment thread? This will mark it as completed.", + "Are you sure you want to unresolve this comment thread?": "Are you sure you want to unresolve this comment thread?", + "Resolved": "Resolved", + "No active comments.": "No active comments.", + "No resolved comments.": "No resolved comments.", "Revoke invitation": "Revoke invitation", "Revoke": "Revoke", "Don't": "Don't", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Anyone with this link can join this workspace.", "Invite link": "Invite link", "Copy": "Copy", + "Copy to space": "Copy to space", "Copied": "Copied", + "Duplicate": "Duplicate", "Select a user": "Select a user", "Select a group": "Select a group", "Export all pages and attachments in this space.": "Export all pages and attachments in this space.", @@ -239,6 +254,7 @@ "Export failed:": "Export failed:", "export error": "export error", "Export page": "Export page", + "Export successful": "Export successful", "Export space": "Export space", "Export {{type}}": "Export {{type}}", "File exceeds the {{limit}} attachment limit": "File exceeds the {{limit}} attachment limit", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Upload any image from your device.", "Upload any video from your device.": "Upload any video from your device.", "Upload any file from your device.": "Upload any file from your device.", + "Uploading {{name}}": "Uploading {{name}}", + "Uploading file": "Uploading file", "Table": "Table", "Insert a table.": "Insert a table.", "Insert collapsible block.": "Insert collapsible block.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Character count: {{characterCount}}", "New update": "New update", "{{latestVersion}} is available": "{{latestVersion}} is available", + "Default page edit mode": "Default page edit mode", + "Choose your preferred page edit mode. Avoid accidental edits.": "Choose your preferred page edit mode. Avoid accidental edits.", + "Reading": "Reading", "Delete member": "Delete member", "Member deleted successfully": "Member deleted successfully", "Are you sure you want to delete this workspace member? This action is irreversible.": "Are you sure you want to delete this workspace member? This action is irreversible.", @@ -386,5 +407,171 @@ "Failed to share page": "Failed to share page", "Copy page": "Copy page", "Copy page to a different space.": "Copy page to a different space.", - "Page copied successfully": "Page copied successfully" + "Page copied successfully": "Page copied successfully", + "Page duplicated successfully": "Page duplicated successfully", + "Find": "Find", + "Not found": "Not found", + "Previous Match (Shift+Enter)": "Previous Match (Shift+Enter)", + "Next match (Enter)": "Next match (Enter)", + "Match case (Alt+C)": "Match case (Alt+C)", + "Replace": "Replace", + "Close (Escape)": "Close (Escape)", + "Replace (Enter)": "Replace (Enter)", + "Replace all (Ctrl+Alt+Enter)": "Replace all (Ctrl+Alt+Enter)", + "Replace all": "Replace all", + "View all spaces": "View all spaces", + "Error": "Error", + "Failed to disable MFA": "Failed to disable MFA", + "Disable two-factor authentication": "Disable two-factor authentication", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.", + "Please enter your password to disable two-factor authentication:": "Please enter your password to disable two-factor authentication:", + "Two-factor authentication has been enabled": "Two-factor authentication has been enabled", + "Two-factor authentication has been disabled": "Two-factor authentication has been disabled", + "2-step verification": "2-step verification", + "Protect your account with an additional verification layer when signing in.": "Protect your account with an additional verification layer when signing in.", + "Two-factor authentication is active on your account.": "Two-factor authentication is active on your account.", + "Add 2FA method": "Add 2FA method", + "Backup codes": "Backup codes", + "Disable": "Disable", + "Invalid verification code": "Invalid verification code", + "New backup codes have been generated": "New backup codes have been generated", + "Failed to regenerate backup codes": "Failed to regenerate backup codes", + "About backup codes": "About backup codes", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "You can regenerate new backup codes at any time. This will invalidate all existing codes.", + "Confirm password": "Confirm password", + "Generate new backup codes": "Generate new backup codes", + "Save your new backup codes": "Save your new backup codes", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.", + "Your new backup codes": "Your new backup codes", + "I've saved my backup codes": "I've saved my backup codes", + "Failed to setup MFA": "Failed to setup MFA", + "Setup & Verify": "Setup & Verify", + "Add to authenticator": "Add to authenticator", + "1. Scan this QR code with your authenticator app": "1. Scan this QR code with your authenticator app", + "Can't scan the code?": "Can't scan the code?", + "Enter this code manually in your authenticator app:": "Enter this code manually in your authenticator app:", + "2. Enter the 6-digit code from your authenticator": "2. Enter the 6-digit code from your authenticator", + "Verify and enable": "Verify and enable", + "Failed to generate QR code. Please try again.": "Failed to generate QR code. Please try again.", + "Backup": "Backup", + "Save codes": "Save codes", + "Save your backup codes": "Save your backup codes", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.", + "Print": "Print", + "Two-factor authentication has been set up. Please log in again.": "Two-factor authentication has been set up. Please log in again.", + "Two-Factor authentication required": "Two-factor authentication required", + "Your workspace requires two-factor authentication for all users": "Your workspace requires two-factor authentication for all users", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.", + "Set up two-factor authentication": "Set up two-factor authentication", + "Cancel and logout": "Cancel and logout", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Your workspace requires two-factor authentication. Please set it up to continue.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.", + "Password is required": "Password is required", + "Password must be at least 8 characters": "Password must be at least 8 characters", + "Please enter a 6-digit code": "Please enter a 6-digit code", + "Code must be exactly 6 digits": "Code must be exactly 6 digits", + "Enter the 6-digit code found in your authenticator app": "Enter the 6-digit code found in your authenticator app", + "Need help authenticating?": "Need help authenticating?", + "MFA QR Code": "MFA QR Code", + "Account created successfully. Please log in to set up two-factor authentication.": "Account created successfully. Please log in to set up two-factor authentication.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Password reset successful. Please log in with your new password and complete two-factor authentication.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Password reset successful. Please log in with your new password to set up two-factor authentication.", + "Password reset was successful. Please log in with your new password.": "Password reset was successful. Please log in with your new password.", + "Two-factor authentication": "Two-factor authentication", + "Use authenticator app instead": "Use authenticator app instead", + "Verify backup code": "Verify backup code", + "Use backup code": "Use backup code", + "Enter one of your backup codes": "Enter one of your backup codes", + "Backup code": "Backup code", + "Enter one of your backup codes. Each backup code can only be used once.": "Enter one of your backup codes. Each backup code can only be used once.", + "Verify": "Verify", + "Trash": "Trash", + "Pages in trash will be permanently deleted after 30 days.": "Pages in trash will be permanently deleted after 30 days.", + "Deleted": "Deleted", + "No pages in trash": "No pages in trash", + "Permanently delete page?": "Permanently delete page?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.", + "Restore '{{title}}' and its sub-pages?": "Restore '{{title}}' and its sub-pages?", + "Move to trash": "Move to trash", + "Move this page to trash?": "Move this page to trash?", + "Restore page": "Restore page", + "Page moved to trash": "Page moved to trash", + "Page restored successfully": "Page restored successfully", + "Deleted by": "Deleted by", + "Deleted at": "Deleted at", + "Preview": "Preview", + "Subpages": "Subpages", + "Failed to load subpages": "Failed to load subpages", + "No subpages": "No subpages", + "Subpages (Child pages)": "Subpages (Child pages)", + "List all subpages of the current page": "List all subpages of the current page", + "Attachments": "Attachments", + "All spaces": "All spaces", + "Unknown": "Unknown", + "Find a space": "Find a space", + "Search in all your spaces": "Search in all your spaces", + "Type": "Type", + "Enterprise": "Enterprise", + "Download attachment": "Download attachment", + "Allowed email domains": "Allowed email domains", + "Only users with email addresses from these domains can signup via SSO.": "Only users with email addresses from these domains can signup via SSO.", + "Enter valid domain names separated by comma or space": "Enter valid domain names separated by comma or space", + "Enforce two-factor authentication": "Enforce two-factor authentication", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Once enforced, all members must enable two-factor authentication to access the workspace.", + "Toggle MFA enforcement": "Toggle MFA enforcement", + "Display name": "Display name", + "Allow signup": "Allow signup", + "Enabled": "Enabled", + "Advanced Settings": "Advanced Settings", + "Enable TLS/SSL": "Enable TLS/SSL", + "Use secure connection to LDAP server": "Use secure connection to LDAP server", + "Group sync": "Group sync", + "No SSO providers found.": "No SSO providers found.", + "Delete SSO provider": "Delete SSO provider", + "Are you sure you want to delete this SSO provider?": "Are you sure you want to delete this SSO provider?", + "Action": "Action", + "{{ssoProviderType}} configuration": "{{ssoProviderType}} configuration", + "Icon": "Icon", + "Upload image": "Upload image", + "Remove image": "Remove image", + "Failed to remove image": "Failed to remove image", + "Image exceeds 10MB limit.": "Image exceeds 10MB limit.", + "Image removed successfully": "Image removed successfully", + "API key": "API key", + "API key created successfully": "API key created successfully", + "API keys": "API keys", + "API management": "API management", + "Are you sure you want to revoke this API key": "Are you sure you want to revoke this API key", + "Create API Key": "Create API Key", + "Custom expiration date": "Custom expiration date", + "Enter a descriptive token name": "Enter a descriptive token name", + "Expiration": "Expiration", + "Expired": "Expired", + "Expires": "Expires", + "I've saved my API key": "I've saved my API key", + "Last use": "Last Used", + "No API keys found": "No API keys found", + "No expiration": "No expiration", + "Revoke API key": "Revoke API key", + "Revoked successfully": "Revoked successfully", + "Select expiration date": "Select expiration date", + "This action cannot be undone. Any applications using this API key will stop working.": "This action cannot be undone. Any applications using this API key will stop working.", + "Update API key": "Update API key", + "Manage API keys for all users in the workspace": "Manage API keys for all users in the workspace", + "AI settings": "AI settings", + "AI search": "AI search", + "AI Answer": "AI Answer", + "Ask AI": "Ask AI", + "AI is thinking...": "AI is thinking...", + "Ask a question...": "Ask a question...", + "AI-powered search (Ask AI)": "AI-powered search (Ask AI)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.", + "Toggle AI search": "Toggle AI search", + "Sources": "Sources", + "Ask AI not available for attachments": "Ask AI not available for attachments", + "No answer available": "No answer available", + "Background color": "Background color", + "Highlight color": "Highlight color", + "Remove color": "Remove color" } diff --git a/apps/client/public/locales/es-ES/translation.json b/apps/client/public/locales/es-ES/translation.json index b1bf62e6..af02c493 100644 --- a/apps/client/public/locales/es-ES/translation.json +++ b/apps/client/public/locales/es-ES/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Elige tu idioma de interfaz preferido.", "Choose your preferred page width.": "Elige el ancho de página que prefieras.", "Confirm": "Confirmar", + "Copy as Markdown": "Copiar como Markdown", "Copy link": "Copiar enlace", "Create": "Crear", "Create group": "Crear grupo", @@ -53,6 +54,7 @@ "e.g Space for product team": "ej: Espacio para el equipo de producto", "e.g Space for sales team to collaborate": "ej: Espacio para que el equipo de ventas colabore", "Edit": "Editar", + "Read": "Leer", "Edit group": "Editar grupo", "Email": "Correo electrónico", "Enter a strong password": "Introduce una contraseña fuerte", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Comentario eliminado con éxito", "Failed to delete comment": "No se pudo eliminar el comentario", "Comment resolved successfully": "Comentario resuelto con éxito", + "Comment re-opened successfully": "Comentario reabierto con éxito", + "Comment unresolved successfully": "Comentario no resuelto con éxito", "Failed to resolve comment": "No se pudo resolver el comentario", + "Resolve comment": "Resolver comentario", + "Unresolve comment": "No resolver comentario", + "Resolve Comment Thread": "Resolver hilo de comentarios", + "Unresolve Comment Thread": "No resolver hilo de comentarios", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "¿Está seguro de que desea resolver este hilo de comentarios? Esto lo marcará como completado.", + "Are you sure you want to unresolve this comment thread?": "¿Está seguro de que desea no resolver este hilo de comentarios?", + "Resolved": "Resuelto", + "No active comments.": "No hay comentarios activos.", + "No resolved comments.": "No hay comentarios resueltos.", "Revoke invitation": "Revocar invitación", "Revoke": "Revocar", "Don't": "No", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Cualquiera con este enlace puede unirse a este espacio de trabajo.", "Invite link": "Enlace de invitación", "Copy": "Copiar", + "Copy to space": "Copiar al espacio", "Copied": "Copiado", + "Duplicate": "Duplicar", "Select a user": "Seleccionar un usuario", "Select a group": "Seleccionar un grupo", "Export all pages and attachments in this space.": "Exportar todas las páginas y archivos adjuntos en este espacio.", @@ -239,6 +254,7 @@ "Export failed:": "Exportación fallida:", "export error": "error de exportación", "Export page": "Exportar página", + "Export successful": "Exportación exitosa", "Export space": "Exportar espacio", "Export {{type}}": "Exportar {{type}}", "File exceeds the {{limit}} attachment limit": "El archivo supera el límite de {{limit}} adjuntos", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Sube cualquier imagen desde tu dispositivo.", "Upload any video from your device.": "Sube cualquier video desde tu dispositivo.", "Upload any file from your device.": "Sube cualquier archivo desde tu dispositivo.", + "Uploading {{name}}": "Subiendo {{name}}", + "Uploading file": "Subiendo archivo", "Table": "Tabla", "Insert a table.": "Insertar una tabla.", "Insert collapsible block.": "Insertar bloque desplegable.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Recuento de caracteres: {{characterCount}}", "New update": "Nueva actualización", "{{latestVersion}} is available": "{{latestVersion}} está disponible", + "Default page edit mode": "Modo de edición de página predeterminado", + "Choose your preferred page edit mode. Avoid accidental edits.": "Elige tu modo de edición de página preferido. Evita ediciones accidentales.", + "Reading": "Leyendo", "Delete member": "Eliminar miembro", "Member deleted successfully": "Miembro eliminado con éxito", "Are you sure you want to delete this workspace member? This action is irreversible.": "¿Está seguro que desea eliminar este miembro del área de trabajo? Esta acción es irreversible.", @@ -384,7 +405,173 @@ "Share deleted successfully": "Compartición eliminada con éxito", "Share not found": "Compartición no encontrada", "Failed to share page": "Error al compartir la página", - "Copy page": "Copy page", - "Copy page to a different space.": "Copy page to a different space.", - "Page copied successfully": "Page copied successfully" + "Copy page": "Copiar página", + "Copy page to a different space.": "Copiar página en otro espacio", + "Page copied successfully": "Página copiada exitosamente", + "Page duplicated successfully": "Página duplicada con éxito", + "Find": "Buscar", + "Not found": "No encontrado", + "Previous Match (Shift+Enter)": "Coincidencia anterior (Shift+Enter)", + "Next match (Enter)": "Siguiente coincidencia (Enter)", + "Match case (Alt+C)": "Distinguir mayúsculas y minúsculas (Alt+C)", + "Replace": "Reemplazar", + "Close (Escape)": "Cerrar (Escape)", + "Replace (Enter)": "Reemplazar (Enter)", + "Replace all (Ctrl+Alt+Enter)": "Reemplazar todo (Ctrl+Alt+Enter)", + "Replace all": "Reemplazar todo", + "View all spaces": "Ver todos los espacios", + "Error": "Error", + "Failed to disable MFA": "No se pudo desactivar MFA", + "Disable two-factor authentication": "Desactivar la autenticación de dos factores", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Desactivar la autenticación de dos factores hará que tu cuenta sea menos segura. Solo necesitarás tu contraseña para iniciar sesión.", + "Please enter your password to disable two-factor authentication:": "Por favor ingresa tu contraseña para desactivar la autenticación de dos factores:", + "Two-factor authentication has been enabled": "La autenticación de dos factores ha sido activada", + "Two-factor authentication has been disabled": "La autenticación de dos factores ha sido desactivada", + "2-step verification": "Verificación en 2 pasos", + "Protect your account with an additional verification layer when signing in.": "Protege tu cuenta con una capa adicional de verificación al iniciar sesión.", + "Two-factor authentication is active on your account.": "La autenticación de dos factores está activa en tu cuenta.", + "Add 2FA method": "Agregar método 2FA", + "Backup codes": "Códigos de seguridad", + "Disable": "Desactivar", + "Invalid verification code": "Código de verificación no válido", + "New backup codes have been generated": "Nuevos códigos de seguridad han sido generados", + "Failed to regenerate backup codes": "No se pudo regenerar los códigos de seguridad", + "About backup codes": "Acerca de los códigos de seguridad", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Los códigos de seguridad pueden usarse para acceder a tu cuenta si pierdes acceso a tu aplicación autenticadora. Cada código solo puede ser usado una vez.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Puedes regenerar nuevos códigos de seguridad en cualquier momento. Esto invalidará todos los códigos existentes.", + "Confirm password": "Confirmar contraseña", + "Generate new backup codes": "Generar nuevos códigos de seguridad", + "Save your new backup codes": "Guarda tus nuevos códigos de seguridad", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Asegúrate de guardar estos códigos en un lugar seguro. Tus viejos códigos de seguridad ya no son válidos.", + "Your new backup codes": "Tus nuevos códigos de seguridad", + "I've saved my backup codes": "He guardado mis códigos de seguridad", + "Failed to setup MFA": "No se pudo configurar MFA", + "Setup & Verify": "Configurar y verificar", + "Add to authenticator": "Agregar al autenticador", + "1. Scan this QR code with your authenticator app": "1. Escanea este código QR con tu aplicación autenticadora", + "Can't scan the code?": "¿No puedes escanear el código?", + "Enter this code manually in your authenticator app:": "Introduce este código manualmente en tu aplicación autenticadora:", + "2. Enter the 6-digit code from your authenticator": "2. Introduce el código de 6 dígitos de tu autenticador", + "Verify and enable": "Verificar y activar", + "Failed to generate QR code. Please try again.": "No se pudo generar el código QR. Por favor, intente de nuevo.", + "Backup": "Respaldo", + "Save codes": "Guardar códigos", + "Save your backup codes": "Guarda tus códigos de seguridad", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Estos códigos pueden usarse para acceder a tu cuenta si pierdes acceso a tu aplicación autenticadora. Cada código solo puede ser usado una vez.", + "Print": "Imprimir", + "Two-factor authentication has been set up. Please log in again.": "La autenticación de dos factores ha sido configurada. Por favor, inicie sesión nuevamente.", + "Two-Factor authentication required": "Se requiere autenticación de dos factores", + "Your workspace requires two-factor authentication for all users": "Tu espacio de trabajo requiere autenticación de dos factores para todos los usuarios", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Para continuar accediendo a tu espacio de trabajo, debes configurar la autenticación de dos factores. Esto añade una capa extra de seguridad a tu cuenta.", + "Set up two-factor authentication": "Configurar la autenticación de dos factores", + "Cancel and logout": "Cancelar y cerrar sesión", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Tu espacio de trabajo requiere autenticación de dos factores. Por favor, configúralo para continuar.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Esto añade una capa extra de seguridad a tu cuenta al requerir un código de verificación de tu aplicación autenticadora.", + "Password is required": "Se requiere contraseña", + "Password must be at least 8 characters": "La contraseña debe tener al menos 8 caracteres", + "Please enter a 6-digit code": "Por favor, introduce un código de 6 dígitos", + "Code must be exactly 6 digits": "El código debe ser exactamente de 6 dígitos", + "Enter the 6-digit code found in your authenticator app": "Introduce el código de 6 dígitos que se encuentra en tu aplicación autenticadora", + "Need help authenticating?": "¿Necesitas ayuda para autenticar?", + "MFA QR Code": "Código QR MFA", + "Account created successfully. Please log in to set up two-factor authentication.": "Cuenta creada exitosamente. Por favor, inicie sesión para configurar la autenticación de dos factores.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Restablecimiento de contraseña exitoso. Por favor, inicie sesión con su nueva contraseña y complete la autenticación de dos factores.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Restablecimiento de contraseña exitoso. Por favor, inicie sesión con su nueva contraseña para configurar la autenticación de dos factores.", + "Password reset was successful. Please log in with your new password.": "El restablecimiento de contraseña fue exitoso. Por favor, inicie sesión con su nueva contraseña.", + "Two-factor authentication": "Autenticación de dos factores", + "Use authenticator app instead": "Usar la aplicación autenticadora en su lugar", + "Verify backup code": "Verificar código de seguridad", + "Use backup code": "Usar código de seguridad", + "Enter one of your backup codes": "Introduce uno de tus códigos de seguridad", + "Backup code": "Código de seguridad", + "Enter one of your backup codes. Each backup code can only be used once.": "Introduce uno de tus códigos de seguridad. Cada código de seguridad solo puede ser usado una vez.", + "Verify": "Verificar", + "Trash": "Papelera", + "Pages in trash will be permanently deleted after 30 days.": "Las páginas en la papelera serán eliminadas permanentemente después de 30 días.", + "Deleted": "Eliminado", + "No pages in trash": "No hay páginas en la papelera", + "Permanently delete page?": "¿Eliminar página permanentemente?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "¿Está seguro de que desea eliminar '{{title}}' permanentemente? Esta acción no se puede deshacer.", + "Restore '{{title}}' and its sub-pages?": "¿Restaurar '{{title}}' y sus subpáginas?", + "Move to trash": "Mover a la papelera", + "Move this page to trash?": "¿Mover esta página a la papelera?", + "Restore page": "Restaurar página", + "Page moved to trash": "Página movida a la papelera", + "Page restored successfully": "Página restaurada con éxito", + "Deleted by": "Eliminado por", + "Deleted at": "Eliminado en", + "Preview": "Vista previa", + "Subpages": "Subpáginas", + "Failed to load subpages": "Error al cargar subpáginas", + "No subpages": "Sin subpáginas", + "Subpages (Child pages)": "Subpáginas (Páginas hijas)", + "List all subpages of the current page": "Listar todas las subpáginas de la página actual", + "Attachments": "Adjuntos", + "All spaces": "Todos los espacios", + "Unknown": "Desconocido", + "Find a space": "Encontrar un espacio", + "Search in all your spaces": "Buscar en todos tus espacios", + "Type": "Tipo", + "Enterprise": "Empresa", + "Download attachment": "Descargar adjunto", + "Allowed email domains": "Dominios de correo electrónico permitidos", + "Only users with email addresses from these domains can signup via SSO.": "Solo los usuarios con direcciones de correo electrónico de estos dominios pueden registrarse a través de SSO.", + "Enter valid domain names separated by comma or space": "Introduce nombres de dominio válidos separados por coma o espacio", + "Enforce two-factor authentication": "Aplicar autenticación de dos factores", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Una vez aplicada, todos los miembros deben habilitar la autenticación de dos factores para acceder al espacio de trabajo.", + "Toggle MFA enforcement": "Alternar la aplicación de MFA", + "Display name": "Nombre para mostrar", + "Allow signup": "Permitir registro", + "Enabled": "Habilitado", + "Advanced Settings": "Configuración avanzada", + "Enable TLS/SSL": "Habilitar TLS/SSL", + "Use secure connection to LDAP server": "Usar conexión segura al servidor LDAP", + "Group sync": "Sincronización de grupos", + "No SSO providers found.": "No se encontraron proveedores de SSO.", + "Delete SSO provider": "Eliminar proveedor de SSO", + "Are you sure you want to delete this SSO provider?": "¿Está seguro de que desea eliminar este proveedor de SSO?", + "Action": "Acción", + "{{ssoProviderType}} configuration": "Configuración de {{ssoProviderType}}", + "Icon": "Icono", + "Upload image": "Subir imagen", + "Remove image": "Eliminar imagen", + "Failed to remove image": "No se ha podido eliminar la imagen", + "Image exceeds 10MB limit.": "La imagen excede del límite de 10 MB", + "Image removed successfully": "Imagen eliminada correctamente", + "API key": "Clave API", + "API key created successfully": "Clave API creada correctamente", + "API keys": "Claves API", + "API management": "Gestión de API", + "Are you sure you want to revoke this API key": "¿Está seguro de que desea revocar esta clave API? ", + "Create API Key": "Crear clave API", + "Custom expiration date": "Fecha de vencimiento personalizada", + "Enter a descriptive token name": "Introduce un nombre descriptivo del token", + "Expiration": "Vencimiento", + "Expired": "Vencido", + "Expires": "Vence", + "I've saved my API key": "He guardado mi clave API", + "Last use": "Último uso", + "No API keys found": "No se han encontrado claves API", + "No expiration": "Sin vencimiento", + "Revoke API key": "Revocar clave API", + "Revoked successfully": "Revocada correctamente", + "Select expiration date": "Seleccionar fecha de vencimiento", + "This action cannot be undone. Any applications using this API key will stop working.": "Esta acción no se puede deshacer. Las aplicaciones que utilicen esta clave API dejarán de funcionar.", + "Update API key": "Actualizar clave API", + "Manage API keys for all users in the workspace": "Gestionar claves API para todos los usuarios en el espacio de trabajo", + "AI settings": "Configuración de IA", + "AI search": "Búsqueda de IA", + "AI Answer": "Respuesta de IA", + "Ask AI": "Preguntar a IA", + "AI is thinking...": "IA está pensando...", + "Ask a question...": "Haz una pregunta...", + "AI-powered search (Ask AI)": "Búsqueda impulsada por IA (Preguntar a IA)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La búsqueda de IA utiliza incrustaciones vectoriales para proporcionar capacidades de búsqueda semántica en todo el contenido de su espacio de trabajo.", + "Toggle AI search": "Alternar búsqueda de IA", + "Sources": "Fuentes", + "Ask AI not available for attachments": "Preguntar a IA no está disponible para adjuntos", + "No answer available": "No hay respuesta disponible", + "Background color": "Color de fondo", + "Highlight color": "Color de resaltado", + "Remove color": "Eliminar color" } diff --git a/apps/client/public/locales/fr-FR/translation.json b/apps/client/public/locales/fr-FR/translation.json index 9885dc51..40a1e68a 100644 --- a/apps/client/public/locales/fr-FR/translation.json +++ b/apps/client/public/locales/fr-FR/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Choisissez votre langue d'interface préférée.", "Choose your preferred page width.": "Choisissez votre largeur de page préférée.", "Confirm": "Confirmer", + "Copy as Markdown": "Copier comme Markdown", "Copy link": "Copier le lien", "Create": "Créer", "Create group": "Créer groupe", @@ -53,6 +54,7 @@ "e.g Space for product team": "par ex. Espace pour l'équipe produit", "e.g Space for sales team to collaborate": "par ex. Espace pour l'équipe de vente pour collaborer", "Edit": "Modifier", + "Read": "Lire", "Edit group": "Modifier groupe", "Email": "Email", "Enter a strong password": "Entrez un mot de passe fort", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Commentaire supprimé avec succès", "Failed to delete comment": "Échec de la suppression du commentaire", "Comment resolved successfully": "Commentaire résolu avec succès", + "Comment re-opened successfully": "Commentaire rouvert avec succès", + "Comment unresolved successfully": "Commentaire non résolu avec succès", "Failed to resolve comment": "Échec de la résolution du commentaire", + "Resolve comment": "Résoudre le commentaire", + "Unresolve comment": "Désorganiser le commentaire", + "Resolve Comment Thread": "Résoudre le fil de commentaires", + "Unresolve Comment Thread": "Désorganiser le fil de commentaires", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Êtes-vous sûr de vouloir résoudre ce fil de commentaires ? Cela le marquera comme terminé.", + "Are you sure you want to unresolve this comment thread?": "Êtes-vous sûr de vouloir désorganiser ce fil de commentaires ?", + "Resolved": "Résolu", + "No active comments.": "Aucun commentaire actif.", + "No resolved comments.": "Aucun commentaire résolu.", "Revoke invitation": "Révoquer l'invitation", "Revoke": "Révoquer", "Don't": "Ne pas", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Toute personne ayant ce lien peut rejoindre cet espace de travail.", "Invite link": "Lien d'invitation", "Copy": "Copier", + "Copy to space": "Copier dans l'espace", "Copied": "Copié", + "Duplicate": "Dupliquer", "Select a user": "Sélectionner un utilisateur", "Select a group": "Sélectionner un groupe", "Export all pages and attachments in this space.": "Exporter toutes les pages et pièces jointes dans cet espace.", @@ -239,6 +254,7 @@ "Export failed:": "Échec de l'exportation :", "export error": "exporter l'erreur", "Export page": "Exporter la page", + "Export successful": "Exportation réussie", "Export space": "Exporter l'espace", "Export {{type}}": "Exporter {{type}}", "File exceeds the {{limit}} attachment limit": "Le fichier dépasse la limite de {{limit}} pièces jointes", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Téléchargez n'importe quelle image depuis votre appareil.", "Upload any video from your device.": "Téléchargez n'importe quelle vidéo depuis votre appareil.", "Upload any file from your device.": "Téléchargez n'importe quel fichier depuis votre appareil.", + "Uploading {{name}}": "Téléchargement de {{name}}", + "Uploading file": "Téléchargement du fichier", "Table": "Tableau", "Insert a table.": "Insérez un tableau.", "Insert collapsible block.": "Insérer un bloc repliable.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Nombre de caractères : {{characterCount}}", "New update": "Nouvelle mise à jour", "{{latestVersion}} is available": "{{latestVersion}} est disponible", + "Default page edit mode": "Mode d'édition de page par défaut", + "Choose your preferred page edit mode. Avoid accidental edits.": "Choisissez votre mode d'édition de page préféré. Évitez les modifications accidentelles.", + "Reading": "Lecture", "Delete member": "Supprimer le membre", "Member deleted successfully": "Membre supprimé avec succès", "Are you sure you want to delete this workspace member? This action is irreversible.": "Êtes-vous sûr de vouloir supprimer ce membre de l'espace de travail? Cette action est irréversible.", @@ -386,5 +407,171 @@ "Failed to share page": "Échec du partage de la page", "Copy page": "Copier la page", "Copy page to a different space.": "Copier la page dans un autre espace.", - "Page copied successfully": "Page copiée avec succès" + "Page copied successfully": "Page copiée avec succès", + "Page duplicated successfully": "Page dupliquée avec succès", + "Find": "Trouver", + "Not found": "Non trouvé", + "Previous Match (Shift+Enter)": "Correspondance précédente (Shift+Entrée)", + "Next match (Enter)": "Correspondance suivante (Entrée)", + "Match case (Alt+C)": "Respecter la casse (Alt+C)", + "Replace": "Remplacer", + "Close (Escape)": "Fermer (Échapper)", + "Replace (Enter)": "Remplacer (Entrée)", + "Replace all (Ctrl+Alt+Enter)": "Tout remplacer (Ctrl+Alt+Entrée)", + "Replace all": "Tout remplacer", + "View all spaces": "Voir tous les espaces", + "Error": "Erreur", + "Failed to disable MFA": "Impossible de désactiver l'A2F", + "Disable two-factor authentication": "Désactiver l'authentification à deux facteurs", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "La désactivation de l'authentification à deux facteurs rendra votre compte moins sécurisé. Vous n'aurez besoin que de votre mot de passe pour vous connecter.", + "Please enter your password to disable two-factor authentication:": "Veuillez entrer votre mot de passe pour désactiver l'authentification à deux facteurs :", + "Two-factor authentication has been enabled": "L'authentification à deux facteurs a été activée", + "Two-factor authentication has been disabled": "L'authentification à deux facteurs a été désactivée", + "2-step verification": "Vérification en 2 étapes", + "Protect your account with an additional verification layer when signing in.": "Protégez votre compte avec une couche de vérification supplémentaire lors de la connexion.", + "Two-factor authentication is active on your account.": "L'authentification à deux facteurs est active sur votre compte.", + "Add 2FA method": "Ajouter une méthode A2F", + "Backup codes": "Codes de sauvegarde", + "Disable": "Désactiver", + "Invalid verification code": "Code de vérification invalide", + "New backup codes have been generated": "De nouveaux codes de sauvegarde ont été générés", + "Failed to regenerate backup codes": "Échec de la régénération des codes de sauvegarde", + "About backup codes": "À propos des codes de sauvegarde", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Les codes de sauvegarde peuvent être utilisés pour accéder à votre compte si vous perdez l'accès à votre application d'authentification. Chaque code ne peut être utilisé qu'une seule fois.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Vous pouvez régénérer de nouveaux codes de sauvegarde à tout moment. Cela invalidera tous les codes existants.", + "Confirm password": "Confirmer le mot de passe", + "Generate new backup codes": "Générer de nouveaux codes de sauvegarde", + "Save your new backup codes": "Enregistrez vos nouveaux codes de sauvegarde", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Assurez-vous d'enregistrer ces codes dans un endroit sécurisé. Vos anciens codes de sauvegarde ne sont plus valides.", + "Your new backup codes": "Vos nouveaux codes de sauvegarde", + "I've saved my backup codes": "J'ai enregistré mes codes de sauvegarde", + "Failed to setup MFA": "Échec de la configuration de l'A2F", + "Setup & Verify": "Configurer et vérifier", + "Add to authenticator": "Ajouter à l'authentification", + "1. Scan this QR code with your authenticator app": "1. Scannez ce code QR avec votre application d'authentification", + "Can't scan the code?": "Impossible de scanner le code ?", + "Enter this code manually in your authenticator app:": "Entrez ce code manuellement dans votre application d'authentification :", + "2. Enter the 6-digit code from your authenticator": "2. Entrez le code à 6 chiffres de votre authentificateur", + "Verify and enable": "Vérifier et activer", + "Failed to generate QR code. Please try again.": "Échec de la génération du code QR. Veuillez réessayer.", + "Backup": "Sauvegarde", + "Save codes": "Enregistrer les codes", + "Save your backup codes": "Enregistrez vos codes de sauvegarde", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Ces codes peuvent être utilisés pour accéder à votre compte si vous perdez l'accès à votre application d'authentification. Chaque code ne peut être utilisé qu'une seule fois.", + "Print": "Imprimer", + "Two-factor authentication has been set up. Please log in again.": "L'authentification à deux facteurs a été configurée. Veuillez vous reconnecter.", + "Two-Factor authentication required": "Authentification à deux facteurs requise", + "Your workspace requires two-factor authentication for all users": "Votre espace de travail nécessite l'authentification à deux facteurs pour tous les utilisateurs", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Pour continuer à accéder à votre espace de travail, vous devez configurer l'authentification à deux facteurs. Cela ajoute une couche de sécurité supplémentaire à votre compte.", + "Set up two-factor authentication": "Configurer l'authentification à deux facteurs", + "Cancel and logout": "Annuler et se déconnecter", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Votre espace de travail nécessite l'authentification à deux facteurs. Veuillez le configurer pour continuer.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Cela ajoute une couche de sécurité supplémentaire à votre compte en exigeant un code de vérification provenant de votre application d'authentification.", + "Password is required": "Mot de passe requis", + "Password must be at least 8 characters": "Le mot de passe doit comporter au moins 8 caractères", + "Please enter a 6-digit code": "Veuillez entrer un code à 6 chiffres", + "Code must be exactly 6 digits": "Le code doit être exactement de 6 chiffres", + "Enter the 6-digit code found in your authenticator app": "Entrez le code à 6 chiffres trouvé dans votre application d'authentification", + "Need help authenticating?": "Besoin d'aide pour l'authentification ?", + "MFA QR Code": "Code QR de l'A2F", + "Account created successfully. Please log in to set up two-factor authentication.": "Compte créé avec succès. Veuillez vous connecter pour configurer l'authentification à deux facteurs.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Réinitialisation du mot de passe réussie. Veuillez vous connecter avec votre nouveau mot de passe et compléter l'authentification à deux facteurs.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Réinitialisation du mot de passe réussie. Veuillez vous connecter avec votre nouveau mot de passe pour configurer l'authentification à deux facteurs.", + "Password reset was successful. Please log in with your new password.": "La réinitialisation du mot de passe a réussi. Veuillez vous connecter avec votre nouveau mot de passe.", + "Two-factor authentication": "Authentification à deux facteurs", + "Use authenticator app instead": "Utilisez l'application d'authentification à la place", + "Verify backup code": "Vérifier le code de sauvegarde", + "Use backup code": "Utiliser le code de sauvegarde", + "Enter one of your backup codes": "Entrez un de vos codes de sauvegarde", + "Backup code": "Code de sauvegarde", + "Enter one of your backup codes. Each backup code can only be used once.": "Entrez un de vos codes de sauvegarde. Chaque code de sauvegarde ne peut être utilisé qu'une seule fois.", + "Verify": "Vérifier", + "Trash": "Corbeille", + "Pages in trash will be permanently deleted after 30 days.": "Les pages dans la corbeille seront définitivement supprimées après 30 jours.", + "Deleted": "Supprimé", + "No pages in trash": "Aucune page dans la corbeille", + "Permanently delete page?": "Supprimer définitivement la page ?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Êtes-vous sûr de vouloir supprimer définitivement « {{title}} » ? Cette action ne peut pas être annulée.", + "Restore '{{title}}' and its sub-pages?": "Restaurer « {{title}} » et ses sous-pages ?", + "Move to trash": "Déplacer vers la corbeille", + "Move this page to trash?": "Déplacer cette page vers la corbeille ?", + "Restore page": "Restaurer la page", + "Page moved to trash": "Page déplacée vers la corbeille", + "Page restored successfully": "Page restaurée avec succès", + "Deleted by": "Supprimé par", + "Deleted at": "Supprimé à", + "Preview": "Aperçu", + "Subpages": "Sous-pages", + "Failed to load subpages": "Échec du chargement des sous-pages", + "No subpages": "Pas de sous-pages", + "Subpages (Child pages)": "Sous-pages (Pages enfants)", + "List all subpages of the current page": "Lister toutes les sous-pages de la page actuelle", + "Attachments": "Pièces jointes", + "All spaces": "Tous les espaces", + "Unknown": "Inconnu", + "Find a space": "Trouver un espace", + "Search in all your spaces": "Rechercher dans tous vos espaces", + "Type": "Type", + "Enterprise": "Entreprise", + "Download attachment": "Télécharger la pièce jointe", + "Allowed email domains": "Domaines de messagerie autorisés", + "Only users with email addresses from these domains can signup via SSO.": "Seuls les utilisateurs possédant des adresses e-mail provenant de ces domaines peuvent s'inscrire via SSO.", + "Enter valid domain names separated by comma or space": "Entrez des noms de domaine valides séparés par une virgule ou un espace", + "Enforce two-factor authentication": "Imposer l'authentification à deux facteurs", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Une fois appliquée, tous les membres doivent activer l'authentification à deux facteurs pour accéder à l'espace de travail.", + "Toggle MFA enforcement": "Basculer l'application de l'AMF", + "Display name": "Nom d'affichage", + "Allow signup": "Autoriser l'inscription", + "Enabled": "Activé", + "Advanced Settings": "Paramètres avancés", + "Enable TLS/SSL": "Activer TLS/SSL", + "Use secure connection to LDAP server": "Utiliser une connexion sécurisée au serveur LDAP", + "Group sync": "Synchronisation de groupe", + "No SSO providers found.": "Aucun fournisseur SSO trouvé.", + "Delete SSO provider": "Supprimer le fournisseur SSO", + "Are you sure you want to delete this SSO provider?": "Êtes-vous sûr de vouloir supprimer ce fournisseur SSO ?", + "Action": "Action", + "{{ssoProviderType}} configuration": "Configuration {{ssoProviderType}}", + "Icon": "Icône", + "Upload image": "Téléverser une image", + "Remove image": "Supprimer l'image", + "Failed to remove image": "Échec de la suppression de l'image", + "Image exceeds 10MB limit.": "L'image dépasse la limite de 10 Mo.", + "Image removed successfully": "Image supprimée avec succès", + "API key": "Clé API", + "API key created successfully": "Clé API créée avec succès", + "API keys": "Clés API", + "API management": "Gestion des API", + "Are you sure you want to revoke this API key": "Êtes-vous sûr de vouloir révoquer cette clé API", + "Create API Key": "Créer une clé API", + "Custom expiration date": "Date d'expiration personnalisée", + "Enter a descriptive token name": "Entrez un nom descriptif pour le jeton", + "Expiration": "Expiration", + "Expired": "Expiré(e)", + "Expires": "Expire", + "I've saved my API key": "J'ai enregistré ma clé API", + "Last use": "Dernière utilisation", + "No API keys found": "Aucune clé API trouvée", + "No expiration": "Pas d'expiration", + "Revoke API key": "Révoquer la clé API", + "Revoked successfully": "Révoqué(e) avec succès", + "Select expiration date": "Sélectionnez la date d'expiration", + "This action cannot be undone. Any applications using this API key will stop working.": "Cette action ne peut pas être annulée. Toutes les applications utilisant cette clé API cesseront de fonctionner.", + "Update API key": "Mettre à jour la clé API", + "Manage API keys for all users in the workspace": "Gérer les clés API pour tous les utilisateurs dans l'espace de travail", + "AI settings": "Paramètres de l'IA", + "AI search": "Recherche IA", + "AI Answer": "Réponse IA", + "Ask AI": "Demander à l'IA", + "AI is thinking...": "L'IA réfléchit...", + "Ask a question...": "Posez une question...", + "AI-powered search (Ask AI)": "Recherche assistée par l'IA (Demander à l'IA)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La recherche IA utilise des incorporations vectorielles pour fournir des capacités de recherche sémantique à travers le contenu de votre espace de travail.", + "Toggle AI search": "Basculer la recherche IA", + "Sources": "Sources", + "Ask AI not available for attachments": "Demande à l'IA non disponible pour les pièces jointes", + "No answer available": "Pas de réponse disponible", + "Background color": "Couleur de fond", + "Highlight color": "Couleur de surbrillance", + "Remove color": "Supprimer la couleur" } diff --git a/apps/client/public/locales/it-IT/translation.json b/apps/client/public/locales/it-IT/translation.json index e0b6db7e..ff80df0f 100644 --- a/apps/client/public/locales/it-IT/translation.json +++ b/apps/client/public/locales/it-IT/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Scegli la lingua da utilizzare per l'interfaccia.", "Choose your preferred page width.": "Scegli la larghezza della pagina che preferisci.", "Confirm": "Conferma", + "Copy as Markdown": "Copia come Markdown", "Copy link": "Copia link", "Create": "Crea", "Create group": "Crea gruppo", @@ -53,6 +54,7 @@ "e.g Space for product team": "es. Spazio per il team di prodotto", "e.g Space for sales team to collaborate": "es. Spazio per la collaborazione del team di vendita", "Edit": "Modifica", + "Read": "Leggi", "Edit group": "Modifica gruppo", "Email": "Email", "Enter a strong password": "Inserisci una password sicura", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Commento eliminato con successo", "Failed to delete comment": "Impossibile eliminare il commento", "Comment resolved successfully": "Commento risolto con successo", + "Comment re-opened successfully": "Commento riaperto con successo", + "Comment unresolved successfully": "Commento non risolto con successo", "Failed to resolve comment": "Impossibile risolvere il commento", + "Resolve comment": "Risolvi commento", + "Unresolve comment": "Annulla risoluzione commento", + "Resolve Comment Thread": "Risolvi discussione commenti", + "Unresolve Comment Thread": "Annulla risoluzione discussione commenti", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Sei sicuro di voler risolvere questa discussione di commenti? Questo la contrassegnerà come completata.", + "Are you sure you want to unresolve this comment thread?": "Sei sicuro di voler annullare la risoluzione di questa discussione di commenti?", + "Resolved": "Risolto", + "No active comments.": "Nessun commento attivo.", + "No resolved comments.": "Nessun commento risolto.", "Revoke invitation": "Revoca invito", "Revoke": "Revoca", "Don't": "Non", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Chiunque con questo link può unirsi a questa area di lavoro.", "Invite link": "Link d'invito", "Copy": "Copia", + "Copy to space": "Copia nello spazio", "Copied": "Copiato", + "Duplicate": "Duplica", "Select a user": "Seleziona un utente", "Select a group": "Seleziona un gruppo", "Export all pages and attachments in this space.": "Esporta tutte le pagine e gli allegati di questo spazio.", @@ -239,6 +254,7 @@ "Export failed:": "Esportazione fallita:", "export error": "errore di esportazione", "Export page": "Esporta pagina", + "Export successful": "Esportazione riuscita", "Export space": "Esporta spazio", "Export {{type}}": "Esporta {{type}}", "File exceeds the {{limit}} attachment limit": "Il file supera il limite per gli allegati di {{limit}}", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Carica un'immagine dal tuo dispositivo.", "Upload any video from your device.": "Carica qualsiasi video dal tuo dispositivo.", "Upload any file from your device.": "Carica qualsiasi file dal tuo dispositivo.", + "Uploading {{name}}": "Caricamento di {{name}}", + "Uploading file": "Caricamento file", "Table": "Tabella", "Insert a table.": "Inserisci una tabella.", "Insert collapsible block.": "Inserisci blocco comprimibile.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Conteggio caratteri: {{characterCount}}", "New update": "Nuovo aggiornamento", "{{latestVersion}} is available": "{{latestVersion}} è disponibile", + "Default page edit mode": "Modalità di modifica pagina predefinita", + "Choose your preferred page edit mode. Avoid accidental edits.": "Scegli la tua modalità di modifica della pagina preferita. Evita modifiche accidentali.", + "Reading": "Lettura", "Delete member": "Elimina membro", "Member deleted successfully": "Membro eliminato con successo", "Are you sure you want to delete this workspace member? This action is irreversible.": "Sei sicuro di voler eliminare questo membro del workspace? Questa azione è irreversibile.", @@ -386,5 +407,171 @@ "Failed to share page": "Condivisione della pagina fallita", "Copy page": "Copia pagina", "Copy page to a different space.": "Copia pagina in un altro spazio.", - "Page copied successfully": "Pagina copiata con successo" + "Page copied successfully": "Pagina copiata con successo", + "Page duplicated successfully": "Pagina duplicata con successo", + "Find": "Trova", + "Not found": "Non trovato", + "Previous Match (Shift+Enter)": "Corrispondenza precedente (Shift+Invio)", + "Next match (Enter)": "Corrispondenza successiva (Invio)", + "Match case (Alt+C)": "Maiuscole/minuscole (Alt+C)", + "Replace": "Sostituisci", + "Close (Escape)": "Chiudi (Esc)", + "Replace (Enter)": "Sostituisci (Invio)", + "Replace all (Ctrl+Alt+Enter)": "Sostituisci tutto (Ctrl+Alt+Invio)", + "Replace all": "Sostituisci tutto", + "View all spaces": "Visualizza tutti gli spazi", + "Error": "Errore", + "Failed to disable MFA": "Disabilitazione MFA non riuscita", + "Disable two-factor authentication": "Disabilita autenticazione a due fattori", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Disabilitare l'autenticazione a due fattori renderà il tuo account meno sicuro. Avrai bisogno solo della tua password per accedere.", + "Please enter your password to disable two-factor authentication:": "Inserisci la tua password per disabilitare l'autenticazione a due fattori:", + "Two-factor authentication has been enabled": "Autenticazione a due fattori abilitata", + "Two-factor authentication has been disabled": "Autenticazione a due fattori disabilitata", + "2-step verification": "Verifica in 2 passaggi", + "Protect your account with an additional verification layer when signing in.": "Proteggi il tuo account con un ulteriore livello di verifica durante l'accesso.", + "Two-factor authentication is active on your account.": "L'autenticazione a due fattori è attiva sul tuo account.", + "Add 2FA method": "Aggiungi metodo 2FA", + "Backup codes": "Codici di backup", + "Disable": "Disabilita", + "Invalid verification code": "Codice di verifica non valido", + "New backup codes have been generated": "Nuovi codici di backup generati", + "Failed to regenerate backup codes": "Rigenerazione codici di backup non riuscita", + "About backup codes": "Informazioni sui codici di backup", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "I codici di backup possono essere utilizzati per accedere al tuo account se perdi l'accesso alla tua app di autenticazione. Ogni codice può essere usato solo una volta.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Puoi rigenerare nuovi codici di backup in qualsiasi momento. Questo invaliderà tutti i codici esistenti.", + "Confirm password": "Conferma password", + "Generate new backup codes": "Genera nuovi codici di backup", + "Save your new backup codes": "Salva i tuoi nuovi codici di backup", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Assicurati di salvare questi codici in un luogo sicuro. I tuoi vecchi codici di backup non sono più validi.", + "Your new backup codes": "I tuoi nuovi codici di backup", + "I've saved my backup codes": "Ho salvato i miei codici di backup", + "Failed to setup MFA": "Impostazione MFA non riuscita", + "Setup & Verify": "Imposta e Verifica", + "Add to authenticator": "Aggiungi ad authenticator", + "1. Scan this QR code with your authenticator app": "1. Scansiona questo codice QR con la tua app di autenticazione", + "Can't scan the code?": "Non riesci a scansionare il codice?", + "Enter this code manually in your authenticator app:": "Inserisci questo codice manualmente nella tua app di autenticazione:", + "2. Enter the 6-digit code from your authenticator": "2. Inserisci il codice a 6 cifre dal tuo autenticatore", + "Verify and enable": "Verifica e abilita", + "Failed to generate QR code. Please try again.": "Generazione del codice QR non riuscita. Si prega di riprovare.", + "Backup": "Backup", + "Save codes": "Salva codici", + "Save your backup codes": "Salva i tuoi codici di backup", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Questi codici possono essere utilizzati per accedere al tuo account se perdi l'accesso alla tua app di autenticazione. Ogni codice può essere usato solo una volta.", + "Print": "Stampa", + "Two-factor authentication has been set up. Please log in again.": "L'autenticazione a due fattori è stata impostata. Effettua nuovamente l'accesso, per favore.", + "Two-Factor authentication required": "Autenticazione a due fattori richiesta", + "Your workspace requires two-factor authentication for all users": "Il tuo spazio di lavoro richiede l'autenticazione a due fattori per tutti gli utenti", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Per continuare ad accedere al tuo spazio di lavoro, devi impostare l'autenticazione a due fattori. Questo aggiunge un ulteriore livello di sicurezza al tuo account.", + "Set up two-factor authentication": "Imposta l'autenticazione a due fattori", + "Cancel and logout": "Annulla e disconnetti", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Il tuo spazio di lavoro richiede l'autenticazione a due fattori. Impostala per continuare.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Questo aggiunge un ulteriore livello di sicurezza al tuo account richiedendo un codice di verifica dalla tua app di autenticazione.", + "Password is required": "La password è richiesta", + "Password must be at least 8 characters": "La password deve essere di almeno 8 caratteri", + "Please enter a 6-digit code": "Inserisci un codice a 6 cifre", + "Code must be exactly 6 digits": "Il codice deve essere esattamente di 6 cifre", + "Enter the 6-digit code found in your authenticator app": "Inserisci il codice a 6 cifre trovato nella tua app di autenticazione", + "Need help authenticating?": "Hai bisogno di aiuto per autenticarti?", + "MFA QR Code": "Codice QR MFA", + "Account created successfully. Please log in to set up two-factor authentication.": "Account creato con successo. Effettua l'accesso per impostare l'autenticazione a due fattori.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Reimpostazione della password riuscita. Accedi con la tua nuova password e completa l'autenticazione a due fattori.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Reimpostazione della password riuscita. Accedi con la tua nuova password per impostare l'autenticazione a due fattori.", + "Password reset was successful. Please log in with your new password.": "Reimpostazione della password riuscita. Accedi con la tua nuova password.", + "Two-factor authentication": "Autenticazione a due fattori", + "Use authenticator app instead": "Usa l'app di autenticazione invece", + "Verify backup code": "Verifica codice di backup", + "Use backup code": "Usa codice di backup", + "Enter one of your backup codes": "Inserisci uno dei tuoi codici di backup", + "Backup code": "Codice di backup", + "Enter one of your backup codes. Each backup code can only be used once.": "Inserisci uno dei tuoi codici di backup. Ogni codice di backup può essere utilizzato solo una volta.", + "Verify": "Verifica", + "Trash": "Cestino", + "Pages in trash will be permanently deleted after 30 days.": "Le pagine nel cestino verranno eliminate definitivamente dopo 30 giorni.", + "Deleted": "Eliminato", + "No pages in trash": "Nessuna pagina nel cestino", + "Permanently delete page?": "Eliminare definitivamente la pagina?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Sei sicuro di voler eliminare definitivamente '{{title}}'? Questa azione non può essere annullata.", + "Restore '{{title}}' and its sub-pages?": "Ripristinare '{{title}}' e le sue sottopagine?", + "Move to trash": "Sposta nel cestino", + "Move this page to trash?": "Spostare questa pagina nel cestino?", + "Restore page": "Ripristina pagina", + "Page moved to trash": "Pagina spostata nel cestino", + "Page restored successfully": "Pagina ripristinata con successo", + "Deleted by": "Eliminato da", + "Deleted at": "Eliminato il", + "Preview": "Anteprima", + "Subpages": "Sottopagine", + "Failed to load subpages": "Caricamento delle sottopagine non riuscito", + "No subpages": "Nessuna sottopagina", + "Subpages (Child pages)": "Sottopagine (Pagine figlie)", + "List all subpages of the current page": "Elenca tutte le sottopagine della pagina corrente", + "Attachments": "Allegati", + "All spaces": "Tutti gli spazi", + "Unknown": "Sconosciuto", + "Find a space": "Trova uno spazio", + "Search in all your spaces": "Cerca in tutti i tuoi spazi", + "Type": "Tipo", + "Enterprise": "Impresa", + "Download attachment": "Scarica allegato", + "Allowed email domains": "Domini email consentiti", + "Only users with email addresses from these domains can signup via SSO.": "Solo gli utenti con indirizzi email provenienti da questi domini possono registrarsi tramite SSO.", + "Enter valid domain names separated by comma or space": "Inserisci nomi di dominio validi separati da virgole o spazi", + "Enforce two-factor authentication": "Imponi l'autenticazione a due fattori", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Una volta impostata, tutti i membri devono abilitare l'autenticazione a due fattori per accedere all'area di lavoro.", + "Toggle MFA enforcement": "Attiva disattiva l'applicazione MFA", + "Display name": "Nome visualizzato", + "Allow signup": "Consenti iscrizione", + "Enabled": "Abilitato", + "Advanced Settings": "Impostazioni avanzate", + "Enable TLS/SSL": "Abilita TLS/SSL", + "Use secure connection to LDAP server": "Usa connessione sicura al server LDAP", + "Group sync": "Sincronizzazione gruppi", + "No SSO providers found.": "Nessun provider SSO trovato.", + "Delete SSO provider": "Elimina provider SSO", + "Are you sure you want to delete this SSO provider?": "Sei sicuro di voler eliminare questo provider SSO?", + "Action": "Azione", + "{{ssoProviderType}} configuration": "Configurazione {{ssoProviderType}}", + "Icon": "Icona", + "Upload image": "Carica immagine", + "Remove image": "Rimuovi immagine", + "Failed to remove image": "Rimozione immagine fallita", + "Image exceeds 10MB limit.": "L'immagine supera il limite di 10MB.", + "Image removed successfully": "Immagine rimossa con successo", + "API key": "Chiave API", + "API key created successfully": "Chiave API creata con successo", + "API keys": "Chiavi API", + "API management": "Gestione API", + "Are you sure you want to revoke this API key": "Sei sicuro di voler revocare questa chiave API", + "Create API Key": "Crea Chiave API", + "Custom expiration date": "Data di scadenza personalizzata", + "Enter a descriptive token name": "Inserisci un nome descrittivo del token", + "Expiration": "Scadenza", + "Expired": "Scaduto", + "Expires": "Scade", + "I've saved my API key": "Ho salvato la mia chiave API", + "Last use": "Ultimo utilizzo", + "No API keys found": "Nessuna chiave API trovata", + "No expiration": "Nessuna scadenza", + "Revoke API key": "Revoca chiave API", + "Revoked successfully": "Revocata con successo", + "Select expiration date": "Seleziona la data di scadenza", + "This action cannot be undone. Any applications using this API key will stop working.": "Questa azione non può essere annullata. Qualsiasi applicazione che utilizza questa chiave API smetterà di funzionare.", + "Update API key": "Aggiorna chiave API", + "Manage API keys for all users in the workspace": "Gestisci le chiavi API per tutti gli utenti nell'area di lavoro", + "AI settings": "Impostazioni AI", + "AI search": "Ricerca AI", + "AI Answer": "Risposta AI", + "Ask AI": "Chiedi all'AI", + "AI is thinking...": "L'AI sta pensando...", + "Ask a question...": "Fai una domanda...", + "AI-powered search (Ask AI)": "Ricerca potenziata dall'AI (Chiedi all'AI)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "La ricerca AI utilizza embeddings vettoriali per fornire capacità di ricerca semantica nel contenuto della tua area di lavoro.", + "Toggle AI search": "Attiva/disattiva ricerca AI", + "Sources": "Fonti", + "Ask AI not available for attachments": "Chiedere all'AI non è disponibile per gli allegati", + "No answer available": "Nessuna risposta disponibile", + "Background color": "Colore di sfondo", + "Highlight color": "Colore evidenziato", + "Remove color": "Rimuovi colore" } diff --git a/apps/client/public/locales/ja-JP/translation.json b/apps/client/public/locales/ja-JP/translation.json index 6ab411de..4d18e074 100644 --- a/apps/client/public/locales/ja-JP/translation.json +++ b/apps/client/public/locales/ja-JP/translation.json @@ -13,22 +13,23 @@ "Are you sure you want to remove this user from the group? The user will lose access to resources this group has access to.": "このユーザをグループから削除してもよろしいですか? ユーザはこのグループがアクセス権を持つリソースにアクセスできなくなります。", "Are you sure you want to remove this user from the space? The user will lose all access to this space.": "このユーザをスペースから削除してもよろしいですか? ユーザはこのスペースへのアクセス権をすべて失います。", "Are you sure you want to restore this version? Any changes not versioned will be lost.": "このバージョンを復元してもよろしいですか? バージョン管理されていない変更は失われます。", - "Can become members of groups and spaces in workspace": "ワークスペース内のグループやスペースのメンバーになることができます", - "Can create and edit pages in space.": "スペース内のページを作成および編集できます。", + "Can become members of groups and spaces in workspace": "ワークスペース内のグループやスペースのメンバーになれます", + "Can create and edit pages in space.": "スペース内のページを作成・編集できます", "Can edit": "編集可能", "Can manage workspace": "ワークスペースを管理できます", - "Can manage workspace but cannot delete it": "ワークスペースを管理できますが、削除はできません", + "Can manage workspace but cannot delete it": "ワークスペースを管理できますが削除はできません", "Can view": "閲覧可能", - "Can view pages in space but not edit.": "スペース内のページを閲覧できますが、編集はできません。", + "Can view pages in space but not edit.": "スペース内のページを閲覧できますが編集はできません", "Cancel": "キャンセル", "Change email": "メールアドレスの変更", "Change password": "パスワードの変更", "Change photo": "画像の変更", "Choose a role": "ロールを選んでください", - "Choose your preferred color scheme.": "お好みのカラースキームを選択してください。", - "Choose your preferred interface language.": "お好みのインターフェース言語を選択してください。", - "Choose your preferred page width.": "左右の余白を縮小する場合はオンにしてください。", + "Choose your preferred color scheme.": "お好みのカラースキームを選択してください", + "Choose your preferred interface language.": "お好みの言語を選択してください", + "Choose your preferred page width.": "お好みのページ幅を選択してください", "Confirm": "確認", + "Copy as Markdown": "Markdownとしてコピー", "Copy link": "リンクをコピー", "Create": "新規作成", "Create group": "グループを作成", @@ -40,23 +41,24 @@ "Date": "日付", "Delete": "削除", "Delete group": "グループを削除", - "Are you sure you want to delete this page? This will delete its children and page history. This action is irreversible.": "このページを削除してもよろしいですか?この操作により、子ページおよびページ履歴が削除されます。この操作は元に戻せません。", + "Are you sure you want to delete this page? This will delete its children and page history. This action is irreversible.": "このページを削除してもよろしいですか?子ページとページ履歴も削除されます。この操作は取り消せません。", "Description": "説明", "Details": "詳細", "e.g ACME": "例: 山田太郎", "e.g ACME Inc": "例: 株式会社サンプル", "e.g Developers": "例: エンジニア", - "e.g Group for developers": "例: エンジニアグループ", + "e.g Group for developers": "例: 開発チーム", "e.g product": "例: product", - "e.g Product Team": "例: 製品チーム", - "e.g Sales": "例: 営業", - "e.g Space for product team": "例: 製品チームのスペース", - "e.g Space for sales team to collaborate": "例: 営業チーム連携用スペース", + "e.g Product Team": "例: プロダクトチーム", + "e.g Sales": "例: 営業部", + "e.g Space for product team": "例: プロダクトチーム用スペース", + "e.g Space for sales team to collaborate": "例: 営業チーム用スペース", "Edit": "編集", + "Read": "閲覧", "Edit group": "グループを編集", "Email": "メールアドレス", "Enter a strong password": "強力なパスワードを入力してください", - "Enter valid email addresses separated by comma or space max_50": "有効なメールアドレスをカンマまたはスペースで区切って入力してください(最大 50 個)", + "Enter valid email addresses separated by comma or space max_50": "メールアドレスをカンマまたはスペース区切りで入力(最大50件)", "enter valid emails addresses": "有効なメールアドレスを入力してください", "Enter your current password": "現在のパスワードを入力してください", "enter your full name": "氏名を入力してください", @@ -80,18 +82,18 @@ "Group description": "グループ説明", "Group name": "グループ名", "Groups": "グループ", - "Has full access to space settings and pages.": "スペース設定とページにフルアクセスできます。", + "Has full access to space settings and pages.": "スペース設定とページにフルアクセスできます", "Home": "ホーム", "Import pages": "ページをインポート", "Import pages & space settings": "ページとスペース設定をインポート", "Importing pages": "ページをインポートしています", - "invalid invitation link": "招待リンクが間違っています", + "invalid invitation link": "無効な招待リンクです", "Invitation signup": "招待登録", "Invite by email": "メールアドレスで招待する", "Invite members": "メンバーを招待する", "Invite new members": "新しいメンバーを招待する", - "Invited members who are yet to accept their invitation will appear here.": "招待をまだ承諾していないメンバーはここに表示されます。", - "Invited members will be granted access to spaces the groups can access": "招待されたメンバーは、グループがアクセスできるスペースにアクセス権が付与されます", + "Invited members who are yet to accept their invitation will appear here.": "招待を承諾していないメンバーがここに表示されます", + "Invited members will be granted access to spaces the groups can access": "招待されたメンバーはグループがアクセスできるスペースにアクセスできます", "Join the workspace": "ワークスペースに参加", "Language": "言語", "Light": "ライト", @@ -112,20 +114,20 @@ "New page": "新規ページ", "New password": "新しいパスワード", "No group found": "グループが見つかりません", - "No page history saved yet.": "まだページの履歴が保存されていません。", + "No page history saved yet.": "ページ履歴がありません", "No pages yet": "ページがありません", - "No results found...": "結果が見つかりませんでした...", - "No user found": "ユーザがいません", + "No results found...": "結果が見つかりません", + "No user found": "ユーザーが見つかりません", "Overview": "概要", "Owner": "所有者", "page": "ページ", - "Page deleted successfully": "ページが正常に削除されました", - "Page history": "ページの履歴", - "Page import is in progress. Please do not close this tab.": "ページのインポートが進行中です。このタブを閉じないでください。", + "Page deleted successfully": "ページを削除しました", + "Page history": "ページ履歴", + "Page import is in progress. Please do not close this tab.": "ページをインポート中です。このタブを閉じないでください", "Pages": "ページ", "pages": "ページ", "Password": "パスワード", - "Password changed successfully": "パスワードが正常に変更されました", + "Password changed successfully": "パスワードを変更しました", "Pending": "保留中", "Please confirm your action": "アクションを確認してください", "Preferences": "設定", @@ -142,96 +144,109 @@ "Search for groups": "グループを検索", "Search for users": "ユーザーを検索", "Search for users and groups": "ユーザーとグループを検索", - "Search...": "検索する語句を入力", + "Search...": "検索", "Select language": "言語を選択", "Select role": "ロールを選択", - "Select role to assign to all invited members": "招待されたすべてのメンバーに割り当てるロールを選択してください", + "Select role to assign to all invited members": "招待するメンバーに割り当てるロールを選択", "Select theme": "テーマを選択", "Send invitation": "招待を送る", - "Invitation sent": "招待が送信されました", + "Invitation sent": "招待を送信しました", "Settings": "設定", "Setup workspace": "ワークスペースを設定する", "Sign In": "サインイン", - "Sign Up": "アカウント登録", - "Slug": "Slug (URL用文字列)", + "Sign Up": "新規登録", + "Slug": "スラッグ(URL識別子)", "Space": "スペース", "Space description": "スペース説明", "Space menu": "スペースメニュー", "Space name": "スペース名", "Space settings": "スペース設定", - "Space slug": "スペースのSlug (URL用文字列)", + "Space slug": "スペースのスラッグ(URL識別子)", "Spaces": "スペース", "Spaces you belong to": "所属しているスペース", "No space found": "スペースが見つかりません", "Search for spaces": "スペースを検索", - "Start typing to search...": "検索を開始するには入力してください...", + "Start typing to search...": "入力して検索", "Status": "ステータス", - "Successfully imported": "インポートに成功しました", - "Successfully restored": "正常に復元されました", + "Successfully imported": "インポートしました", + "Successfully restored": "復元しました", "System settings": "システム設定", "Theme": "テーマ", - "To change your email, you have to enter your password and new email.": "メールアドレスを変更するには、パスワードと新しいメールアドレスを入力する必要があります。", - "Toggle full page width": "ページ幅を切り替える", - "Unable to import pages. Please try again.": "ページをインポートできません。もう一度お試しください。", + "To change your email, you have to enter your password and new email.": "メールアドレスを変更するには、パスワードと新しいメールアドレスを入力してください", + "Toggle full page width": "ページ幅を切り替え", + "Unable to import pages. Please try again.": "ページをインポートできませんでした。もう一度お試しください", "untitled": "無題", "Untitled": "無題", - "Updated successfully": "正常に更新されました", + "Updated successfully": "更新しました", "User": "ユーザー", "Workspace": "ワークスペース", "Workspace Name": "ワークスペース名", "Workspace settings": "ワークスペース設定", - "You can change your password here.": "パスワードを変更できます。", + "You can change your password here.": "パスワードを変更できます", "Your Email": "メールアドレス", - "Your import is complete.": "インポートが完了しました。", + "Your import is complete.": "インポートが完了しました", "Your name": "名前", "Your Name": "名前", "Your password": "パスワード", - "Your password must be a minimum of 8 characters.": "パスワードは最低 8 文字必要です。", + "Your password must be a minimum of 8 characters.": "パスワードは8文字以上にしてください", "Sidebar toggle": "サイドバー切り替え", "Comments": "コメント", "404 page not found": "404 ページが見つかりません", - "Sorry, we can't find the page you are looking for.": "お探しのページが見つかりません。", + "Sorry, we can't find the page you are looking for.": "お探しのページが見つかりません", "Take me back to homepage": "ホームに戻る", "Forgot password": "パスワードを忘れた", "Forgot your password?": "パスワードを忘れましたか?", - "A password reset link has been sent to your email. Please check your inbox.": "パスワードリセットリンクがあなたのメールアドレスに送信されました。受信箱を確認してください。", - "Send reset link": "リセットリンクを送る", + "A password reset link has been sent to your email. Please check your inbox.": "パスワードリセット用のリンクをメールに送信しました。受信トレイを確認してください", + "Send reset link": "リセットリンクを送信", "Password reset": "パスワードリセット", "Your new password": "新しいパスワード", "Set password": "パスワードを設定", "Write a comment": "コメントを書く", "Reply...": "返信...", - "Error loading comments.": "コメントの読み込み中にエラーが発生しました。", - "No comments yet.": "コメントがありません。", + "Error loading comments.": "コメントの読み込みに失敗しました", + "No comments yet.": "コメントがありません", "Edit comment": "コメントを編集する", "Delete comment": "コメントを削除する", "Are you sure you want to delete this comment?": "このコメントを削除してもよろしいですか?", - "Comment created successfully": "コメントが作成されました", - "Error creating comment": "コメントの作成中にエラーが発生しました", - "Comment updated successfully": "コメントが更新されました", + "Comment created successfully": "コメントを作成しました", + "Error creating comment": "コメントの作成に失敗しました", + "Comment updated successfully": "コメントを更新しました", "Failed to update comment": "コメントの更新に失敗しました", - "Comment deleted successfully": "コメントが削除されました", + "Comment deleted successfully": "コメントを削除しました", "Failed to delete comment": "コメントの削除に失敗しました", - "Comment resolved successfully": "コメントが解決されました", + "Comment resolved successfully": "コメントを解決しました", + "Comment re-opened successfully": "コメントを再開しました", + "Comment unresolved successfully": "コメントを未解決に戻しました", "Failed to resolve comment": "コメントの解決に失敗しました", + "Resolve comment": "コメントを解決", + "Unresolve comment": "コメントを未解決に戻す", + "Resolve Comment Thread": "コメントスレッドを解決", + "Unresolve Comment Thread": "コメントスレッドを未解決に戻す", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "このコメントスレッドを解決しますか?完了としてマークされます", + "Are you sure you want to unresolve this comment thread?": "このコメントスレッドを未解決に戻しますか?", + "Resolved": "解決済", + "No active comments.": "アクティブなコメントはありません", + "No resolved comments.": "解決済みのコメントはありません", "Revoke invitation": "招待を取り消す", "Revoke": "取り消す", "Don't": "取り消さない", - "Are you sure you want to revoke this invitation? The user will not be able to join the workspace.": "この招待を取り消してもよろしいですか? ユーザはワークスペースに参加できなくなります。", + "Are you sure you want to revoke this invitation? The user will not be able to join the workspace.": "この招待を取り消してもよろしいですか?ユーザーはワークスペースに参加できなくなります", "Resend invitation": "招待を再度送る", - "Anyone with this link can join this workspace.": "このリンクを持っている人は誰でもこのワークスペースに参加できます。", + "Anyone with this link can join this workspace.": "このリンクを知っている人は誰でもワークスペースに参加できます", "Invite link": "招待リンク", "Copy": "コピー", + "Copy to space": "スペースにコピー", "Copied": "コピーしました", + "Duplicate": "複製", "Select a user": "ユーザを選択", "Select a group": "グループを選択", - "Export all pages and attachments in this space.": "このスペースのすべてのページと添付ファイルをエクスポートします。", + "Export all pages and attachments in this space.": "このスペースのすべてのページと添付ファイルをエクスポートします", "Delete space": "スペースを削除", "Are you sure you want to delete this space?": "このスペースを削除してもよろしいですか?", - "Delete this space with all its pages and data.": "このスペースおよびスペース内のすべてのページとデータを削除します。", - "All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "このスペース内のすべてのページ、コメント、添付ファイル、および権限は完全に削除されます。", + "Delete this space with all its pages and data.": "このスペースとすべてのページ、データを削除します", + "All pages, comments, attachments and permissions in this space will be deleted irreversibly.": "スペース内のすべてのページ、コメント、添付ファイル、権限が完全に削除されます", "Confirm space name": "スペース名を確認する", - "Type the space name {{spaceName}} to confirm your action.": "アクションを確認するためにスペース名 {{spaceName}} を入力してください。", + "Type the space name {{spaceName}} to confirm your action.": "確認のためスペース名 {{spaceName}} を入力してください", "Format": "フォーマット", "Include subpages": "サブページを含める", "Include attachments": "添付ファイルを含める", @@ -239,6 +254,7 @@ "Export failed:": "エクスポートに失敗しました:", "export error": "エクスポートエラー", "Export page": "エクスポートページ", + "Export successful": "エクスポート成功", "Export space": "エクスポートスペース", "Export {{type}}": "{{type}}をエクスポート", "File exceeds the {{limit}} attachment limit": "ファイルが{{limit}}の添付制限を超えています", @@ -259,12 +275,12 @@ "Success": "成功", "Warning": "警告", "Danger": "危険", - "Mermaid diagram error:": "Mermaid コードエラー", - "Invalid Mermaid diagram": "無効な Mermaid コードです", - "Double-click to edit Draw.io diagram": "ダブルクリックしてDraw.ioの図を編集", + "Mermaid diagram error:": "Mermaid ダイアグラムエラー:", + "Invalid Mermaid diagram": "無効な Mermaid ダイアグラムです", + "Double-click to edit Draw.io diagram": "ダブルクリックして Draw.io 図を編集", "Exit": "終了", "Save & Exit": "保存して終了", - "Double-click to edit Excalidraw diagram": "ダブルクリックしてExcalidraw図を編集", + "Double-click to edit Excalidraw diagram": "ダブルクリックして Excalidraw 図を編集", "Paste link": "リンクを貼り付け", "Edit link": "リンクを編集", "Remove link": "リンクを削除", @@ -301,22 +317,24 @@ "Bullet List": "箇条書きリスト", "Numbered List": "番号付きリスト", "Blockquote": "引用", - "Just start typing with plain text.": "すぐに文章を書き始められます。", - "Track tasks with a to-do list.": "Todoリストでタスクを追跡します。", - "Big section heading.": "大きいフォントのセクション見出しです。", - "Medium section heading.": "中くらいのフォントのセクション見出しです。", - "Small section heading.": "小さいフォントのセクション見出しです。", - "Create a simple bullet list.": "シンプルな箇条書きのリストを作成します。", - "Create a list with numbering.": "番号付きのリストを作成します。", - "Create block quote.": "引用文を作成します。", - "Insert code snippet.": "コードスニペットを入力します。", - "Insert horizontal rule divider": "水平線を挿入します。", - "Upload any image from your device.": "画像をアップロードします。", - "Upload any video from your device.": "動画をアップロードします。", - "Upload any file from your device.": "ファイルをアップロードします。", + "Just start typing with plain text.": "プレーンテキストを入力します", + "Track tasks with a to-do list.": "Todo リストでタスクを管理します", + "Big section heading.": "大見出し", + "Medium section heading.": "中見出し", + "Small section heading.": "小見出し", + "Create a simple bullet list.": "箇条書きリストを作成します", + "Create a list with numbering.": "番号付きリストを作成します", + "Create block quote.": "引用ブロックを作成します", + "Insert code snippet.": "コードスニペットを挿入します", + "Insert horizontal rule divider": "区切り線を挿入します", + "Upload any image from your device.": "デバイスから画像をアップロードします", + "Upload any video from your device.": "デバイスから動画をアップロードします", + "Upload any file from your device.": "デバイスからファイルをアップロードします", + "Uploading {{name}}": "{{name}} をアップロード中", + "Uploading file": "ファイルをアップロード中", "Table": "テーブル", - "Insert a table.": "表を挿入します。", - "Insert collapsible block.": "折りたたみ可能なブロックを挿入します。", + "Insert a table.": "テーブルを挿入します", + "Insert collapsible block.": "折りたたみブロックを挿入します", "Video": "動画", "Divider": "区切り線", "Quote": "引用", @@ -324,16 +342,16 @@ "File attachment": "ファイル添付", "Toggle block": "ブロックを切り替える", "Callout": "コールアウト", - "Insert callout notice.": "コールアウトブロックを挿入します。", + "Insert callout notice.": "コールアウトを挿入します", "Math inline": "インライン数式", - "Insert inline math equation.": "インライン数式を挿入します。", + "Insert inline math equation.": "インライン数式を挿入します", "Math block": "数式ブロック", "Insert math equation": "数式を挿入します", - "Mermaid diagram": "Mermaidコード", - "Insert mermaid diagram": "Mermaidコードを記述して図を挿入します", - "Insert and design Drawio diagrams": "Drawioの図を挿入してデザインします", - "Insert current date": "今日の日付を挿入します", - "Draw and sketch excalidraw diagrams": "Excalidrawの図を埋め込みます", + "Mermaid diagram": "Mermaid ダイアグラム", + "Insert mermaid diagram": "Mermaid ダイアグラムを挿入します", + "Insert and design Drawio diagrams": "Draw.io 図を挿入・編集します", + "Insert current date": "現在の日付を挿入します", + "Draw and sketch excalidraw diagrams": "Excalidraw 図を挿入します", "Multiple": "複数", "Heading {{level}}": "見出し {{level}}", "Toggle title": "タイトルの表示/非表示を切り替える", @@ -343,26 +361,29 @@ "Yesterday, {{time}}": "昨日、{{time}}", "Space created successfully": "スペースを作成しました", "Space updated successfully": "スペースを更新しました", - "Space deleted successfully": "スペースが削除されました", + "Space deleted successfully": "スペースを削除しました", "Members added successfully": "メンバーを追加しました", - "Member removed successfully": "メンバーが削除されました", + "Member removed successfully": "メンバーを削除しました", "Member role updated successfully": "メンバーのロールを更新しました", "Created by: {{creatorName}}": "作成者: {{creatorName}}", - "Created at: {{time}}": "が作成しました:{{time}}", + "Created at: {{time}}": "作成日: {{time}}", "Edited by {{name}} {{time}}": "最終編集: {{name}} {{time}}", - "Word count: {{wordCount}}": "ワード数: {{wordCount}}", + "Word count: {{wordCount}}": "単語数: {{wordCount}}", "Character count: {{characterCount}}": "文字数: {{characterCount}}", "New update": "新規更新", - "{{latestVersion}} is available": "{{latestVersion}}は利用可能です", + "{{latestVersion}} is available": "{{latestVersion}} が利用可能です", + "Default page edit mode": "デフォルトのページ編集モード", + "Choose your preferred page edit mode. Avoid accidental edits.": "お好みのページ編集モードを選択してください(誤編集を防止します)", + "Reading": "読み取り", "Delete member": "メンバーを削除する", - "Member deleted successfully": "メンバーが削除されました", - "Are you sure you want to delete this workspace member? This action is irreversible.": "ワークスペースメンバーを削除してもよろしいですか?この操作は元に戻せません。", + "Member deleted successfully": "メンバーを削除しました", + "Are you sure you want to delete this workspace member? This action is irreversible.": "このメンバーを削除してもよろしいですか?この操作は取り消せません", "Move": "移動", "Move page": "ページを移動", - "Move page to a different space.": "ページを別のスペースに移動します。", - "Real-time editor connection lost. Retrying...": "リアルタイムエディターの接続が失われました。再試行しています…", + "Move page to a different space.": "ページを別のスペースに移動します", + "Real-time editor connection lost. Retrying...": "リアルタイム編集の接続が切断されました。再接続中...", "Table of contents": "目次", - "Add headings (H1, H2, H3) to generate a table of contents.": "見出し(H1、H2、H3)を追加して目次を生成します。", + "Add headings (H1, H2, H3) to generate a table of contents.": "見出し(H1、H2、H3)を追加すると目次が生成されます", "Share": "共有", "Public sharing": "公開共有", "Shared by": "共有者", @@ -381,10 +402,176 @@ "Delete share": "共有を削除", "Are you sure you want to delete this shared link?": "この共有リンクを削除してもよろしいですか?", "Publicly shared pages from spaces you are a member of will appear here": "メンバーであるスペースからの公開ページがここに表示されます", - "Share deleted successfully": "共有が正常に削除されました", + "Share deleted successfully": "共有を削除しました", "Share not found": "共有が見つかりません", "Failed to share page": "ページの共有に失敗しました", "Copy page": "ページをコピー", - "Copy page to a different space.": "ページを別のスペースにコピーします。", - "Page copied successfully": "ページのコピーに成功しました" + "Copy page to a different space.": "ページを別のスペースにコピーします", + "Page copied successfully": "ページをコピーしました", + "Page duplicated successfully": "ページを複製しました", + "Find": "検索", + "Not found": "見つかりません", + "Previous Match (Shift+Enter)": "前の一致 (Shift+Enter)", + "Next match (Enter)": "次の一致 (Enter)", + "Match case (Alt+C)": "大文字小文字を区別 (Alt+C)", + "Replace": "置換", + "Close (Escape)": "閉じる (Escape)", + "Replace (Enter)": "置換 (Enter)", + "Replace all (Ctrl+Alt+Enter)": "すべて置換 (Ctrl+Alt+Enter)", + "Replace all": "すべて置換", + "View all spaces": "すべてのスペースを表示", + "Error": "エラー", + "Failed to disable MFA": "MFAの無効化に失敗しました", + "Disable two-factor authentication": "二要素認証を無効化", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "二要素認証を無効にすると、アカウントのセキュリティが低下します。サインインにはパスワードのみが必要になります", + "Please enter your password to disable two-factor authentication:": "二要素認証を無効にするにはパスワードを入力してください", + "Two-factor authentication has been enabled": "二要素認証を有効にしました", + "Two-factor authentication has been disabled": "二要素認証を無効にしました", + "2-step verification": "2段階認証", + "Protect your account with an additional verification layer when signing in.": "サインイン時に追加の認証でアカウントを保護します", + "Two-factor authentication is active on your account.": "二要素認証が有効です", + "Add 2FA method": "2FAメソッドを追加", + "Backup codes": "バックアップコード", + "Disable": "無効にする", + "Invalid verification code": "無効な認証コード", + "New backup codes have been generated": "新しいバックアップコードを生成しました", + "Failed to regenerate backup codes": "バックアップコードの再生成に失敗しました", + "About backup codes": "バックアップコードについて", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "認証アプリにアクセスできない場合、バックアップコードでアカウントにアクセスできます。各コードは1回のみ使用可能です", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "新しいバックアップコードはいつでも再生成できます。既存のコードはすべて無効になります", + "Confirm password": "パスワードを確認", + "Generate new backup codes": "新しいバックアップコードを生成", + "Save your new backup codes": "新しいバックアップコードを保存", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "これらのコードを安全な場所に保存してください。古いバックアップコードは無効になりました", + "Your new backup codes": "新しいバックアップコード", + "I've saved my backup codes": "バックアップコードを保存しました", + "Failed to setup MFA": "MFAの設定に失敗しました", + "Setup & Verify": "設定と確認", + "Add to authenticator": "認証アプリに追加", + "1. Scan this QR code with your authenticator app": "1. このQRコードを認証アプリでスキャンしてください", + "Can't scan the code?": "コードをスキャンできませんか?", + "Enter this code manually in your authenticator app:": "このコードを認証アプリに手動で入力してください:", + "2. Enter the 6-digit code from your authenticator": "2. 認証アプリからの6桁のコードを入力してください", + "Verify and enable": "確認と有効化", + "Failed to generate QR code. Please try again.": "QRコードの生成に失敗しました。もう一度お試しください", + "Backup": "バックアップ", + "Save codes": "コードを保存", + "Save your backup codes": "バックアップコードを保存", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "認証アプリにアクセスできない場合、これらのコードでアカウントにアクセスできます。各コードは1回のみ使用可能です", + "Print": "印刷", + "Two-factor authentication has been set up. Please log in again.": "二要素認証を設定しました。再度ログインしてください", + "Two-Factor authentication required": "二要素認証が必要です", + "Your workspace requires two-factor authentication for all users": "このワークスペースではすべてのユーザーに二要素認証が必要です", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "ワークスペースにアクセスするには二要素認証を設定してください。アカウントのセキュリティが強化されます", + "Set up two-factor authentication": "二要素認証を設定", + "Cancel and logout": "キャンセルしてログアウト", + "Your workspace requires two-factor authentication. Please set it up to continue.": "このワークスペースでは二要素認証が必要です。続行するには設定してください", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "認証アプリからの確認コードでアカウントのセキュリティが強化されます", + "Password is required": "パスワードが必要です", + "Password must be at least 8 characters": "パスワードは8文字以上必要です", + "Please enter a 6-digit code": "6桁のコードを入力してください", + "Code must be exactly 6 digits": "コードは6桁で入力してください", + "Enter the 6-digit code found in your authenticator app": "認証アプリに表示された6桁のコードを入力してください", + "Need help authenticating?": "認証に関するヘルプが必要ですか?", + "MFA QR Code": "MFA QRコード", + "Account created successfully. Please log in to set up two-factor authentication.": "アカウントを作成しました。二要素認証を設定するためにログインしてください", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "パスワードをリセットしました。新しいパスワードでログインして二要素認証を完了してください", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "パスワードをリセットしました。新しいパスワードでログインして二要素認証を設定してください", + "Password reset was successful. Please log in with your new password.": "パスワードをリセットしました。新しいパスワードでログインしてください", + "Two-factor authentication": "二要素認証", + "Use authenticator app instead": "代わりに認証アプリを使用", + "Verify backup code": "バックアップコードを確認", + "Use backup code": "バックアップコードを使用", + "Enter one of your backup codes": "バックアップコードのいずれかを入力してください", + "Backup code": "バックアップコード", + "Enter one of your backup codes. Each backup code can only be used once.": "バックアップコードを入力してください。各コードは1回のみ使用可能です", + "Verify": "確認", + "Trash": "ごみ箱", + "Pages in trash will be permanently deleted after 30 days.": "ごみ箱内のページは30日後に完全に削除されます", + "Deleted": "削除", + "No pages in trash": "ごみ箱にページがありません", + "Permanently delete page?": "ページを完全に削除しますか?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "「{{title}}」を完全に削除しますか?この操作は取り消せません", + "Restore '{{title}}' and its sub-pages?": "「{{title}}」とそのサブページを復元しますか?", + "Move to trash": "ごみ箱に移動", + "Move this page to trash?": "このページをごみ箱に移動しますか?", + "Restore page": "ページを復元", + "Page moved to trash": "ページをごみ箱に移動しました", + "Page restored successfully": "ページを復元しました", + "Deleted by": "削除者", + "Deleted at": "削除日時", + "Preview": "プレビュー", + "Subpages": "サブページ", + "Failed to load subpages": "サブページの読み込みに失敗しました", + "No subpages": "サブページがありません", + "Subpages (Child pages)": "サブページ(子ページ)", + "List all subpages of the current page": "現在のページのすべてのサブページをリスト", + "Attachments": "添付ファイル", + "All spaces": "すべてのスペース", + "Unknown": "不明", + "Find a space": "スペースを探す", + "Search in all your spaces": "あなたのすべてのスペースで検索", + "Type": "タイプ", + "Enterprise": "エンタープライズ", + "Download attachment": "添付ファイルをダウンロード", + "Allowed email domains": "許可されたメールドメイン", + "Only users with email addresses from these domains can signup via SSO.": "これらのドメインのメールアドレスを持つユーザーのみSSO経由で登録できます", + "Enter valid domain names separated by comma or space": "コンマまたはスペースで区切って有効なドメイン名を入力してください", + "Enforce two-factor authentication": "二要素認証を強制する", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "有効にすると、すべてのメンバーが二要素認証を設定しないとワークスペースにアクセスできなくなります", + "Toggle MFA enforcement": "MFAの強制を切り替える", + "Display name": "表示名", + "Allow signup": "登録を許可する", + "Enabled": "有効", + "Advanced Settings": "詳細設定", + "Enable TLS/SSL": "TLS/SSLを有効にする", + "Use secure connection to LDAP server": "LDAPサーバーへの安全な接続を使用する", + "Group sync": "グループ同期", + "No SSO providers found.": "SSOプロバイダーが見つかりませんでした。", + "Delete SSO provider": "SSOプロバイダーを削除する", + "Are you sure you want to delete this SSO provider?": "このSSOプロバイダーを削除してもよろしいですか?", + "Action": "アクション", + "{{ssoProviderType}} configuration": "{{ssoProviderType}}の構成", + "Icon": "アイコン", + "Upload image": "画像をアップロード", + "Remove image": "画像を削除", + "Failed to remove image": "画像の削除に失敗しました", + "Image exceeds 10MB limit.": "画像が10MBの制限を超えています", + "Image removed successfully": "画像を削除しました", + "API key": "APIキー", + "API key created successfully": "APIキーを作成しました", + "API keys": "APIキー", + "API management": "API管理", + "Are you sure you want to revoke this API key": "このAPIキーを無効にしてもよろしいですか", + "Create API Key": "APIキーを作成", + "Custom expiration date": "カスタム有効期限", + "Enter a descriptive token name": "説明的なトークン名を入力してください", + "Expiration": "有効期限", + "Expired": "期限切れ", + "Expires": "期限が切れます", + "I've saved my API key": "APIキーを保存しました", + "Last use": "最終使用", + "No API keys found": "APIキーが見つかりません", + "No expiration": "期限なし", + "Revoke API key": "APIキーを無効にする", + "Revoked successfully": "無効にしました", + "Select expiration date": "有効期限を選択してください", + "This action cannot be undone. Any applications using this API key will stop working.": "この操作は取り消せません。このAPIキーを使用しているアプリケーションは動作しなくなります", + "Update API key": "APIキーを更新", + "Manage API keys for all users in the workspace": "ワークスペース内のすべてのユーザーのAPIキーを管理", + "AI settings": "AI設定", + "AI search": "AI検索", + "AI Answer": "AI回答", + "Ask AI": "AIに質問する", + "AI is thinking...": "AIが考え中...", + "Ask a question...": "質問を入力...", + "AI-powered search (Ask AI)": "AIによる検索(AIに質問)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI検索はベクター埋め込みを使用してワークスペース全体の意味検索を実現します", + "Toggle AI search": "AI検索を切り替え", + "Sources": "ソース", + "Ask AI not available for attachments": "添付ファイルにはAI質問は利用できません", + "No answer available": "回答がありません", + "Background color": "背景色", + "Highlight color": "ハイライト色", + "Remove color": "色を削除" } diff --git a/apps/client/public/locales/ko-KR/translation.json b/apps/client/public/locales/ko-KR/translation.json index 45a79e92..d9b48b04 100644 --- a/apps/client/public/locales/ko-KR/translation.json +++ b/apps/client/public/locales/ko-KR/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "선호하는 인터페이스 언어를 선택하세요.", "Choose your preferred page width.": "선호하는 페이지 너비를 선택하세요.", "Confirm": "확인", + "Copy as Markdown": "Markdown으로 복사", "Copy link": "링크 복사", "Create": "생성", "Create group": "팀 생성", @@ -53,6 +54,7 @@ "e.g Space for product team": "예: 제품 팀을 위한 Space", "e.g Space for sales team to collaborate": "예: 영업 팀의 Space", "Edit": "편집", + "Read": "읽기", "Edit group": "팀 편집", "Email": "이메일", "Enter a strong password": "강력한 비밀번호를 입력하세요", @@ -213,7 +215,18 @@ "Comment deleted successfully": "댓글 삭제 완료", "Failed to delete comment": "댓글 삭제 실패", "Comment resolved successfully": "댓글 처리 완료", + "Comment re-opened successfully": "댓글이 성공적으로 다시 열렸습니다", + "Comment unresolved successfully": "댓글 미해결로 변경 완료", "Failed to resolve comment": "댓글 처리 실패", + "Resolve comment": "댓글 해결하기", + "Unresolve comment": "댓글 미해결로 변경하기", + "Resolve Comment Thread": "댓글 스레드 해결하기", + "Unresolve Comment Thread": "댓글 스레드 미해결로 변경하기", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "이 댓글 스레드를 해결하시겠습니까? 완료로 표시됩니다.", + "Are you sure you want to unresolve this comment thread?": "이 댓글 스레드를 미해결로 변경하시겠습니까?", + "Resolved": "해결됨", + "No active comments.": "활성 댓글이 없습니다.", + "No resolved comments.": "해결된 댓글이 없습니다.", "Revoke invitation": "초대 취소", "Revoke": "취소", "Don't": "하지 않음", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "이 링크를 가진 모든 사용자가 이 Workspace에 참여할 수 있습니다.", "Invite link": "초대 링크", "Copy": "복사", + "Copy to space": "공간에 복사하기", "Copied": "복사됨", + "Duplicate": "중복", "Select a user": "사용자 선택", "Select a group": "팀 선택", "Export all pages and attachments in this space.": "이 Space의 모든 페이지와 첨부파일을 내보냅니다.", @@ -239,6 +254,7 @@ "Export failed:": "내보내기 실패:", "export error": "내보내기 오류", "Export page": "페이지 내보내기", + "Export successful": "내보내기 성공", "Export space": "Space 내보내기", "Export {{type}}": "{{type}} 내보내기", "File exceeds the {{limit}} attachment limit": "첨부 파일 크기 제한 {{limit}}을 초과했습니다", @@ -314,6 +330,8 @@ "Upload any image from your device.": "기기에서 이미지를 업로드하세요.", "Upload any video from your device.": "기기에서 비디오를 업로드하세요.", "Upload any file from your device.": "기기에서 파일을 업로드하세요.", + "Uploading {{name}}": "{{name}} 업로드 중", + "Uploading file": "파일 업로드 중", "Table": "테이블", "Insert a table.": "테이블 삽입.", "Insert collapsible block.": "접을 수 있는 블록 삽입.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "문자 수: {{characterCount}}", "New update": "새로운 업데이트", "{{latestVersion}} is available": "{{latestVersion}}이 사용 가능합니다", + "Default page edit mode": "기본 페이지 편집 모드", + "Choose your preferred page edit mode. Avoid accidental edits.": "선호하는 페이지 편집 모드를 선택하세요. 실수로 인한 편집을 방지하세요.", + "Reading": "읽기", "Delete member": "회원 삭제", "Member deleted successfully": "멤버가 성공적으로 제거되었습니다", "Are you sure you want to delete this workspace member? This action is irreversible.": "이 워크스페이스 멤버를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", @@ -384,7 +405,173 @@ "Share deleted successfully": "공유가 성공적으로 삭제되었습니다", "Share not found": "공유를 찾을 수 없습니다", "Failed to share page": "페이지 공유에 실패했습니다", - "Copy page": "Copy page", - "Copy page to a different space.": "Copy page to a different space.", - "Page copied successfully": "Page copied successfully" + "Copy page": "페이지 복사하기", + "Copy page to a different space.": "다른 공간으로 페이지 복사하기.", + "Page copied successfully": "페이지가 성공적으로 복사되었습니다", + "Page duplicated successfully": "페이지가 성공적으로 복제되었습니다", + "Find": "찾기", + "Not found": "찾을 수 없음", + "Previous Match (Shift+Enter)": "이전 일치 항목 (Shift+Enter)", + "Next match (Enter)": "다음 일치 항목 (Enter)", + "Match case (Alt+C)": "대소문자 구분 (Alt+C)", + "Replace": "교체", + "Close (Escape)": "닫기 (Escape)", + "Replace (Enter)": "교체 (Enter)", + "Replace all (Ctrl+Alt+Enter)": "모두 교체하기 (Ctrl+Alt+Enter)", + "Replace all": "모두 교체하기", + "View all spaces": "모든 공간 보기", + "Error": "오류", + "Failed to disable MFA": "MFA 비활성화 실패", + "Disable two-factor authentication": "이중 인증 비활성화", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "이중 인증을 비활성화하면 계정의 보안이 낮아집니다. 로그인 시 비밀번호만 필요하게 됩니다.", + "Please enter your password to disable two-factor authentication:": "이중 인증 비활성화를 위해 비밀번호를 입력하세요:", + "Two-factor authentication has been enabled": "이중 인증이 활성화되었습니다", + "Two-factor authentication has been disabled": "이중 인증이 비활성화되었습니다", + "2-step verification": "2단계 인증", + "Protect your account with an additional verification layer when signing in.": "로그인 시 추가 인증 단계를 통해 계정을 보호하세요.", + "Two-factor authentication is active on your account.": "이중 인증이 계정에 활성화되어 있습니다.", + "Add 2FA method": "2FA 방법 추가", + "Backup codes": "백업 코드", + "Disable": "비활성화", + "Invalid verification code": "유효하지 않은 인증 코드", + "New backup codes have been generated": "새 백업 코드가 생성되었습니다", + "Failed to regenerate backup codes": "백업 코드 재생성 실패", + "About backup codes": "백업 코드에 대하여", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "인증 앱에 접근할 수 없게 된 경우, 백업 코드를 사용하여 계정에 접근할 수 있습니다. 각 코드는 한 번만 사용할 수 있습니다.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "언제든지 새 백업 코드를 재생성할 수 있습니다. 이 작업은 기존 모든 코드를 무효화합니다.", + "Confirm password": "비밀번호 확인", + "Generate new backup codes": "새 백업 코드 생성하기", + "Save your new backup codes": "새 백업 코드 저장하기", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "이 코드를 안전한 장소에 저장하세요. 이전 백업 코드는 더 이상 유효하지 않습니다.", + "Your new backup codes": "새 백업 코드", + "I've saved my backup codes": "백업 코드를 저장했습니다", + "Failed to setup MFA": "MFA 설정 실패", + "Setup & Verify": "설정 및 확인", + "Add to authenticator": "인증앱에 추가", + "1. Scan this QR code with your authenticator app": "1. 인증앱으로 이 QR 코드를 스캔하십시오.", + "Can't scan the code?": "코드를 스캔할 수 없습니까?", + "Enter this code manually in your authenticator app:": "이 코드를 인증앱에 수동으로 입력해 주세요:", + "2. Enter the 6-digit code from your authenticator": "2. 인증앱에서 6자리 코드를 입력하십시오", + "Verify and enable": "확인 및 활성화", + "Failed to generate QR code. Please try again.": "QR 코드 생성 실패. 다시 시도해 주세요.", + "Backup": "백업", + "Save codes": "코드 저장", + "Save your backup codes": "백업 코드 저장하기", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "인증 앱에 대한 접근 권한을 잃은 경우, 이 코드를 사용하여 귀하의 계정에 접근할 수 있습니다. 각 코드는 한 번만 사용할 수 있습니다.", + "Print": "인쇄", + "Two-factor authentication has been set up. Please log in again.": "이중 인증이 설정되었습니다. 다시 로그인해 주세요.", + "Two-Factor authentication required": "이중 인증 필요", + "Your workspace requires two-factor authentication for all users": "워크스페이스에서는 모든 사용자에게 이중 인증이 필요합니다.", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "워크스페이스 접근을 계속하려면 이중 인증을 설정해야 합니다. 이는 계정에 추가 보안 계층을 추가합니다.", + "Set up two-factor authentication": "이중 인증 설정하기", + "Cancel and logout": "취소 및 로그아웃", + "Your workspace requires two-factor authentication. Please set it up to continue.": "워크스페이스에서는 이중 인증이 필요합니다. 계속하려면 설정해 주세요.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "인증앱에서 얻은 인증 코드를 요구하여 계정의 보안에 추가적인 계층을 추가합니다.", + "Password is required": "비밀번호가 필요합니다", + "Password must be at least 8 characters": "비밀번호는 최소 8자 이상이어야 합니다", + "Please enter a 6-digit code": "6자리 코드를 입력해 주세요", + "Code must be exactly 6 digits": "코드는 정확히 6자리여야 합니다", + "Enter the 6-digit code found in your authenticator app": "인증앱에서 찾은 6자리 코드를 입력하십시오", + "Need help authenticating?": "인증에 도움이 필요하십니까?", + "MFA QR Code": "MFA QR 코드", + "Account created successfully. Please log in to set up two-factor authentication.": "계정이 성공적으로 생성되었습니다. 이중 인증을 설정하려면 로그인해 주세요.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "비밀번호 재설정 성공. 새 비밀번호로 로그인하여 이중 인증을 완료하세요.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "비밀번호 재설정 성공. 새 비밀번호로 로그인하여 이중 인증을 설정하세요.", + "Password reset was successful. Please log in with your new password.": "비밀번호 재설정이 성공적으로 완료되었습니다. 새 비밀번호로 로그인하세요.", + "Two-factor authentication": "이중 인증", + "Use authenticator app instead": "대신 인증 앱 사용", + "Verify backup code": "백업 코드 확인", + "Use backup code": "백업 코드 사용", + "Enter one of your backup codes": "백업 코드 중 하나를 입력하세요", + "Backup code": "백업 코드", + "Enter one of your backup codes. Each backup code can only be used once.": "백업 코드 중 하나를 입력하세요. 각 백업 코드는 한 번만 사용할 수 있습니다.", + "Verify": "확인", + "Trash": "휴지통", + "Pages in trash will be permanently deleted after 30 days.": "휴지통의 페이지는 30일 후에 영구적으로 삭제됩니다.", + "Deleted": "삭제됨", + "No pages in trash": "휴지통에 페이지가 없습니다", + "Permanently delete page?": "페이지를 영구적으로 삭제하시겠습니까?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "'{{title}}'을(를) 영구적으로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "Restore '{{title}}' and its sub-pages?": "'{{title}}' 및 하위 페이지를 복구하시겠습니까?", + "Move to trash": "휴지통으로 이동", + "Move this page to trash?": "이 페이지를 휴지통으로 이동하시겠습니까?", + "Restore page": "페이지 복구", + "Page moved to trash": "페이지가 휴지통으로 이동되었습니다", + "Page restored successfully": "페이지가 성공적으로 복구되었습니다", + "Deleted by": "삭제자", + "Deleted at": "삭제 시간", + "Preview": "미리보기", + "Subpages": "하위 페이지", + "Failed to load subpages": "하위 페이지 로드 실패", + "No subpages": "하위 페이지 없음", + "Subpages (Child pages)": "하위 페이지 (자식 페이지)", + "List all subpages of the current page": "현재 페이지의 모든 하위 페이지 목록", + "Attachments": "첨부 파일", + "All spaces": "전체 공간", + "Unknown": "알 수 없음", + "Find a space": "공간 찾기", + "Search in all your spaces": "모든 공간에서 검색", + "Type": "유형", + "Enterprise": "기업", + "Download attachment": "첨부 파일 다운로드", + "Allowed email domains": "허용된 이메일 도메인", + "Only users with email addresses from these domains can signup via SSO.": "이 도메인의 이메일 주소를 가진 사용자만 SSO를 통해 가입할 수 있습니다.", + "Enter valid domain names separated by comma or space": "콤마 또는 공백으로 구분하여 유효한 도메인 이름 입력", + "Enforce two-factor authentication": "이중 인증 시행", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "시행되면 모든 멤버가 작업 공간에 액세스하기 위해 이중 인증을 활성화해야 합니다.", + "Toggle MFA enforcement": "MFA 시행 전환", + "Display name": "표시 이름", + "Allow signup": "가입 허용", + "Enabled": "활성화됨", + "Advanced Settings": "고급 설정", + "Enable TLS/SSL": "TLS\\/SSL 활성화", + "Use secure connection to LDAP server": "LDAP 서버에 안전한 연결 사용", + "Group sync": "그룹 동기화", + "No SSO providers found.": "SSO 제공자를 찾을 수 없습니다.", + "Delete SSO provider": "SSO 제공자 삭제", + "Are you sure you want to delete this SSO provider?": "이 SSO 제공자를 삭제하시겠습니까?", + "Action": "작업", + "{{ssoProviderType}} configuration": "{{ssoProviderType}} 구성", + "Icon": "아이콘", + "Upload image": "이미지 업로드", + "Remove image": "이미지 제거", + "Failed to remove image": "이미지 제거 실패", + "Image exceeds 10MB limit.": "이미지가 10MB 용량 제한을 초과합니다.", + "Image removed successfully": "이미지가 성공적으로 제거되었습니다", + "API key": "API 키", + "API key created successfully": "API 키 생성 완료", + "API keys": "API 키", + "API management": "API 관리", + "Are you sure you want to revoke this API key": "이 API 키를 취소하시겠습니까?", + "Create API Key": "API 키 생성", + "Custom expiration date": "사용자 정의 만료일", + "Enter a descriptive token name": "토큰 이름을 입력하세요", + "Expiration": "만료", + "Expired": "만료됨", + "Expires": "만료일", + "I've saved my API key": "API 키를 저장했습니다", + "Last use": "최근 사용", + "No API keys found": "API 키를 찾을 수 없습니다", + "No expiration": "유효기간 없음", + "Revoke API key": "API 키 취소", + "Revoked successfully": "성공적으로 취소되었습니다", + "Select expiration date": "만료일 선택", + "This action cannot be undone. Any applications using this API key will stop working.": "이 작업은 되돌릴 수 없습니다. 이 API 키를 사용하는 모든 응용 프로그램이 작동을 멈출 것입니다.", + "Update API key": "API 키 갱신", + "Manage API keys for all users in the workspace": "워크스페이스 내 모든 사용자의 API 키 관리", + "AI settings": "AI 설정", + "AI search": "AI 검색", + "AI Answer": "AI 답변", + "Ask AI": "AI에게 묻기", + "AI is thinking...": "AI가 생각 중입니다...", + "Ask a question...": "질문하세요...", + "AI-powered search (Ask AI)": "AI 지원 검색 (AI에게 묻기)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI 검색은 벡터 임베딩을 사용하여 작업공간 콘텐츠에 대한 의미 검색 기능을 제공합니다.", + "Toggle AI search": "AI 검색 전환", + "Sources": "출처", + "Ask AI not available for attachments": "AI에게 묻기 기능은 첨부 파일에 대해 사용할 수 없습니다", + "No answer available": "답변을 제공할 수 없습니다", + "Background color": "배경 색", + "Highlight color": "강조 색", + "Remove color": "색 제거" } diff --git a/apps/client/public/locales/nl-NL/translation.json b/apps/client/public/locales/nl-NL/translation.json index 0ba3631a..a7923b98 100644 --- a/apps/client/public/locales/nl-NL/translation.json +++ b/apps/client/public/locales/nl-NL/translation.json @@ -29,12 +29,13 @@ "Choose your preferred interface language.": "Kies uw gewenste interfacetaal.", "Choose your preferred page width.": "Kies uw gewenste paginabreedte.", "Confirm": "Bevestig", + "Copy as Markdown": "Kopiëren als Markdown", "Copy link": "Link kopiëren", "Create": "Aanmaken", "Create group": "Groep aanmaken", "Create page": "Pagina aanmaken", "Create space": "Ruimte aanmaken", - "Create workspace": "Wwerkruimte aanmaken", + "Create workspace": "Werkruimte aanmaken", "Current password": "Huidig wachtwoord", "Dark": "Donker", "Date": "Datum", @@ -53,6 +54,7 @@ "e.g Space for product team": "bijv. Ruimte voor productteam", "e.g Space for sales team to collaborate": "bijv. Ruimte voor verkoopteam om samen te werken", "Edit": "Bewerken", + "Read": "Lezen", "Edit group": "Groep bewerken", "Email": "E-mailadres", "Enter a strong password": "Voer een sterk wachtwoord in", @@ -90,7 +92,7 @@ "Invite by email": "Uitnodigen via e-mail", "Invite members": "Leden uitnodigen", "Invite new members": "Nieuwe leden uitnodigen", - "Invited members who are yet to accept their invitation will appear here.": "Uigenodigde leden die hun uitnodiging nog moeten accepteren zullen hier worden getoond.", + "Invited members who are yet to accept their invitation will appear here.": "Uitgenodigde leden die hun uitnodiging nog moeten accepteren zullen hier worden getoond.", "Invited members will be granted access to spaces the groups can access": "Uitgenodigde leden wordt toegang gegeven tot ruimtes de groepen toegang toe heeft", "Join the workspace": "Word lid van de werkruimte", "Language": "Taal", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Reactie met succes verwijderd", "Failed to delete comment": "Verwijderen van reactie mislukt", "Comment resolved successfully": "Reactie succesvol opgelost", + "Comment re-opened successfully": "Reactie succesvol heropend", + "Comment unresolved successfully": "Reactie succesvol niet-opgelost gemaakt", "Failed to resolve comment": "Reactie oplossen mislukt", + "Resolve comment": "Reactie oplossen", + "Unresolve comment": "Reactie niet oplossen", + "Resolve Comment Thread": "Reactiedraad oplossen", + "Unresolve Comment Thread": "Reactiedraad niet oplossen", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Weet u zeker dat u deze reactiedraad wilt oplossen? Dit zal het als voltooid markeren.", + "Are you sure you want to unresolve this comment thread?": "Weet u zeker dat u deze reactiedraad niet wilt oplossen?", + "Resolved": "Opgelost", + "No active comments.": "Geen actieve reacties.", + "No resolved comments.": "Geen opgeloste reacties.", "Revoke invitation": "Uitnodiging intrekken", "Revoke": "Intrekken", "Don't": "Niet doen", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Iedereen met deze link kan zich aansluiten bij deze werkruimte.", "Invite link": "Uitnodigingslink", "Copy": "Kopieer", + "Copy to space": "Kopiëren naar ruimte", "Copied": "Gekopieerd", + "Duplicate": "Dupliceren", "Select a user": "Selecteer een gebruiker", "Select a group": "Selecteer een groep", "Export all pages and attachments in this space.": "Exporteer alle pagina's en bijlagen in deze ruimte.", @@ -239,6 +254,7 @@ "Export failed:": "Exporteren mislukt:", "export error": "Exporteer fout", "Export page": "Exporteer pagina", + "Export successful": "Export succesvol", "Export space": "Exporteer ruimte", "Export {{type}}": "Exporteer {{type}}", "File exceeds the {{limit}} attachment limit": "Bestand overschrijdt de bijlagelimiet van {{limit}}", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Upload een afbeelding vanaf uw apparaat.", "Upload any video from your device.": "Upload een video vanaf uw apparaat.", "Upload any file from your device.": "Upload een bestand vanaf uw apparaat.", + "Uploading {{name}}": "Uploaden {{name}}", + "Uploading file": "Bestand uploaden", "Table": "Tabel", "Insert a table.": "Voeg een tabel in.", "Insert collapsible block.": "Inklapbaar blok invoegen.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Aantal tekens: {{characterCount}}", "New update": "Nieuwe update", "{{latestVersion}} is available": "{{latestVersion}} is beschikbaar", + "Default page edit mode": "Standaard pagina bewerkmodus", + "Choose your preferred page edit mode. Avoid accidental edits.": "Kies uw voorkeurs bewerkmodus voor pagina's. Vermijd per ongeluk bewerken.", + "Reading": "Lezen", "Delete member": "Verwijder lid", "Member deleted successfully": "Lid succesvol verwijderd", "Are you sure you want to delete this workspace member? This action is irreversible.": "Weet u zeker dat u dit lid van de werkruimte wilt verwijderen? Deze actie kan niet ongedaan gemaakt worden.", @@ -384,7 +405,173 @@ "Share deleted successfully": "Delen succesvol verwijderd", "Share not found": "Delen niet gevonden", "Failed to share page": "Pagina delen mislukt", - "Copy page": "Copy page", - "Copy page to a different space.": "Copy page to a different space.", - "Page copied successfully": "Page copied successfully" + "Copy page": "Pagina kopiëren", + "Copy page to a different space.": "Kopieer pagina naar een andere ruimte.", + "Page copied successfully": "Pagina succesvol gekopieerd", + "Page duplicated successfully": "Pagina succesvol gedupliceerd", + "Find": "Zoeken", + "Not found": "Niet gevonden", + "Previous Match (Shift+Enter)": "Vorige overeenkomst (Shift+Enter)", + "Next match (Enter)": "Volgende overeenkomst (Enter)", + "Match case (Alt+C)": "Hoofdlettergevoeligheid (Alt+C)", + "Replace": "Vervangen", + "Close (Escape)": "Sluiten (Escape)", + "Replace (Enter)": "Vervangen (Enter)", + "Replace all (Ctrl+Alt+Enter)": "Alles vervangen (Ctrl+Alt+Enter)", + "Replace all": "Alles vervangen", + "View all spaces": "Bekijk alle ruimtes", + "Error": "Fout", + "Failed to disable MFA": "MFA uitschakelen mislukt", + "Disable two-factor authentication": "Twee-factor authenticatie uitschakelen", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Indien u twee-factor authenticatie uitschakelt, zal uw account minder veilig zijn. U heeft alleen uw wachtwoord nodig om in te loggen.", + "Please enter your password to disable two-factor authentication:": "Voer uw wachtwoord in om twee-factor authenticatie uit te schakelen:", + "Two-factor authentication has been enabled": "Twee-factor authenticatie is ingeschakeld", + "Two-factor authentication has been disabled": "Twee-factor authenticatie is uitgeschakeld", + "2-step verification": "2-staps verificatie", + "Protect your account with an additional verification layer when signing in.": "Bescherm uw account met een extra verificatielaag tijdens het inloggen.", + "Two-factor authentication is active on your account.": "Twee-factor authenticatie is actief op uw account.", + "Add 2FA method": "2FA-methode toevoegen", + "Backup codes": "Back-up codes", + "Disable": "Uitschakelen", + "Invalid verification code": "Ongeldige verificatiecode", + "New backup codes have been generated": "Nieuwe back-up codes zijn gegenereerd", + "Failed to regenerate backup codes": "Back-up codes opnieuw genereren mislukt", + "About backup codes": "Over back-up codes", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Back-up codes kunnen worden gebruikt om uw account te bereiken als u toegang tot uw authenticator-app verliest. Elke code kan slechts één keer worden gebruikt.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "U kunt te allen tijde nieuwe back-up codes genereren. Dit zal alle bestaande codes ongeldig maken.", + "Confirm password": "Bevestig wachtwoord", + "Generate new backup codes": "Genereer nieuwe back-up codes", + "Save your new backup codes": "Sla uw nieuwe back-up codes op", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Zorg ervoor dat u deze codes op een veilige plek opslaat. Uw oude back-up codes zijn niet langer geldig.", + "Your new backup codes": "Uw nieuwe back-up codes", + "I've saved my backup codes": "Ik heb mijn back-up codes opgeslagen", + "Failed to setup MFA": "MFA instellen mislukt", + "Setup & Verify": "Instellen & Verifiëren", + "Add to authenticator": "Toevoegen aan de authenticator", + "1. Scan this QR code with your authenticator app": "1. Scan deze QR-code met uw authenticator-app", + "Can't scan the code?": "Kan de code niet scannen?", + "Enter this code manually in your authenticator app:": "Voer deze code handmatig in uw authenticator-app in:", + "2. Enter the 6-digit code from your authenticator": "2. Voer de 6-cijferige code van uw authenticator in", + "Verify and enable": "Verifiëren en inschakelen", + "Failed to generate QR code. Please try again.": "Het genereren van de QR-code is mislukt. Probeer het opnieuw.", + "Backup": "Back-up", + "Save codes": "Codes opslaan", + "Save your backup codes": "Sla uw back-up codes op", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Deze codes kunnen worden gebruikt om toegang te krijgen tot uw account als u de toegang tot uw authenticator-app verliest. Elke code kan slechts één keer worden gebruikt.", + "Print": "Afdrukken", + "Two-factor authentication has been set up. Please log in again.": "Twee-factor authenticatie is ingesteld. Log alstublieft opnieuw in.", + "Two-Factor authentication required": "Twee-factor authenticatie vereist", + "Your workspace requires two-factor authentication for all users": "Uw werkruimte vereist twee-factor authenticatie voor alle gebruikers", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Om toegang te blijven krijgen tot uw werkruimte, moet u twee-factor authenticatie instellen. Dit voegt een extra beveiligingslaag toe aan uw account.", + "Set up two-factor authentication": "Stel twee-factor authenticatie in", + "Cancel and logout": "Annuleren en uitloggen", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Uw werkruimte vereist twee-factor authenticatie. Stel het in om door te gaan.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Dit voegt een extra beveiligingslaag toe aan uw account door een verificatiecode van uw authenticator-app te vereisen.", + "Password is required": "Wachtwoord is vereist", + "Password must be at least 8 characters": "Wachtwoord moet minimaal 8 tekens zijn", + "Please enter a 6-digit code": "Voer alstublieft een 6-cijferige code in", + "Code must be exactly 6 digits": "Code moet exact 6 cijfers zijn", + "Enter the 6-digit code found in your authenticator app": "Voer de 6-cijferige code in die in uw authenticator-app staat", + "Need help authenticating?": "Hulp nodig bij het authenticeren?", + "MFA QR Code": "MFA QR-code", + "Account created successfully. Please log in to set up two-factor authentication.": "Account succesvol aangemaakt. Log alstublieft in om twee-factor authenticatie in te stellen.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Wachtwoord reset succesvol. Log in met uw nieuwe wachtwoord en voltooi twee-factor authenticatie.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Wachtwoord reset succesvol. Log in met uw nieuwe wachtwoord om twee-factor authenticatie in te stellen.", + "Password reset was successful. Please log in with your new password.": "De wachtwoord reset was succesvol. Log in met uw nieuwe wachtwoord.", + "Two-factor authentication": "Twee-factor authenticatie", + "Use authenticator app instead": "Gebruik in plaats daarvan de authenticator-app", + "Verify backup code": "Back-up code verifiëren", + "Use backup code": "Gebruik back-up code", + "Enter one of your backup codes": "Voer een van uw back-up codes in", + "Backup code": "Back-up code", + "Enter one of your backup codes. Each backup code can only be used once.": "Voer een van uw back-up codes in. Elke back-up code kan slechts één keer worden gebruikt.", + "Verify": "Verifiëren", + "Trash": "Prullenbak", + "Pages in trash will be permanently deleted after 30 days.": "Pagina's in de prullenbak worden na 30 dagen permanent verwijderd.", + "Deleted": "Verwijderd", + "No pages in trash": "Geen pagina's in de prullenbak", + "Permanently delete page?": "Pagina permanent verwijderen?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Weet u zeker dat u '{{title}}' permanent wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "Restore '{{title}}' and its sub-pages?": "'{{title}}' en zijn subpagina's herstellen?", + "Move to trash": "Naar de prullenbak verplaatsen", + "Move this page to trash?": "Deze pagina naar de prullenbak verplaatsen?", + "Restore page": "Pagina herstellen", + "Page moved to trash": "Pagina verplaatst naar de prullenbak", + "Page restored successfully": "Pagina succesvol hersteld", + "Deleted by": "Verwijderd door", + "Deleted at": "Verwijderd op", + "Preview": "Voorbeeld", + "Subpages": "Subpagina's", + "Failed to load subpages": "Laden van subpagina's mislukt", + "No subpages": "Geen subpagina's", + "Subpages (Child pages)": "Subpagina's (Kindpagina's)", + "List all subpages of the current page": "Lijst van alle subpagina's van de huidige pagina", + "Attachments": "Bijlagen", + "All spaces": "Alle ruimtes", + "Unknown": "Onbekend", + "Find a space": "Vind een ruimte", + "Search in all your spaces": "Zoek in al je ruimtes", + "Type": "Type", + "Enterprise": "Onderneming", + "Download attachment": "Bijlage downloaden", + "Allowed email domains": "Toegestane e-maildomeinen", + "Only users with email addresses from these domains can signup via SSO.": "Alleen gebruikers met e-mailadressen van deze domeinen kunnen zich aanmelden via SSO.", + "Enter valid domain names separated by comma or space": "Voer geldige domeinnamen in, gescheiden door komma of spatie", + "Enforce two-factor authentication": "Handhaaf tweefactorauthenticatie", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Na handhaving moeten alle leden tweefactorauthenticatie inschakelen om toegang te krijgen tot de werkomgeving.", + "Toggle MFA enforcement": "Schakel MFA-handhaving in of uit", + "Display name": "Weergavenaam", + "Allow signup": "Aanmelden toestaan", + "Enabled": "Ingeschakeld", + "Advanced Settings": "Geavanceerde instellingen", + "Enable TLS/SSL": "TLS/SSL inschakelen", + "Use secure connection to LDAP server": "Gebruik een beveiligde verbinding met de LDAP-server", + "Group sync": "Groepssynchronisatie", + "No SSO providers found.": "Geen SSO-providers gevonden.", + "Delete SSO provider": "Verwijder SSO-provider", + "Are you sure you want to delete this SSO provider?": "Weet u zeker dat u deze SSO-provider wilt verwijderen?", + "Action": "Actie", + "{{ssoProviderType}} configuration": "{{ssoProviderType}} configuratie", + "Icon": "Icoon", + "Upload image": "Afbeelding uploaden", + "Remove image": "Afbeelding verwijderen", + "Failed to remove image": "Afbeelding verwijderen mislukt", + "Image exceeds 10MB limit.": "Afbeelding overschrijdt de limiet van 10MB.", + "Image removed successfully": "Afbeelding succesvol verwijderd", + "API key": "API-sleutel", + "API key created successfully": "API-sleutel succesvol aangemaakt", + "API keys": "API-sleutels", + "API management": "API-beheer", + "Are you sure you want to revoke this API key": "Weet u zeker dat u deze API-sleutel wilt intrekken", + "Create API Key": "API-sleutel aanmaken", + "Custom expiration date": "Aangepaste vervaldatum", + "Enter a descriptive token name": "Voer een beschrijvende tokennaam in", + "Expiration": "Vervaldatum", + "Expired": "Verlopen", + "Expires": "Verloopt", + "I've saved my API key": "Ik heb mijn API-sleutel opgeslagen", + "Last use": "Laatst gebruikt", + "No API keys found": "Geen API-sleutels gevonden", + "No expiration": "Geen vervaldatum", + "Revoke API key": "API-sleutel intrekken", + "Revoked successfully": "Succesvol ingetrokken", + "Select expiration date": "Selecteer vervaldatum", + "This action cannot be undone. Any applications using this API key will stop working.": "Deze actie kan niet ongedaan worden gemaakt. Alle toepassingen die deze API-sleutel gebruiken, zullen niet meer werken.", + "Update API key": "API-sleutel bijwerken", + "Manage API keys for all users in the workspace": "Beheer API-sleutels voor alle gebruikers in de werkruimte", + "AI settings": "AI-instellingen", + "AI search": "AI-zoekopdracht", + "AI Answer": "AI Antwoord", + "Ask AI": "Vraag AI", + "AI is thinking...": "AI is aan het nadenken...", + "Ask a question...": "Stel een vraag...", + "AI-powered search (Ask AI)": "AI-ondersteunde zoekopdracht (Vraag AI)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI-zoekopdracht maakt gebruik van vectorembeddings om semantische zoekmogelijkheden te bieden in uw werkruimte-inhoud.", + "Toggle AI search": "Schakel AI-zoekopdracht in/uit", + "Sources": "Bronnen", + "Ask AI not available for attachments": "Vraag AI is niet beschikbaar voor bijlages", + "No answer available": "Geen antwoord beschikbaar", + "Background color": "Achtergrondkleur", + "Highlight color": "Markeerkleur", + "Remove color": "Kleur verwijderen" } diff --git a/apps/client/public/locales/pt-BR/translation.json b/apps/client/public/locales/pt-BR/translation.json index 3b6be52c..30cc0b21 100644 --- a/apps/client/public/locales/pt-BR/translation.json +++ b/apps/client/public/locales/pt-BR/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Escolha o idioma da interface.", "Choose your preferred page width.": "Escolha a largura preferida da página.", "Confirm": "Confirmar", + "Copy as Markdown": "Copiar como Markdown", "Copy link": "Copiar link", "Create": "Criar", "Create group": "Criar grupo", @@ -53,6 +54,7 @@ "e.g Space for product team": "ex.: Espaço para a equipe de produto", "e.g Space for sales team to collaborate": "ex.: Espaço para a equipe de vendas colaborar", "Edit": "Editar", + "Read": "Ler", "Edit group": "Editar grupo", "Email": "Email", "Enter a strong password": "Insira uma senha forte", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Comentário excluído com sucesso", "Failed to delete comment": "Falha ao excluir comentário", "Comment resolved successfully": "Comentário resolvido com sucesso", + "Comment re-opened successfully": "Comentário reaberto com sucesso", + "Comment unresolved successfully": "Comentário não resolvido com sucesso", "Failed to resolve comment": "Falha ao resolver comentário", + "Resolve comment": "Resolver comentário", + "Unresolve comment": "Não resolver comentário", + "Resolve Comment Thread": "Resolver Fio de Comentários", + "Unresolve Comment Thread": "Não resolver Fio de Comentários", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Tem certeza de que deseja resolver este fio de comentários? Isso o marcará como concluído.", + "Are you sure you want to unresolve this comment thread?": "Tem certeza de que deseja não resolver este fio de comentários?", + "Resolved": "Resolvido", + "No active comments.": "Sem comentários ativos.", + "No resolved comments.": "Sem comentários resolvidos.", "Revoke invitation": "Cancelar o convite", "Revoke": "Anular", "Don't": "Não", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Qualquer um com este link pode participar deste espaço de trabalho.", "Invite link": "Link do convite", "Copy": "Copiar", + "Copy to space": "Copiar para o espaço", "Copied": "Copiado", + "Duplicate": "Duplicar", "Select a user": "Selecione um usuário", "Select a group": "Selecione um grupo", "Export all pages and attachments in this space.": "Exportar todas as páginas e anexos deste espaço.", @@ -239,6 +254,7 @@ "Export failed:": "Falha ao exportar:", "export error": "erro de exportação", "Export page": "Exportar página", + "Export successful": "Exportação bem-sucedida", "Export space": "Exportar espaço", "Export {{type}}": "Exportar para {{type}}", "File exceeds the {{limit}} attachment limit": "O arquivo excede o limite de anexos {{limit}}", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Envie qualquer imagem do seu dispositivo.", "Upload any video from your device.": "Envie qualquer vídeo do seu dispositivo.", "Upload any file from your device.": "Envie qualquer arquivo do seu dispositivo.", + "Uploading {{name}}": "Enviando {{name}}", + "Uploading file": "Enviando arquivo", "Table": "Tabela", "Insert a table.": "Insira uma tabela.", "Insert collapsible block.": "Insira um bloco colapsável.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Contagem de caracteres: {{characterCount}}", "New update": "Nova atualização", "{{latestVersion}} is available": "{{latestVersion}} está disponível", + "Default page edit mode": "Modo de edição de página padrão", + "Choose your preferred page edit mode. Avoid accidental edits.": "Escolha o modo de edição de página preferido. Evite edições acidentais.", + "Reading": "Leitura", "Delete member": "Excluir membro", "Member deleted successfully": "Membro removido com sucesso", "Are you sure you want to delete this workspace member? This action is irreversible.": "Você tem certeza que deseja deletar este membro do workspace? Esta ação é irreversível.", @@ -384,7 +405,173 @@ "Share deleted successfully": "Compartilhamento excluído com sucesso", "Share not found": "Compartilhamento não encontrado", "Failed to share page": "Falha ao compartilhar página", - "Copy page": "Copy page", - "Copy page to a different space.": "Copy page to a different space.", - "Page copied successfully": "Page copied successfully" + "Copy page": "Copiar página", + "Copy page to a different space.": "Copiar página para um espaço diferente.", + "Page copied successfully": "Página copiada com sucesso", + "Page duplicated successfully": "Página duplicada com sucesso", + "Find": "Encontrar", + "Not found": "Não encontrado", + "Previous Match (Shift+Enter)": "Correspondência anterior (Shift+Enter)", + "Next match (Enter)": "Próxima correspondência (Enter)", + "Match case (Alt+C)": "Diferenciar maiúsculas de minúsculas (Alt+C)", + "Replace": "Substituir", + "Close (Escape)": "Fechar (Escape)", + "Replace (Enter)": "Substituir (Enter)", + "Replace all (Ctrl+Alt+Enter)": "Substituir tudo (Ctrl+Alt+Enter)", + "Replace all": "Substituir tudo", + "View all spaces": "Ver todos os espaços", + "Error": "Erro", + "Failed to disable MFA": "Falha ao desativar a MFA", + "Disable two-factor authentication": "Desativar autenticação de dois fatores", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Desativar a autenticação de dois fatores tornará sua conta menos segura. Você só precisará de sua senha para entrar.", + "Please enter your password to disable two-factor authentication:": "Por favor, insira sua senha para desativar a autenticação de dois fatores:", + "Two-factor authentication has been enabled": "Autenticação de dois fatores foi ativada", + "Two-factor authentication has been disabled": "Autenticação de dois fatores foi desativada", + "2-step verification": "Verificação em duas etapas", + "Protect your account with an additional verification layer when signing in.": "Proteja sua conta com uma camada adicional de verificação ao entrar.", + "Two-factor authentication is active on your account.": "Autenticação de dois fatores está ativa na sua conta.", + "Add 2FA method": "Adicionar método de 2FA", + "Backup codes": "Códigos de backup", + "Disable": "Desativar", + "Invalid verification code": "Código de verificação inválido", + "New backup codes have been generated": "Novos códigos de backup foram gerados", + "Failed to regenerate backup codes": "Falha ao regenerar códigos de backup", + "About backup codes": "Sobre códigos de backup", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Códigos de backup podem ser usados para acessar sua conta se perder acesso ao aplicativo autenticador. Cada código só pode ser usado uma vez.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Você pode regenerar novos códigos de backup a qualquer momento. Isso invalidará todos os códigos existentes.", + "Confirm password": "Confirmar senha", + "Generate new backup codes": "Gerar novos códigos de backup", + "Save your new backup codes": "Salvar seus novos códigos de backup", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Certifique-se de salvar esses códigos em um local seguro. Seus códigos de backup antigos não são mais válidos.", + "Your new backup codes": "Seus novos códigos de backup", + "I've saved my backup codes": "Eu salvei meus códigos de backup", + "Failed to setup MFA": "Falha ao configurar a MFA", + "Setup & Verify": "Configurar & Verificar", + "Add to authenticator": "Adicionar ao autenticador", + "1. Scan this QR code with your authenticator app": "1. Escaneie este código QR com seu aplicativo autenticador", + "Can't scan the code?": "Não consegue escanear o código?", + "Enter this code manually in your authenticator app:": "Digite este código manualmente em seu aplicativo autenticador:", + "2. Enter the 6-digit code from your authenticator": "2. Digite o código de 6 dígitos do seu autenticador", + "Verify and enable": "Verificar e ativar", + "Failed to generate QR code. Please try again.": "Falha ao gerar código QR. Por favor, tente novamente.", + "Backup": "Backup", + "Save codes": "Salvar códigos", + "Save your backup codes": "Salvar seus códigos de backup", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Esses códigos podem ser usados para acessar sua conta se você perder o acesso ao aplicativo autenticador. Cada código só pode ser usado uma vez.", + "Print": "Imprimir", + "Two-factor authentication has been set up. Please log in again.": "A autenticação de dois fatores foi configurada. Por favor, faça login novamente.", + "Two-Factor authentication required": "Autenticação de dois fatores necessária", + "Your workspace requires two-factor authentication for all users": "Seu espaço de trabalho requer autenticação de dois fatores para todos os usuários", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Para continuar acessando seu espaço de trabalho, você deve configurar a autenticação de dois fatores. Isso adiciona uma camada extra de segurança à sua conta.", + "Set up two-factor authentication": "Configurar autenticação de dois fatores", + "Cancel and logout": "Cancelar e sair", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Seu espaço de trabalho requer autenticação de dois fatores. Por favor, configure para continuar.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Isso adiciona uma camada extra de segurança à sua conta, exigindo um código de verificação de seu aplicativo autenticador.", + "Password is required": "Senha é necessária", + "Password must be at least 8 characters": "A senha deve ter pelo menos 8 caracteres", + "Please enter a 6-digit code": "Por favor, insira um código de 6 dígitos", + "Code must be exactly 6 digits": "O código deve ter exatamente 6 dígitos", + "Enter the 6-digit code found in your authenticator app": "Insira o código de 6 dígitos encontrado em seu aplicativo autenticador", + "Need help authenticating?": "Precisa de ajuda para autenticar?", + "MFA QR Code": "Código QR de MFA", + "Account created successfully. Please log in to set up two-factor authentication.": "Conta criada com sucesso. Por favor, faça login para configurar a autenticação de dois fatores.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Redefinição de senha bem-sucedida. Por favor, faça login com sua nova senha e complete a autenticação de dois fatores.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Redefinição de senha bem-sucedida. Por favor, faça login com sua nova senha para configurar a autenticação de dois fatores.", + "Password reset was successful. Please log in with your new password.": "Redefinição de senha foi bem-sucedida. Por favor, faça login com sua nova senha.", + "Two-factor authentication": "Autenticação de dois fatores", + "Use authenticator app instead": "Use o aplicativo autenticador em vez disso", + "Verify backup code": "Verificar código de backup", + "Use backup code": "Usar código de backup", + "Enter one of your backup codes": "Digite um de seus códigos de backup", + "Backup code": "Código de backup", + "Enter one of your backup codes. Each backup code can only be used once.": "Digite um de seus códigos de backup. Cada código de backup só pode ser usado uma vez.", + "Verify": "Verificar", + "Trash": "Lixeira", + "Pages in trash will be permanently deleted after 30 days.": "Páginas na lixeira serão excluídas permanentemente após 30 dias.", + "Deleted": "Excluído", + "No pages in trash": "Sem páginas na lixeira", + "Permanently delete page?": "Excluir página permanentemente?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Tem certeza de que deseja excluir permanentemente '{{title}}'? Esta ação não pode ser desfeita.", + "Restore '{{title}}' and its sub-pages?": "Restaurar '{{title}}' e suas subpáginas?", + "Move to trash": "Mover para a lixeira", + "Move this page to trash?": "Mover esta página para a lixeira?", + "Restore page": "Restaurar página", + "Page moved to trash": "Página movida para a lixeira", + "Page restored successfully": "Página restaurada com sucesso", + "Deleted by": "Excluído por", + "Deleted at": "Excluído em", + "Preview": "Visualização", + "Subpages": "Subpáginas", + "Failed to load subpages": "Falha ao carregar subpáginas", + "No subpages": "Sem subpáginas", + "Subpages (Child pages)": "Subpáginas (Páginas filhas)", + "List all subpages of the current page": "Listar todas as subpáginas da página atual", + "Attachments": "Anexos", + "All spaces": "Todos os espaços", + "Unknown": "Desconhecido", + "Find a space": "Encontrar um espaço", + "Search in all your spaces": "Pesquisar em todos os seus espaços", + "Type": "Tipo", + "Enterprise": "Empresa", + "Download attachment": "Baixar anexo", + "Allowed email domains": "Domínios de email permitidos", + "Only users with email addresses from these domains can signup via SSO.": "Apenas usuários com endereços de email desses domínios podem se inscrever via SSO.", + "Enter valid domain names separated by comma or space": "Insira nomes de domínio válidos separados por vírgula ou espaço", + "Enforce two-factor authentication": "Impor autenticação de dois fatores", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Uma vez imposto, todos os membros devem habilitar a autenticação de dois fatores para acessar o espaço de trabalho.", + "Toggle MFA enforcement": "Alternar imposição de MFA", + "Display name": "Nome de exibição", + "Allow signup": "Permitir inscrição", + "Enabled": "Habilitado", + "Advanced Settings": "Configurações Avançadas", + "Enable TLS/SSL": "Habilitar TLS/SSL", + "Use secure connection to LDAP server": "Usar conexão segura com o servidor LDAP", + "Group sync": "Sincronização de grupo", + "No SSO providers found.": "Nenhum provedor de SSO encontrado.", + "Delete SSO provider": "Excluir provedor de SSO", + "Are you sure you want to delete this SSO provider?": "Tem certeza de que deseja excluir este provedor de SSO?", + "Action": "Ação", + "{{ssoProviderType}} configuration": "Configuração de {{ssoProviderType}}", + "Icon": "Ícone", + "Upload image": "Fazer upload da imagem", + "Remove image": "Remover imagem", + "Failed to remove image": "Falha ao remover imagem", + "Image exceeds 10MB limit.": "A imagem excede o limite de 10MB.", + "Image removed successfully": "Imagem removida com sucesso", + "API key": "Chave API", + "API key created successfully": "Chave API criada com sucesso", + "API keys": "Chaves API", + "API management": "Gestão de API", + "Are you sure you want to revoke this API key": "Tem certeza de que deseja revogar esta chave API", + "Create API Key": "Criar Chave API", + "Custom expiration date": "Data de expiração personalizada", + "Enter a descriptive token name": "Insira um nome descritivo para o token", + "Expiration": "Expiração", + "Expired": "Expirado", + "Expires": "Expira", + "I've saved my API key": "Salvei minha chave API", + "Last use": "Último uso", + "No API keys found": "Nenhuma chave API encontrada", + "No expiration": "Sem expiração", + "Revoke API key": "Revogar chave API", + "Revoked successfully": "Revogada com sucesso", + "Select expiration date": "Selecionar data de expiração", + "This action cannot be undone. Any applications using this API key will stop working.": "Esta ação não pode ser desfeita. Qualquer aplicação usando esta chave API deixará de funcionar.", + "Update API key": "Atualizar chave API", + "Manage API keys for all users in the workspace": "Gerenciar chaves API para todos os usuários no espaço de trabalho", + "AI settings": "Configurações de IA", + "AI search": "Pesquisa IA", + "AI Answer": "Resposta de IA", + "Ask AI": "Pergunte à IA", + "AI is thinking...": "IA está pensando...", + "Ask a question...": "Faça uma pergunta...", + "AI-powered search (Ask AI)": "Pesquisa com IA (Pergunte à IA)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "A pesquisa IA usa vetores de incorporação para fornecer capacidades de pesquisa semântica em todo o conteúdo do seu espaço de trabalho.", + "Toggle AI search": "Alternar pesquisa de IA", + "Sources": "Fontes", + "Ask AI not available for attachments": "Perguntar à IA não está disponível para anexos", + "No answer available": "Nenhuma resposta disponível", + "Background color": "Cor de fundo", + "Highlight color": "Cor de destaque", + "Remove color": "Remover cor" } diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index 92677ff4..88e1f701 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Выберите предпочитаемый язык интерфейса.", "Choose your preferred page width.": "Выберите предпочитаемую ширину страницы.", "Confirm": "Подтвердить", + "Copy as Markdown": "Копировать как Markdown", "Copy link": "Копировать ссылку", "Create": "Создать", "Create group": "Создать группу", @@ -53,6 +54,7 @@ "e.g Space for product team": "например, Пространство для продуктовой команды", "e.g Space for sales team to collaborate": "например, Пространство для совместной работы команды продаж", "Edit": "Редактировать", + "Read": "Читать", "Edit group": "Редактировать группу", "Email": "Электронная почта", "Enter a strong password": "Введите надёжный пароль", @@ -61,7 +63,7 @@ "Enter your current password": "Введите ваш текущий пароль", "enter your full name": "введите ваше полное имя", "Enter your new password": "Введите ваш новый пароль", - "Enter your new preferred email": "Введите ваш новый предпочитаемый адрес электронной почты", + "Enter your new preferred email": "Введите ваш новый предпочтительный адрес электронной почты", "Enter your password": "Введите ваш пароль", "Error fetching page data.": "Ошибка при загрузке данных страницы.", "Error loading page history.": "Ошибка при загрузке истории страницы.", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Комментарий успешно удалён", "Failed to delete comment": "Не удалось удалить комментарий", "Comment resolved successfully": "Комментарий успешно разрешён", + "Comment re-opened successfully": "Комментарий успешно открыт заново", + "Comment unresolved successfully": "Комментарий успешно размечен как нерешённый", "Failed to resolve comment": "Не удалось разрешить комментарий", + "Resolve comment": "Разрешить комментарий", + "Unresolve comment": "Отметить комментарий как нерешённый", + "Resolve Comment Thread": "Закрыть цепочку комментариев", + "Unresolve Comment Thread": "Отметить цепочку комментариев как нерешённую", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Вы уверены, что хотите закрыть эту цепочку комментариев? Это пометит её как завершённую.", + "Are you sure you want to unresolve this comment thread?": "Вы уверены, что хотите отметить эту цепочку комментариев как нерешённую?", + "Resolved": "Решено", + "No active comments.": "Нет активных комментариев.", + "No resolved comments.": "Нет решённых комментариев.", "Revoke invitation": "Отозвать приглашение", "Revoke": "Отозвать", "Don't": "Нет", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Любой, у кого есть данная ссылка, может присоединиться к этой рабочей области.", "Invite link": "Ссылка для приглашения", "Copy": "Копировать", + "Copy to space": "Копировать в пространство", "Copied": "Скопировано", + "Duplicate": "Дублировать", "Select a user": "Выберите пользователя", "Select a group": "Выберите группу", "Export all pages and attachments in this space.": "Экспортировать все страницы и вложения в этом пространстве.", @@ -239,6 +254,7 @@ "Export failed:": "Экспортирование не удалось:", "export error": "ошибка экспорта", "Export page": "Экспорт страницы", + "Export successful": "Экспорт выполнен успешно", "Export space": "Экспорт пространства", "Export {{type}}": "Экспорт {{type}}", "File exceeds the {{limit}} attachment limit": "Файл превышает лимит вложений {{limit}}", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Загрузить любое изображение с вашего устройства.", "Upload any video from your device.": "Загрузить любое видео с вашего устройства.", "Upload any file from your device.": "Загрузить любой файл с вашего устройства.", + "Uploading {{name}}": "Загрузка {{name}}", + "Uploading file": "Загрузка файла", "Table": "Таблица", "Insert a table.": "Вставить таблицу.", "Insert collapsible block.": "Вставить сворачиваемый блок.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Количество символов: {{characterCount}}", "New update": "Новое обновление", "{{latestVersion}} is available": "Доступна новая версия {{latestVersion}}", + "Default page edit mode": "Режим редактирования страницы по умолчанию", + "Choose your preferred page edit mode. Avoid accidental edits.": "Выберите предпочитаемый режим редактирования страницы. Избегайте случайных изменений.", + "Reading": "Чтение", "Delete member": "Удалить участника", "Member deleted successfully": "Участник успешно удален", "Are you sure you want to delete this workspace member? This action is irreversible.": "Вы уверены, что хотите удалить этого участника рабочей области? Это действие необратимо.", @@ -386,5 +407,171 @@ "Failed to share page": "Не удалось поделиться страницей", "Copy page": "Копировать страницу", "Copy page to a different space.": "Копировать страницу в другое пространство.", - "Page copied successfully": "Страница успешно скопирована" + "Page copied successfully": "Страница успешно скопирована", + "Page duplicated successfully": "Страница успешно дублирована", + "Find": "Найти", + "Not found": "Не найдено", + "Previous Match (Shift+Enter)": "Предыдущее совпадение (Shift+Enter)", + "Next match (Enter)": "Следующее совпадение (Enter)", + "Match case (Alt+C)": "Учитывать регистр (Alt+C)", + "Replace": "Заменить", + "Close (Escape)": "Закрыть (Escape)", + "Replace (Enter)": "Заменить (Enter)", + "Replace all (Ctrl+Alt+Enter)": "Заменить все (Ctrl+Alt+Enter)", + "Replace all": "Заменить все", + "View all spaces": "Просмотреть все пространства", + "Error": "Ошибка", + "Failed to disable MFA": "Не удалось отключить двухфакторную аутентификацию", + "Disable two-factor authentication": "Отключить двухфакторную аутентификацию", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Отключение двухфакторной аутентификации сделает вашу учетную запись менее безопасной. Для входа потребуется только пароль.", + "Please enter your password to disable two-factor authentication:": "Пожалуйста, введите ваш пароль, чтобы отключить двухфакторную аутентификацию:", + "Two-factor authentication has been enabled": "Двухфакторная аутентификация включена", + "Two-factor authentication has been disabled": "Двухфакторная аутентификация отключена", + "2-step verification": "Двухэтапная проверка", + "Protect your account with an additional verification layer when signing in.": "Защитите свою учетную запись дополнительным уровнем проверки при входе.", + "Two-factor authentication is active on your account.": "Двухфакторная аутентификация активна на вашей учетной записи.", + "Add 2FA method": "Добавить метод 2FA", + "Backup codes": "Резервные коды", + "Disable": "Отключить", + "Invalid verification code": "Неверный код проверки", + "New backup codes have been generated": "Созданы новые резервные коды", + "Failed to regenerate backup codes": "Не удалось создать новые резервные коды", + "About backup codes": "О резервных кодах", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Резервные коды можно использовать для доступа к вашей учетной записи, если вы потеряли доступ к приложению-аутентификатору. Каждый код можно использовать только один раз.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Вы можете создать новые резервные коды в любое время. Это аннулирует все существующие коды.", + "Confirm password": "Подтвердите пароль", + "Generate new backup codes": "Создать новые резервные коды", + "Save your new backup codes": "Сохраните ваши новые резервные коды", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Убедитесь, что сохранили эти коды в безопасном месте. Ваши старые резервные коды больше недействительны.", + "Your new backup codes": "Ваши новые резервные коды", + "I've saved my backup codes": "Я сохранил(а) свои резервные коды", + "Failed to setup MFA": "Не удалось настроить многофакторную аутентификацию", + "Setup & Verify": "Настроить и проверить", + "Add to authenticator": "Добавить в аутентификатор", + "1. Scan this QR code with your authenticator app": "1. Отсканируйте этот QR-код с помощью вашего приложения-аутентификатора", + "Can't scan the code?": "Не удается сканировать код?", + "Enter this code manually in your authenticator app:": "Введите этот код вручную в приложении-аутентификаторе:", + "2. Enter the 6-digit code from your authenticator": "2. Введите 6-значный код из вашего аутентификатора", + "Verify and enable": "Проверить и включить", + "Failed to generate QR code. Please try again.": "Не удалось создать QR-код. Пожалуйста, попробуйте снова.", + "Backup": "Резервное копирование", + "Save codes": "Сохранить коды", + "Save your backup codes": "Сохраните ваши резервные коды", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Эти коды можно использовать для доступа к вашей учетной записи, если вы потеряли доступ к приложению-аутентификатору. Каждый код можно использовать только один раз.", + "Print": "Печать", + "Two-factor authentication has been set up. Please log in again.": "Двухфакторная аутентификация настроена. Пожалуйста, войдите снова.", + "Two-Factor authentication required": "Требуется двухфакторная аутентификация", + "Your workspace requires two-factor authentication for all users": "Ваше рабочее пространство требует двухфакторной аутентификации для всех пользователей", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Чтобы продолжать доступ к вашему рабочему пространству, вы должны настроить двухфакторную аутентификацию. Это добавляет дополнительный уровень безопасности к вашей учетной записи.", + "Set up two-factor authentication": "Настройте двухфакторную аутентификацию", + "Cancel and logout": "Отменить и выйти", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Ваше рабочее пространство требует двухфакторной аутентификации. Пожалуйста, настройте её, чтобы продолжить.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Это добавляет дополнительный уровень безопасности к вашей учетной записи, требуя код проверки из вашего приложения-аутентификатора.", + "Password is required": "Требуется пароль", + "Password must be at least 8 characters": "Пароль должен содержать как минимум 8 символов", + "Please enter a 6-digit code": "Пожалуйста, введите 6-значный код", + "Code must be exactly 6 digits": "Код должен содержать ровно 6 цифр", + "Enter the 6-digit code found in your authenticator app": "Введите 6-значный код из вашего приложения-аутентификатора", + "Need help authenticating?": "Нужна помощь с аутентификацией?", + "MFA QR Code": "QR-код двухфакторной аутентификации", + "Account created successfully. Please log in to set up two-factor authentication.": "Учетная запись успешно создана. Пожалуйста, войдите, чтобы настроить двухфакторную аутентификацию.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем и завершите настройку двухфакторной аутентификации.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем, чтобы настроить двухфакторную аутентификацию.", + "Password reset was successful. Please log in with your new password.": "Сброс пароля выполнен успешно. Пожалуйста, войдите с вашим новым паролем.", + "Two-factor authentication": "Двухфакторная аутентификация", + "Use authenticator app instead": "Используйте приложение-аутентификатор вместо этого", + "Verify backup code": "Проверка резервного кода", + "Use backup code": "Использовать резервный код", + "Enter one of your backup codes": "Введите один из ваших резервных кодов", + "Backup code": "Резервный код", + "Enter one of your backup codes. Each backup code can only be used once.": "Введите один из ваших резервных кодов. Каждый резервный код можно использовать только один раз.", + "Verify": "Проверить", + "Trash": "Корзина", + "Pages in trash will be permanently deleted after 30 days.": "Страницы в корзине будут окончательно удалены через 30 дней.", + "Deleted": "Удалено", + "No pages in trash": "В корзине нет страниц", + "Permanently delete page?": "Удалить страницу окончательно?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Вы уверены, что хотите окончательно удалить '{{title}}'? Это действие невозможно отменить.", + "Restore '{{title}}' and its sub-pages?": "Восстановить '{{title}}' и её подстраницы?", + "Move to trash": "Переместить в корзину", + "Move this page to trash?": "Переместить эту страницу в корзину?", + "Restore page": "Восстановить страницу", + "Page moved to trash": "Страница перемещена в корзину", + "Page restored successfully": "Страница успешно восстановлена", + "Deleted by": "Удалено пользователем", + "Deleted at": "Удалено в", + "Preview": "Предпросмотр", + "Subpages": "Подстраницы", + "Failed to load subpages": "Не удалось загрузить под страницы", + "No subpages": "Нет подстраниц", + "Subpages (Child pages)": "Подстраницы (вложенные страницы)", + "List all subpages of the current page": "Показать все под страницы", + "Attachments": "Вложения", + "All spaces": "Все пространства", + "Unknown": "Неизвестно", + "Find a space": "Найти пространство", + "Search in all your spaces": "Поиск во всех ваших пространствах", + "Type": "Тип", + "Enterprise": "Предприятие", + "Download attachment": "Скачать вложение", + "Allowed email domains": "Разрешенные домены электронной почты", + "Only users with email addresses from these domains can signup via SSO.": "Только пользователи с электронными адресами из этих доменов могут зарегистрироваться через SSO.", + "Enter valid domain names separated by comma or space": "Введите допустимые доменные имена, разделённые запятыми или пробелами", + "Enforce two-factor authentication": "Обязательная двухфакторная аутентификация", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "После введения обязательности все участники должны будут включить двухфакторную аутентификацию для доступа к рабочему пространству.", + "Toggle MFA enforcement": "Переключить обязательность MFA", + "Display name": "Отображаемое имя", + "Allow signup": "Разрешить регистрацию", + "Enabled": "Включено", + "Advanced Settings": "Расширенные настройки", + "Enable TLS/SSL": "Включить TLS/SSL", + "Use secure connection to LDAP server": "Использовать защищённое соединение с сервером LDAP", + "Group sync": "Синхронизация группы", + "No SSO providers found.": "Поставщики SSO не найдены.", + "Delete SSO provider": "Удалить поставщика SSO", + "Are you sure you want to delete this SSO provider?": "Вы уверены, что хотите удалить этого поставщика SSO?", + "Action": "Действие", + "{{ssoProviderType}} configuration": "Настройка {{ssoProviderType}}", + "Icon": "Иконка", + "Upload image": "Загрузить изображение", + "Remove image": "Удалить изображение", + "Failed to remove image": "Не удалось удалить изображение", + "Image exceeds 10MB limit.": "Изображение превышает предел 10MB.", + "Image removed successfully": "Изображение успешно удалено", + "API key": "API ключ", + "API key created successfully": "API ключ успешно создан", + "API keys": "API ключи", + "API management": "Управление API", + "Are you sure you want to revoke this API key": "Вы уверены, что хотите отозвать этот API ключ", + "Create API Key": "Создать API ключ", + "Custom expiration date": "Пользовательская дата срока действия", + "Enter a descriptive token name": "Введите понятное имя токена", + "Expiration": "Срок действия", + "Expired": "Истек", + "Expires": "Истекает", + "I've saved my API key": "Я сохранил мой API ключ", + "Last use": "Последнее использование", + "No API keys found": "API ключи не найдены", + "No expiration": "Не истекает", + "Revoke API key": "Отозвать API ключ", + "Revoked successfully": "Отозван успешно", + "Select expiration date": "Выберете срок действия", + "This action cannot be undone. Any applications using this API key will stop working.": "Это действие необратимо. Любые приложения, использующие этот API ключ, перестанут работать.", + "Update API key": "Обновить API ключ", + "Manage API keys for all users in the workspace": "Управлять API ключами для всех пользователей в рабочей области", + "AI settings": "Настройки ИИ", + "AI search": "Поиск ИИ", + "AI Answer": "Ответ ИИ", + "Ask AI": "Спросить ИИ", + "AI is thinking...": "ИИ обрабатывает запрос...", + "Ask a question...": "Задайте вопрос...", + "AI-powered search (Ask AI)": "Поиск на базе ИИ (Спросить ИИ)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Поиск ИИ использует векторные встраивания для обеспечения семантического поиска по содержимому вашего рабочего пространства.", + "Toggle AI search": "Переключить поиск ИИ", + "Sources": "Источники", + "Ask AI not available for attachments": "Функция \"Спросить ИИ\" недоступна для вложений", + "No answer available": "Ответ недоступен", + "Background color": "Цвет фона", + "Highlight color": "Цвет выделения", + "Remove color": "Удалить цвет" } diff --git a/apps/client/public/locales/uk-UA/translation.json b/apps/client/public/locales/uk-UA/translation.json index 4585c028..e5cdaa40 100644 --- a/apps/client/public/locales/uk-UA/translation.json +++ b/apps/client/public/locales/uk-UA/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "Оберіть бажану мову інтерфейсу.", "Choose your preferred page width.": "Оберіть бажану ширину сторінки.", "Confirm": "Підтвердити", + "Copy as Markdown": "Скопіювати як Markdown", "Copy link": "Копіювати посилання", "Create": "Створити", "Create group": "Створити групу", @@ -53,6 +54,7 @@ "e.g Space for product team": "наприклад, Простір для продуктової команди", "e.g Space for sales team to collaborate": "наприклад, Простір для спільної роботи команди продажів", "Edit": "Редагувати", + "Read": "Читати", "Edit group": "Редагувати групу", "Email": "Електронна пошта", "Enter a strong password": "Введіть надійний пароль", @@ -213,7 +215,18 @@ "Comment deleted successfully": "Коментар успішно видалено", "Failed to delete comment": "Не вдалося видалити коментар", "Comment resolved successfully": "Коментар успішно вирішено", + "Comment re-opened successfully": "Коментар успішно відкрито повторно", + "Comment unresolved successfully": "Коментар успішно розв'язано", "Failed to resolve comment": "Не вдалося вирішити коментар", + "Resolve comment": "Вирішити коментар", + "Unresolve comment": "Розв'язати коментар", + "Resolve Comment Thread": "Вирішити ланцюжок коментарів", + "Unresolve Comment Thread": "Розв'язати ланцюжок коментарів", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "Ви впевнені, що хочете вирішити цей ланцюжок коментарів? Це позначить його як завершений.", + "Are you sure you want to unresolve this comment thread?": "Ви впевнені, що хочете розв'язати цей ланцюжок коментарів?", + "Resolved": "Вирішено", + "No active comments.": "Немає активних коментарів.", + "No resolved comments.": "Немає вирішених коментарів.", "Revoke invitation": "Відкликати запрошення", "Revoke": "Відкликати", "Don't": "Ні", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "Будь-хто, хто має це посилання, може приєднатися до цієї робочої області.", "Invite link": "Посилання для запрошення", "Copy": "Копіювати", + "Copy to space": "Скопіювати в простір", "Copied": "Скопійовано", + "Duplicate": "Дублювати", "Select a user": "Оберіть користувача", "Select a group": "Оберіть групу", "Export all pages and attachments in this space.": "Експортувати всі сторінки та вкладення в цьому просторі.", @@ -239,6 +254,7 @@ "Export failed:": "Експортування не вдалося:", "export error": "помилка експорту", "Export page": "Експорт сторінки", + "Export successful": "Експорт виконано успішно", "Export space": "Експорт простору", "Export {{type}}": "Експорт {{type}}", "File exceeds the {{limit}} attachment limit": "Файл перевищує ліміт вкладень {{limit}}", @@ -314,6 +330,8 @@ "Upload any image from your device.": "Завантажити будь-яке зображення з вашого пристрою.", "Upload any video from your device.": "Завантажити будь-яке відео з вашого пристрою.", "Upload any file from your device.": "Завантажити будь-який файл з вашого пристрою.", + "Uploading {{name}}": "Завантаження {{name}}", + "Uploading file": "Завантаження файлу", "Table": "Таблиця", "Insert a table.": "Вставити таблицю.", "Insert collapsible block.": "Вставити блок, що згортається.", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "Кількість символів: {{characterCount}}", "New update": "Нове оновлення", "{{latestVersion}} is available": "Доступна нова версія {{latestVersion}}", + "Default page edit mode": "Режим редагування сторінки за замовчуванням", + "Choose your preferred page edit mode. Avoid accidental edits.": "Виберіть бажаний режим редагування сторінки. Уникайте випадкових редагувань.", + "Reading": "Читання", "Delete member": "Видалити учасника", "Member deleted successfully": "Учасника успішно видалено", "Are you sure you want to delete this workspace member? This action is irreversible.": "Ви впевнені, що хочете видалити цього учасника робочої області? Ця дія незворотна.", @@ -386,5 +407,171 @@ "Failed to share page": "Не вдалося поділитися сторінкою", "Copy page": "Копіювати сторінки", "Copy page to a different space.": "Скопіювати сторінку в інший простір.", - "Page copied successfully": "Сторінку успішно скопійовано" + "Page copied successfully": "Сторінку успішно скопійовано", + "Page duplicated successfully": "Сторінку успішно дубльовано", + "Find": "Знайти", + "Not found": "Не знайдено", + "Previous Match (Shift+Enter)": "Попередній збіг (Shift+Enter)", + "Next match (Enter)": "Наступний збіг (Enter)", + "Match case (Alt+C)": "Враховувати регістр (Alt+C)", + "Replace": "Замінити", + "Close (Escape)": "Закрити (Escape)", + "Replace (Enter)": "Замінити (Enter)", + "Replace all (Ctrl+Alt+Enter)": "Замінити все (Ctrl+Alt+Enter)", + "Replace all": "Замінити все", + "View all spaces": "Переглянути всі простори", + "Error": "Помилка", + "Failed to disable MFA": "Не вдалося вимкнути MFA", + "Disable two-factor authentication": "Вимкнути двоетапну аутентифікацію", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "Вимкнення двоетапної аутентифікації зробить ваш обліковий запис менш захищеним. Для входу потрібен лише пароль.", + "Please enter your password to disable two-factor authentication:": "Будь ласка, введіть свій пароль, щоб вимкнути двоетапну аутентифікацію:", + "Two-factor authentication has been enabled": "Двоетапну аутентифікацію включено", + "Two-factor authentication has been disabled": "Двоетапну аутентифікацію вимкнено", + "2-step verification": "Двоетапна перевірка", + "Protect your account with an additional verification layer when signing in.": "Захистіть свій обліковий запис за допомогою додаткового шару перевірки при вході.", + "Two-factor authentication is active on your account.": "Двоетапну аутентифікацію активовано у вашому обліковому записі.", + "Add 2FA method": "Додати метод 2FA", + "Backup codes": "Резервні коди", + "Disable": "Вимкнути", + "Invalid verification code": "Невірний код перевірки", + "New backup codes have been generated": "Нові резервні коди створено", + "Failed to regenerate backup codes": "Не вдалося повторно створити резервні коди", + "About backup codes": "Про резервні коди", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Резервні коди можуть бути використані для доступу до вашого облікового запису, якщо ви втратите доступ до додатку аутентифікатора. Кожен код можна використовувати лише один раз.", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "Ви можете повторно створити нові резервні коди в будь-який час. Це зробить усі існуючі коди недійсними.", + "Confirm password": "Підтвердити пароль", + "Generate new backup codes": "Створити нові резервні коди", + "Save your new backup codes": "Збережіть нові резервні коди", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "Обов'язково збережіть ці коди у безпечному місці. Ваші старі резервні коди більше не дійсні.", + "Your new backup codes": "Ваші нові резервні коди", + "I've saved my backup codes": "Я зберіг резервні коди", + "Failed to setup MFA": "Не вдалося налаштувати MFA", + "Setup & Verify": "Налаштувати та перевірити", + "Add to authenticator": "Додати до аутентифікатора", + "1. Scan this QR code with your authenticator app": "1. Скануйте цей QR-код за допомогою додатку аутентифікатора", + "Can't scan the code?": "Не можете відсканувати код?", + "Enter this code manually in your authenticator app:": "Введіть цей код вручну у додатку аутентифікатора:", + "2. Enter the 6-digit code from your authenticator": "2. Введіть 6-значний код із аутентифікатора", + "Verify and enable": "Перевірити та увімкнути", + "Failed to generate QR code. Please try again.": "Не вдалося створити QR-код. Будь ласка, спробуйте ще раз.", + "Backup": "Резервне копіювання", + "Save codes": "Зберегти коди", + "Save your backup codes": "Зберегти резервні коди", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "Ці коди можуть бути використані для доступу до вашого облікового запису, якщо ви втратите доступ до додатку аутентифікатора. Кожен код можна використовувати лише один раз.", + "Print": "Друкувати", + "Two-factor authentication has been set up. Please log in again.": "Двоетапну аутентифікацію налаштовано. Будь ласка, увійдіть знову.", + "Two-Factor authentication required": "Потрібна двоетапна аутентифікація", + "Your workspace requires two-factor authentication for all users": "Ваш робочий простір вимагає двоетапної аутентифікації для всіх користувачів", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "Щоб продовжити доступ до робочого простору, вам потрібно налаштувати двоетапну аутентифікацію. Це додає додатковий шар захисту до вашого облікового запису.", + "Set up two-factor authentication": "Налаштувати двоетапну аутентифікацію", + "Cancel and logout": "Скасувати та вийти", + "Your workspace requires two-factor authentication. Please set it up to continue.": "Ваш робочий простір вимагає двоетапної аутентифікації. Будь ласка, налаштуйте це щоб продовжити.", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "Це додає додатковий шар захисту до вашого облікового запису, вимагаючи код підтвердження з вашого додатку аутентифікатора.", + "Password is required": "Вимагається пароль", + "Password must be at least 8 characters": "Пароль повинен містити щонайменше 8 символів", + "Please enter a 6-digit code": "Будь ласка, введіть 6-значний код", + "Code must be exactly 6 digits": "Код повинен мати точно 6 цифр", + "Enter the 6-digit code found in your authenticator app": "Введіть 6-значний код з вашого додатку аутентифікатора", + "Need help authenticating?": "Потрібна допомога з аутентифікацією?", + "MFA QR Code": "MFA QR-код", + "Account created successfully. Please log in to set up two-factor authentication.": "Обліковий запис успішно створено. Будь ласка, увійдіть, щоб налаштувати двоетапну аутентифікацію.", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "Скидання паролю успішне. Будь ласка, увійдіть за допомогою нового паролю та завершіть двоетапну аутентифікацію.", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "Скидання паролю успішне. Будь ласка, увійдіть за допомогою нового паролю, щоб налаштувати двоетапну аутентифікацію.", + "Password reset was successful. Please log in with your new password.": "Скидання паролю успішне. Будь ласка, увійдіть за допомогою нового паролю.", + "Two-factor authentication": "Двоетапна аутентифікація", + "Use authenticator app instead": "Використовуйте додаток аутентифікатора замість цього", + "Verify backup code": "Перевірити резервний код", + "Use backup code": "Використовуйте резервний код", + "Enter one of your backup codes": "Введіть один з ваших резервних кодів", + "Backup code": "Резервний код", + "Enter one of your backup codes. Each backup code can only be used once.": "Введіть один з ваших резервних кодів. Кожен резервний код можна використовувати лише один раз.", + "Verify": "Перевірити", + "Trash": "Кошик", + "Pages in trash will be permanently deleted after 30 days.": "Сторінки у кошику будуть остаточно видалені через 30 днів.", + "Deleted": "Видалено", + "No pages in trash": "Немає сторінок у кошику", + "Permanently delete page?": "Остаточно видалити сторінку?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "Ви впевнені, що хочете остаточно видалити '{{title}}'? Цю дію не можна скасувати.", + "Restore '{{title}}' and its sub-pages?": "Відновити '{{title}}' та її підсторінки?", + "Move to trash": "Перемістити до кошика", + "Move this page to trash?": "Перемістити цю сторінку до кошика?", + "Restore page": "Відновити сторінку", + "Page moved to trash": "Сторінка переміщена до кошика", + "Page restored successfully": "Сторінку успішно відновлено", + "Deleted by": "Видалено", + "Deleted at": "Видалено о", + "Preview": "Попередній перегляд", + "Subpages": "Підсторінки", + "Failed to load subpages": "Не вдалося завантажити підсторінки", + "No subpages": "Немає підсторінок", + "Subpages (Child pages)": "Підсторінки (дочірні сторінки)", + "List all subpages of the current page": "Перелік всіх підсторінок поточної сторінки", + "Attachments": "Вкладення", + "All spaces": "Усі простори", + "Unknown": "Невідомо", + "Find a space": "Знайти простір", + "Search in all your spaces": "Шукати у всіх ваших просторах", + "Type": "Тип", + "Enterprise": "Підприємство", + "Download attachment": "Завантажити вкладення", + "Allowed email domains": "Дозволені домени електронної пошти", + "Only users with email addresses from these domains can signup via SSO.": "Лише користувачі з адресами електронної пошти з цих доменів можуть реєструватися через SSO.", + "Enter valid domain names separated by comma or space": "Введіть дійсні доменні імена, розділені комою або пробілом", + "Enforce two-factor authentication": "Вимагати двофакторну автентифікацію", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "Після увімкнення всі учасники повинні ввімкнути двофакторну автентифікацію для доступу до робочого простору.", + "Toggle MFA enforcement": "Перемикання вимоги MFA", + "Display name": "Відображуване ім'я", + "Allow signup": "Дозволити реєстрацію", + "Enabled": "Увімкнено", + "Advanced Settings": "Розширені налаштування", + "Enable TLS/SSL": "Увімкнути TLS/SSL", + "Use secure connection to LDAP server": "Використовувати захищене з'єднання з сервером LDAP", + "Group sync": "Синхронізація групи", + "No SSO providers found.": "Постачальників SSO не знайдено.", + "Delete SSO provider": "Видалити постачальника SSO", + "Are you sure you want to delete this SSO provider?": "Ви впевнені, що хочете видалити цього постачальника SSO?", + "Action": "Дія", + "{{ssoProviderType}} configuration": "Конфігурація {{ssoProviderType}}", + "Icon": "Іконка", + "Upload image": "Завантажити зображення", + "Remove image": "Видалити зображення", + "Failed to remove image": "Не вдалося видалити зображення", + "Image exceeds 10MB limit.": "Зображення має займати менше, ніж 10 МБ.", + "Image removed successfully": "Зображення видалено", + "API key": "Ключ API", + "API key created successfully": "Ключ API успішно створено", + "API keys": "Ключі API", + "API management": "Управління API", + "Are you sure you want to revoke this API key": "Ви впевнені, що хочете відкликати цей ключ API", + "Create API Key": "Створити ключ API", + "Custom expiration date": "Користувацька дата закінчення", + "Enter a descriptive token name": "Введіть описову назву токена", + "Expiration": "Термін дії", + "Expired": "Закінчився", + "Expires": "Закінчується", + "I've saved my API key": "Я зберіг свій ключ API", + "Last use": "Останнє використання", + "No API keys found": "Ключі API не знайдено", + "No expiration": "Без терміну дії", + "Revoke API key": "Відкликати ключ API", + "Revoked successfully": "Успішно відкликано", + "Select expiration date": "Виберіть дату закінчення", + "This action cannot be undone. Any applications using this API key will stop working.": "Цю дію не можна скасувати. Будь-які додатки, що використовують цей ключ API, перестануть працювати.", + "Update API key": "Оновити ключ API", + "Manage API keys for all users in the workspace": "Керувати ключами API для всіх користувачів у робочій області", + "AI settings": "Налаштування ШІ", + "AI search": "Пошук з ШІ", + "AI Answer": "Відповідь ШІ", + "Ask AI": "Запитати ШІ", + "AI is thinking...": "ШІ думає...", + "Ask a question...": "Задайте питання...", + "AI-powered search (Ask AI)": "Пошук на базі ШІ (Запитати ШІ)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "Пошук з ШІ використовує векторні вбудовування для надання можливостей семантичного пошуку у вашому робочому вмісті.", + "Toggle AI search": "Переключити пошук з ШІ", + "Sources": "Джерела", + "Ask AI not available for attachments": "Запитати ШІ недоступно для вкладень", + "No answer available": "Відповідь недоступна", + "Background color": "Колір фону", + "Highlight color": "Колір підсвічування", + "Remove color": "Видалити колір" } diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index ee7a6888..a5eb84f1 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -29,6 +29,7 @@ "Choose your preferred interface language.": "选择您喜欢的界面语言。", "Choose your preferred page width.": "选择您喜欢的页面宽度。", "Confirm": "确认", + "Copy as Markdown": "复制为Markdown", "Copy link": "复制链接", "Create": "创建", "Create group": "创建群组", @@ -53,6 +54,7 @@ "e.g Space for product team": "例如:产品团队的空间", "e.g Space for sales team to collaborate": "例如:销售团队协作的空间", "Edit": "编辑", + "Read": "阅读", "Edit group": "编辑群组", "Email": "电子邮箱", "Enter a strong password": "输入一个强密码", @@ -213,7 +215,18 @@ "Comment deleted successfully": "成功删除评论", "Failed to delete comment": "删除评论失败", "Comment resolved successfully": "成功标记评论为解决", + "Comment re-opened successfully": "成功重新打开评论", + "Comment unresolved successfully": "成功标记评论为未解决", "Failed to resolve comment": "标记评论为解决失败", + "Resolve comment": "解决评论", + "Unresolve comment": "取消解决评论", + "Resolve Comment Thread": "解决评论线程", + "Unresolve Comment Thread": "取消解决评论线程", + "Are you sure you want to resolve this comment thread? This will mark it as completed.": "确定要解决此评论线程吗?这将标记为已完成。", + "Are you sure you want to unresolve this comment thread?": "确定要取消解决此评论线程吗?", + "Resolved": "已解决", + "No active comments.": "没有活跃的评论。", + "No resolved comments.": "没有已解决的评论。", "Revoke invitation": "撤回邀请", "Revoke": "撤销", "Don't": "不要", @@ -222,7 +235,9 @@ "Anyone with this link can join this workspace.": "任何拥有此连接的人都可以加入此工作区", "Invite link": "邀请链接", "Copy": "复制", + "Copy to space": "复制到空间", "Copied": "已复制", + "Duplicate": "重复", "Select a user": "选择一个用户", "Select a group": "选择一个组", "Export all pages and attachments in this space.": "导出当前空间的所有页面和附件", @@ -239,6 +254,7 @@ "Export failed:": "导出失败:", "export error": "导出出错", "Export page": "导出页面", + "Export successful": "导出成功", "Export space": "导出空间", "Export {{type}}": "导出为 {{type}}", "File exceeds the {{limit}} attachment limit": "文件超出了 {{limit}} 类型附件限制", @@ -314,6 +330,8 @@ "Upload any image from your device.": "从设备上传任何图像", "Upload any video from your device.": "从设备上传任何视频", "Upload any file from your device.": "从设备上传任何文件", + "Uploading {{name}}": "正在上传{{name}}", + "Uploading file": "正在上传文件", "Table": "表格", "Insert a table.": "插入一个表格", "Insert collapsible block.": "插入一个折叠块", @@ -354,6 +372,9 @@ "Character count: {{characterCount}}": "字符数:{{characterCount}}", "New update": "新更新", "{{latestVersion}} is available": "{{latestVersion}} 已经可以使用", + "Default page edit mode": "默认页面编辑模式", + "Choose your preferred page edit mode. Avoid accidental edits.": "选择您偏好的页面编辑模式。避免意外编辑。", + "Reading": "阅读", "Delete member": "删除成员", "Member deleted successfully": "成员删除成功", "Are you sure you want to delete this workspace member? This action is irreversible.": "您确定要删除此工作区成员吗?此操作不可逆。", @@ -386,5 +407,171 @@ "Failed to share page": "页面分享失败", "Copy page": "复制页面", "Copy page to a different space.": "将页面复制到不同的空间。", - "Page copied successfully": "页面复制成功" + "Page copied successfully": "页面复制成功", + "Page duplicated successfully": "页面复制成功", + "Find": "查找", + "Not found": "未找到", + "Previous Match (Shift+Enter)": "上一个匹配 (Shift+Enter)", + "Next match (Enter)": "下一个匹配 (Enter)", + "Match case (Alt+C)": "区分大小写 (Alt+C)", + "Replace": "替换", + "Close (Escape)": "关闭 (Escape)", + "Replace (Enter)": "替换 (Enter)", + "Replace all (Ctrl+Alt+Enter)": "全部替换 (Ctrl+Alt+Enter)", + "Replace all": "全部替换", + "View all spaces": "查看所有空间", + "Error": "错误", + "Failed to disable MFA": "停用 MFA 失败", + "Disable two-factor authentication": "停用双因素认证", + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.": "停用双因素认证会降低账户安全性。您只需密码即可登录。", + "Please enter your password to disable two-factor authentication:": "请输入您的密码以停用双因素认证:", + "Two-factor authentication has been enabled": "双因素认证已启用", + "Two-factor authentication has been disabled": "双因素认证已停用", + "2-step verification": "两步验证", + "Protect your account with an additional verification layer when signing in.": "通过额外的验证层保护您的账户安全。", + "Two-factor authentication is active on your account.": "您的账户已激活双因素认证。", + "Add 2FA method": "添加 2FA 方法", + "Backup codes": "备份代码", + "Disable": "停用", + "Invalid verification code": "无效的验证码", + "New backup codes have been generated": "已生成新的备份代码", + "Failed to regenerate backup codes": "重新生成备份代码失败", + "About backup codes": "关于备份代码", + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "如果您无法访问身份验证器应用,可使用备份代码访问账户。每个代码仅可使用一次。", + "You can regenerate new backup codes at any time. This will invalidate all existing codes.": "您可以随时重新生成新的备份代码。这将使所有现有代码失效。", + "Confirm password": "确认密码", + "Generate new backup codes": "生成新的备份代码", + "Save your new backup codes": "保存您的新备份代码", + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.": "请确保将这些代码保存在安全的地方。您的旧备份代码不再有效。", + "Your new backup codes": "您的新备份代码", + "I've saved my backup codes": "我已经保存了我的备份代码", + "Failed to setup MFA": "设置 MFA 失败", + "Setup & Verify": "设置并验证", + "Add to authenticator": "添加到身份验证器", + "1. Scan this QR code with your authenticator app": "1. 用身份验证器应用扫描此二维码", + "Can't scan the code?": "无法扫描代码?", + "Enter this code manually in your authenticator app:": "在您的身份验证器应用中手动输入此代码:", + "2. Enter the 6-digit code from your authenticator": "2. 输入来自身份验证器的6位代码", + "Verify and enable": "验证并启用", + "Failed to generate QR code. Please try again.": "生成二维码失败。请重试。", + "Backup": "备份", + "Save codes": "保存代码", + "Save your backup codes": "保存您的备份代码", + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.": "如果无法访问身份验证器应用,可以使用这些代码访问账户。每个代码仅可使用一次。", + "Print": "打印", + "Two-factor authentication has been set up. Please log in again.": "双因素认证已设置。请重新登录。", + "Two-Factor authentication required": "需要双因素认证", + "Your workspace requires two-factor authentication for all users": "您的工作区要求所有用户启用双因素认证", + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.": "要继续访问工作区,必须设置双因素认证。此操作为您的账户添加一层额外的安全保障。", + "Set up two-factor authentication": "设置双因素认证", + "Cancel and logout": "取消并退出登录", + "Your workspace requires two-factor authentication. Please set it up to continue.": "您的工作区需要双因素认证。请设置以继续。", + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.": "通过要求您的身份验证器应用提供验证码,此操作为您的账户增加了一层额外的安全保障。", + "Password is required": "需要密码", + "Password must be at least 8 characters": "密码必须至少包含8个字符", + "Please enter a 6-digit code": "请输入6位代码", + "Code must be exactly 6 digits": "代码必须正好是6位", + "Enter the 6-digit code found in your authenticator app": "输入在您的身份验证器应用中找到的6位代码", + "Need help authenticating?": "需要帮助进行身份验证吗?", + "MFA QR Code": "MFA二维码", + "Account created successfully. Please log in to set up two-factor authentication.": "账户创建成功。请登录以设置双因素认证。", + "Password reset successful. Please log in with your new password and complete two-factor authentication.": "密码重置成功。请使用新密码登录并完成双因素认证。", + "Password reset successful. Please log in with your new password to set up two-factor authentication.": "密码重置成功。请使用新密码登录以设置双因素认证。", + "Password reset was successful. Please log in with your new password.": "密码重置成功。请使用新密码登录。", + "Two-factor authentication": "双因素认证", + "Use authenticator app instead": "改用身份验证器应用", + "Verify backup code": "验证备份代码", + "Use backup code": "使用备份代码", + "Enter one of your backup codes": "输入您的一个备份代码", + "Backup code": "备份代码", + "Enter one of your backup codes. Each backup code can only be used once.": "输入您的一个备份代码。每个备份代码只能使用一次。", + "Verify": "验证", + "Trash": "垃圾箱", + "Pages in trash will be permanently deleted after 30 days.": "垃圾箱中的页面将在30天后被永久删除。", + "Deleted": "已删除", + "No pages in trash": "垃圾箱中没有页面", + "Permanently delete page?": "永久删除页面?", + "Are you sure you want to permanently delete '{{title}}'? This action cannot be undone.": "确定要永久删除“{{title}}”吗?此操作无法撤销。", + "Restore '{{title}}' and its sub-pages?": "恢复“{{title}}”及其子页面?", + "Move to trash": "移至垃圾箱", + "Move this page to trash?": "将此页面移至垃圾箱?", + "Restore page": "恢复页面", + "Page moved to trash": "页面已移至垃圾箱", + "Page restored successfully": "页面恢复成功", + "Deleted by": "删除人", + "Deleted at": "删除时间", + "Preview": "预览", + "Subpages": "子页面", + "Failed to load subpages": "加载子页面失败", + "No subpages": "没有子页面", + "Subpages (Child pages)": "子页面(子页面)", + "List all subpages of the current page": "列出当前页面的所有子页面", + "Attachments": "附件", + "All spaces": "所有空间", + "Unknown": "未知", + "Find a space": "查找空间", + "Search in all your spaces": "在您的所有空间中搜索", + "Type": "类型", + "Enterprise": "企业", + "Download attachment": "下载附件", + "Allowed email domains": "允许的电子邮件域", + "Only users with email addresses from these domains can signup via SSO.": "只有来自这些域的电子邮件地址的用户才能通过SSO注册。", + "Enter valid domain names separated by comma or space": "输入用逗号或空格分隔的有效域名", + "Enforce two-factor authentication": "强制实施双因素认证", + "Once enforced, all members must enable two-factor authentication to access the workspace.": "一旦实施,所有成员必须启用双因素认证才能访问工作区。", + "Toggle MFA enforcement": "切换多因素认证实施", + "Display name": "显示名称", + "Allow signup": "允许注册", + "Enabled": "已启用", + "Advanced Settings": "高级设置", + "Enable TLS/SSL": "启用TLS/SSL", + "Use secure connection to LDAP server": "使用安全连接到LDAP服务器", + "Group sync": "组同步", + "No SSO providers found.": "未找到SSO提供商。", + "Delete SSO provider": "删除SSO提供商", + "Are you sure you want to delete this SSO provider?": "您确定要删除此SSO提供商吗?", + "Action": "操作", + "{{ssoProviderType}} configuration": "{{ssoProviderType}} 配置", + "Icon": "图标", + "Upload image": "上传图片", + "Remove image": "删除图片", + "Failed to remove image": "无法删除图片", + "Image exceeds 10MB limit.": "图片超过10MB限制。", + "Image removed successfully": "图片删除成功", + "API key": "API密钥", + "API key created successfully": "API密钥创建成功", + "API keys": "API密钥", + "API management": "API管理", + "Are you sure you want to revoke this API key": "确定要撤销此API密钥吗", + "Create API Key": "创建API密钥", + "Custom expiration date": "自定义到期日期", + "Enter a descriptive token name": "输入描述性令牌名称", + "Expiration": "到期", + "Expired": "已过期", + "Expires": "到期", + "I've saved my API key": "我已保存我的API密钥", + "Last use": "上次使用", + "No API keys found": "找不到API密钥", + "No expiration": "无到期", + "Revoke API key": "撤销API密钥", + "Revoked successfully": "撤销成功", + "Select expiration date": "选择到期日期", + "This action cannot be undone. Any applications using this API key will stop working.": "此操作无法撤销。使用此API密钥的任何应用程序将停止工作。", + "Update API key": "更新API密钥", + "Manage API keys for all users in the workspace": "管理工作空间中所有用户的API密钥", + "AI settings": "AI设置", + "AI search": "AI搜索", + "AI Answer": "AI回答", + "Ask AI": "询问AI", + "AI is thinking...": "AI正在思考...", + "Ask a question...": "提问...", + "AI-powered search (Ask AI)": "AI驱动的搜索(询问AI)", + "AI search uses vector embeddings to provide semantic search capabilities across your workspace content.": "AI搜索使用向量嵌入提供跨工作空间内容的语义搜索功能。", + "Toggle AI search": "切换AI搜索", + "Sources": "来源", + "Ask AI not available for attachments": "附件不支持询问AI", + "No answer available": "无可用答案", + "Background color": "背景颜色", + "Highlight color": "突出显示颜色", + "Remove color": "移除颜色" } diff --git a/apps/client/public/manifest.json b/apps/client/public/manifest.json new file mode 100644 index 00000000..3e4b35dd --- /dev/null +++ b/apps/client/public/manifest.json @@ -0,0 +1,30 @@ +{ + "name": "Docmost", + "short_name": "Docmost", + "start_url": "/", + "display": "standalone", + "background_color": "#222", + "theme_color": "#222", + "icons": [ + { + "src": "icons/favicon-16x16.png", + "type": "image/png", + "sizes": "16x16" + }, + { + "src": "icons/favicon-32x32.png", + "type": "image/png", + "sizes": "32x32" + }, + { + "src": "icons/app-icon-192x192.png", + "type": "image/png", + "sizes": "180x180 192x192" + }, + { + "src": "icons/app-icon-512x512.png", + "type": "image/png", + "sizes": "512x512" + } + ] +} diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 6fab378c..e0df67a7 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -29,8 +29,15 @@ import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-selec import SharedPage from "@/pages/share/shared-page.tsx"; import Shares from "@/pages/settings/shares/shares.tsx"; import ShareLayout from "@/features/share/components/share-layout.tsx"; -import ShareRedirect from '@/pages/share/share-redirect.tsx'; +import ShareRedirect from "@/pages/share/share-redirect.tsx"; import { useTrackOrigin } from "@/hooks/use-track-origin"; +import SpacesPage from "@/pages/spaces/spaces.tsx"; +import { MfaChallengePage } from "@/ee/mfa/pages/mfa-challenge-page"; +import { MfaSetupRequiredPage } from "@/ee/mfa/pages/mfa-setup-required-page"; +import SpaceTrash from "@/pages/space/space-trash.tsx"; +import UserApiKeys from "@/ee/api-key/pages/user-api-keys"; +import WorkspaceApiKeys from "@/ee/api-key/pages/workspace-api-keys"; +import AiSettings from "@/ee/ai/pages/ai-settings.tsx"; export default function App() { const { t } = useTranslation(); @@ -45,6 +52,8 @@ export default function App() { } /> } /> } /> + } /> + } /> {!isCloud() && ( } /> @@ -58,7 +67,10 @@ export default function App() { )} }> - } /> + } + /> } /> @@ -67,7 +79,9 @@ export default function App() { }> } /> + } /> } /> + } /> } /> + } /> } /> } /> + } /> } /> } /> } /> } /> } /> + } /> {!isCloud() && } />} {isCloud() && } />} diff --git a/apps/client/src/components/common/avatar-uploader.tsx b/apps/client/src/components/common/avatar-uploader.tsx new file mode 100644 index 00000000..0c83411c --- /dev/null +++ b/apps/client/src/components/common/avatar-uploader.tsx @@ -0,0 +1,165 @@ +import React, { useRef } from "react"; +import { Menu, Box, Loader } from "@mantine/core"; +import { useTranslation } from "react-i18next"; +import { IconTrash, IconUpload } from "@tabler/icons-react"; +import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; +import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts"; +import { notifications } from "@mantine/notifications"; + +interface AvatarUploaderProps { + currentImageUrl?: string | null; + fallbackName?: string; + radius?: string | number; + size?: string | number; + variant?: string; + type: AvatarIconType; + onUpload: (file: File) => Promise; + onRemove: () => Promise; + isLoading?: boolean; + disabled?: boolean; +} + +export default function AvatarUploader({ + currentImageUrl, + fallbackName, + radius, + variant, + size, + type, + onUpload, + onRemove, + isLoading = false, + disabled = false, +}: AvatarUploaderProps) { + const { t } = useTranslation(); + const fileInputRef = useRef(null); + + const handleFileInputChange = async ( + event: React.ChangeEvent, + ) => { + const file = event.target.files?.[0]; + if (!file || disabled) { + return; + } + + // Validate file size (max 10MB) + const maxSizeInBytes = 10 * 1024 * 1024; + if (file.size > maxSizeInBytes) { + notifications.show({ + message: t("Image exceeds 10MB limit."), + color: "red", + }); + // Reset the input + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + return; + } + + try { + await onUpload(file); + } catch (error) { + console.error(error); + notifications.show({ + message: t("Failed to upload image"), + color: "red", + }); + } + + // Reset the input so the same file can be selected again + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }; + + const handleUploadClick = () => { + if (fileInputRef.current) { + fileInputRef.current.click(); + } else { + console.error("File input ref is null!"); + } + }; + + const handleRemove = async () => { + if (disabled) return; + + try { + await onRemove(); + notifications.show({ + message: t("Image removed successfully"), + }); + } catch (error) { + console.error(error); + notifications.show({ + message: t("Failed to remove image"), + color: "red", + }); + } + }; + + return ( + + + + + + + + {isLoading && ( + + + + )} + + + + + } + disabled={isLoading || disabled} + onClick={handleUploadClick} + > + {t("Upload image")} + + + {currentImageUrl && ( + } + color="red" + onClick={handleRemove} + disabled={isLoading || disabled} + > + {t("Remove image")} + + )} + + + + ); +} diff --git a/apps/client/src/components/common/export-modal.tsx b/apps/client/src/components/common/export-modal.tsx index a53094d4..53de8246 100644 --- a/apps/client/src/components/common/export-modal.tsx +++ b/apps/client/src/components/common/export-modal.tsx @@ -29,19 +29,27 @@ export default function ExportModal({ }: ExportModalProps) { const [format, setFormat] = useState(ExportFormat.Markdown); const [includeChildren, setIncludeChildren] = useState(false); - const [includeAttachments, setIncludeAttachments] = useState(true); + const [includeAttachments, setIncludeAttachments] = useState(false); + const [isExporting, setIsExporting] = useState(false); const { t } = useTranslation(); const handleExport = async () => { + setIsExporting(true); try { if (type === "page") { - await exportPage({ pageId: id, format, includeChildren }); + await exportPage({ + pageId: id, + format, + includeChildren, + includeAttachments, + }); } if (type === "space") { await exportSpace({ spaceId: id, format, includeAttachments }); } - setIncludeChildren(false); - setIncludeAttachments(true); + notifications.show({ + message: t("Export successful"), + }); onClose(); } catch (err) { notifications.show({ @@ -49,6 +57,8 @@ export default function ExportModal({ color: "red", }); console.error("export error", err); + } finally { + setIsExporting(false); } }; @@ -96,6 +106,18 @@ export default function ExportModal({ checked={includeChildren} /> + + +
+ {t("Include attachments")} +
+ + setIncludeAttachments(event.currentTarget.checked) + } + checked={includeAttachments} + /> +
)} @@ -121,7 +143,7 @@ export default function ExportModal({ - + diff --git a/apps/client/src/components/common/no-table-results.tsx b/apps/client/src/components/common/no-table-results.tsx index 124bbb9b..0f34fa2f 100644 --- a/apps/client/src/components/common/no-table-results.tsx +++ b/apps/client/src/components/common/no-table-results.tsx @@ -4,14 +4,15 @@ import { useTranslation } from "react-i18next"; interface NoTableResultsProps { colSpan: number; + text?: string; } -export default function NoTableResults({ colSpan }: NoTableResultsProps) { +export default function NoTableResults({ colSpan, text }: NoTableResultsProps) { const { t } = useTranslation(); return ( - {t("No results found...")} + {text || t("No results found...")} diff --git a/apps/client/src/components/common/paginate.tsx b/apps/client/src/components/common/paginate.tsx index d8e8106f..721c2f43 100644 --- a/apps/client/src/components/common/paginate.tsx +++ b/apps/client/src/components/common/paginate.tsx @@ -2,17 +2,17 @@ import { Button, Group } from "@mantine/core"; import { useTranslation } from "react-i18next"; export interface PagePaginationProps { - currentPage: number; hasPrevPage: boolean; hasNextPage: boolean; - onPageChange: (newPage: number) => void; + onPrev: () => void; + onNext: () => void; } export default function Paginate({ - currentPage, hasPrevPage, hasNextPage, - onPageChange, + onPrev, + onNext, }: PagePaginationProps) { const { t } = useTranslation(); @@ -25,7 +25,7 @@ export default function Paginate({ + + + ); +} diff --git a/apps/client/src/ee/api-key/components/api-key-table.tsx b/apps/client/src/ee/api-key/components/api-key-table.tsx new file mode 100644 index 00000000..48757acc --- /dev/null +++ b/apps/client/src/ee/api-key/components/api-key-table.tsx @@ -0,0 +1,143 @@ +import { ActionIcon, Group, Menu, Table, Text } from "@mantine/core"; +import { IconDots, IconEdit, IconTrash } from "@tabler/icons-react"; +import { format } from "date-fns"; +import { useTranslation } from "react-i18next"; +import { IApiKey } from "@/ee/api-key"; +import { CustomAvatar } from "@/components/ui/custom-avatar.tsx"; +import React from "react"; +import NoTableResults from "@/components/common/no-table-results"; + +interface ApiKeyTableProps { + apiKeys: IApiKey[]; + isLoading?: boolean; + showUserColumn?: boolean; + onUpdate?: (apiKey: IApiKey) => void; + onRevoke?: (apiKey: IApiKey) => void; +} + +export function ApiKeyTable({ + apiKeys, + isLoading, + showUserColumn = false, + onUpdate, + onRevoke, +}: ApiKeyTableProps) { + const { t } = useTranslation(); + + const formatDate = (date: Date | string | null) => { + if (!date) return t("Never"); + return format(new Date(date), "MMM dd, yyyy"); + }; + + const isExpired = (expiresAt: string | null) => { + if (!expiresAt) return false; + return new Date(expiresAt) < new Date(); + }; + + return ( + + + + + {t("Name")} + {showUserColumn && {t("User")}} + {t("Last used")} + {t("Expires")} + {t("Created")} + + + + + + {apiKeys && apiKeys.length > 0 ? ( + apiKeys.map((apiKey: IApiKey, index: number) => ( + + + + {apiKey.name} + + + + {showUserColumn && apiKey.creator && ( + + + + + {apiKey.creator.name} + + + + )} + + + + {formatDate(apiKey.lastUsedAt)} + + + + + {apiKey.expiresAt ? ( + isExpired(apiKey.expiresAt) ? ( + + {t("Expired")} + + ) : ( + + {formatDate(apiKey.expiresAt)} + + ) + ) : ( + + {t("Never")} + + )} + + + + + {formatDate(apiKey.createdAt)} + + + + + + + + + + + + {onUpdate && ( + } + onClick={() => onUpdate(apiKey)} + > + {t("Rename")} + + )} + {onRevoke && ( + } + color="red" + onClick={() => onRevoke(apiKey)} + > + {t("Revoke")} + + )} + + + + + )) + ) : ( + + )} + +
+
+ ); +} diff --git a/apps/client/src/ee/api-key/components/create-api-key-modal.tsx b/apps/client/src/ee/api-key/components/create-api-key-modal.tsx new file mode 100644 index 00000000..cade36e8 --- /dev/null +++ b/apps/client/src/ee/api-key/components/create-api-key-modal.tsx @@ -0,0 +1,153 @@ +import { lazy, Suspense, useState } from "react"; +import { Modal, TextInput, Button, Group, Stack, Select } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { z } from "zod"; +import { useTranslation } from "react-i18next"; +import { useCreateApiKeyMutation } from "@/ee/api-key/queries/api-key-query"; +import { IconCalendar } from "@tabler/icons-react"; +import { IApiKey } from "@/ee/api-key"; + +const DateInput = lazy(() => + import("@mantine/dates").then((module) => ({ + default: module.DateInput, + })), +); + +interface CreateApiKeyModalProps { + opened: boolean; + onClose: () => void; + onSuccess: (response: IApiKey) => void; +} + +const formSchema = z.object({ + name: z.string().min(1, "Name is required"), + expiresAt: z.string().optional(), +}); +type FormValues = z.infer; + +export function CreateApiKeyModal({ + opened, + onClose, + onSuccess, +}: CreateApiKeyModalProps) { + const { t } = useTranslation(); + const [expirationOption, setExpirationOption] = useState("30"); + const createApiKeyMutation = useCreateApiKeyMutation(); + + const form = useForm({ + validate: zodResolver(formSchema), + initialValues: { + name: "", + expiresAt: "", + }, + }); + + const getExpirationDate = (): string | undefined => { + if (expirationOption === "never") { + return undefined; + } + if (expirationOption === "custom") { + return form.values.expiresAt; + } + const days = parseInt(expirationOption); + const date = new Date(); + date.setDate(date.getDate() + days); + return date.toISOString(); + }; + + const getExpirationLabel = (days: number) => { + const date = new Date(); + date.setDate(date.getDate() + days); + const formatted = date.toLocaleDateString("en-US", { + month: "short", + day: "2-digit", + year: "numeric", + }); + return `${days} days (${formatted})`; + }; + + const expirationOptions = [ + { value: "30", label: getExpirationLabel(30) }, + { value: "60", label: getExpirationLabel(60) }, + { value: "90", label: getExpirationLabel(90) }, + { value: "365", label: getExpirationLabel(365) }, + { value: "custom", label: t("Custom") }, + { value: "never", label: t("No expiration") }, + ]; + + const handleSubmit = async (data: { + name?: string; + expiresAt?: string | Date; + }) => { + const groupData = { + name: data.name, + expiresAt: getExpirationDate(), + }; + + try { + const createdKey = await createApiKeyMutation.mutateAsync(groupData); + onSuccess(createdKey); + form.reset(); + onClose(); + } catch (err) { + // + } + }; + + const handleClose = () => { + form.reset(); + setExpirationOption("30"); + onClose(); + }; + + return ( + +
handleSubmit(values))}> + + + + + )} - - {plan.name} - - - {interval === "monthly" && ( - <> - ${price}{" "} - - /user/month - - - )} - {interval === "yearly" && ( - <> - ${yearlyMonthPrice}{" "} - - /user/month - - - )} -
- - billed {interval} - -
- - - - - - - + + Monthly + setIsAnnual(event.target.checked)} size="sm" - center - icon={ - - - - } - > - {plan.features.map((feature, index) => ( - {feature} - ))} - - - - ); - })} - + /> + + Annually + + 15% OFF + + + + + +
+ + {/* Plans Grid */} + + {plans.map((plan, index) => { + let price; + let displayPrice; + const priceId = isAnnual ? plan.yearlyId : plan.monthlyId; + + if (plan.billingScheme === 'tiered' && plan.pricingTiers?.length > 0) { + // Tiered billing logic + const planSelectedTier = + plan.pricingTiers.find( + (tier) => tier.upTo.toString() === selectedTierValue, + ) || plan.pricingTiers[0]; + + price = isAnnual + ? planSelectedTier.yearly + : planSelectedTier.monthly; + displayPrice = isAnnual ? (price / 12).toFixed(0) : price; + } else { + // Per-unit billing logic + const monthlyPrice = parseFloat(plan.price?.monthly || '0'); + const yearlyPrice = parseFloat(plan.price?.yearly || '0'); + price = isAnnual ? yearlyPrice : monthlyPrice; + displayPrice = isAnnual ? (yearlyPrice / 12).toFixed(0) : monthlyPrice; + } + + return ( + + + {/* Plan Header */} + + + {plan.name} + + {plan.description && ( + + {plan.description} + + )} + + + {/* Pricing */} + + + + ${displayPrice} + + + {plan.billingScheme === 'per_unit' + ? `per user/month` + : `per month`} + + + + {isAnnual ? "Billed annually" : "Billed monthly"} + + {plan.billingScheme === 'tiered' && plan.pricingTiers && ( + + For {plan.pricingTiers.find(tier => tier.upTo.toString() === selectedTierValue)?.upTo || plan.pricingTiers[0].upTo} users + + )} + + + {/* CTA Button */} + + + {/* Features */} + + + + } + > + {plan.features.map((feature, featureIndex) => ( + {feature} + ))} + + + + ); + })} + + ); } diff --git a/apps/client/src/ee/billing/types/billing.types.ts b/apps/client/src/ee/billing/types/billing.types.ts index 240e55fd..58225519 100644 --- a/apps/client/src/ee/billing/types/billing.types.ts +++ b/apps/client/src/ee/billing/types/billing.types.ts @@ -25,6 +25,11 @@ export interface IBilling { createdAt: Date; updatedAt: Date; deletedAt: Date; + billingScheme: string | null; + tieredUpTo: string | null; + tieredFlatAmount: number | null; + tieredUnitAmount: number | null; + planName: string | null; } export interface ICheckoutLink { @@ -42,9 +47,18 @@ export interface IBillingPlan { monthlyId: string; yearlyId: string; currency: string; - price: { + price?: { monthly: string; yearly: string; }; features: string[]; + billingScheme: string | null; + pricingTiers?: PricingTier[]; } + +interface PricingTier { + upTo: number; + monthly?: number; + yearly?: number; + custom?: boolean; +} \ No newline at end of file diff --git a/apps/client/src/ee/comment/components/resolve-comment.tsx b/apps/client/src/ee/comment/components/resolve-comment.tsx new file mode 100644 index 00000000..4e22fb71 --- /dev/null +++ b/apps/client/src/ee/comment/components/resolve-comment.tsx @@ -0,0 +1,67 @@ +import { ActionIcon, Tooltip } from "@mantine/core"; +import { IconCircleCheck, IconCircleCheckFilled } from "@tabler/icons-react"; +import { useResolveCommentMutation } from "@/ee/comment/queries/comment-query"; +import { useTranslation } from "react-i18next"; +import { Editor } from "@tiptap/react"; + +interface ResolveCommentProps { + editor: Editor; + commentId: string; + pageId: string; + resolvedAt?: Date; +} + +function ResolveComment({ + editor, + commentId, + pageId, + resolvedAt, +}: ResolveCommentProps) { + const { t } = useTranslation(); + const resolveCommentMutation = useResolveCommentMutation(); + + const isResolved = resolvedAt != null; + const iconColor = isResolved ? "green" : "gray"; + + const handleResolveToggle = async () => { + try { + await resolveCommentMutation.mutateAsync({ + commentId, + pageId, + resolved: !isResolved, + }); + + if (editor) { + editor.commands.setCommentResolved(commentId, !isResolved); + } + + // + } catch (error) { + console.error("Failed to toggle resolved state:", error); + } + }; + + return ( + + + {isResolved ? ( + + ) : ( + + )} + + + ); +} + +export default ResolveComment; diff --git a/apps/client/src/ee/comment/queries/comment-query.ts b/apps/client/src/ee/comment/queries/comment-query.ts new file mode 100644 index 00000000..b09f4e79 --- /dev/null +++ b/apps/client/src/ee/comment/queries/comment-query.ts @@ -0,0 +1,87 @@ +import { + useMutation, + useQueryClient, +} from "@tanstack/react-query"; +import { resolveComment } from "@/features/comment/services/comment-service"; +import { + IComment, + IResolveComment, +} from "@/features/comment/types/comment.types"; +import { notifications } from "@mantine/notifications"; +import { IPagination } from "@/lib/types.ts"; +import { useTranslation } from "react-i18next"; +import { useQueryEmit } from "@/features/websocket/use-query-emit"; +import { RQ_KEY } from "@/features/comment/queries/comment-query"; + +export function useResolveCommentMutation() { + const queryClient = useQueryClient(); + const { t } = useTranslation(); + const emit = useQueryEmit(); + + return useMutation({ + mutationFn: (data: IResolveComment) => resolveComment(data), + onMutate: async (variables) => { + await queryClient.cancelQueries({ queryKey: RQ_KEY(variables.pageId) }); + const previousComments = queryClient.getQueryData(RQ_KEY(variables.pageId)); + queryClient.setQueryData(RQ_KEY(variables.pageId), (old: IPagination) => { + if (!old || !old.items) return old; + const updatedItems = old.items.map((comment) => + comment.id === variables.commentId + ? { + ...comment, + resolvedAt: variables.resolved ? new Date() : null, + resolvedById: variables.resolved ? 'optimistic-user' : null, + resolvedBy: variables.resolved ? { id: 'optimistic-user', name: 'Resolving...', avatarUrl: null } : null + } + : comment, + ); + return { + ...old, + items: updatedItems, + }; + }); + return { previousComments }; + }, + onError: (err, variables, context) => { + if (context?.previousComments) { + queryClient.setQueryData(RQ_KEY(variables.pageId), context.previousComments); + } + notifications.show({ + message: t("Failed to resolve comment"), + color: "red", + }); + }, + onSuccess: (data: IComment, variables) => { + const pageId = data.pageId; + const currentComments = queryClient.getQueryData( + RQ_KEY(pageId), + ) as IPagination; + if (currentComments && currentComments.items) { + const updatedComments = currentComments.items.map((comment) => + comment.id === variables.commentId + ? { ...comment, resolvedAt: data.resolvedAt, resolvedById: data.resolvedById, resolvedBy: data.resolvedBy } + : comment, + ); + queryClient.setQueryData(RQ_KEY(pageId), { + ...currentComments, + items: updatedComments, + }); + } + emit({ + operation: "resolveComment", + pageId: pageId, + commentId: variables.commentId, + resolved: variables.resolved, + resolvedAt: data.resolvedAt, + resolvedById: data.resolvedById, + resolvedBy: data.resolvedBy, + }); + queryClient.invalidateQueries({ queryKey: RQ_KEY(pageId) }); + notifications.show({ + message: variables.resolved + ? t("Comment resolved successfully") + : t("Comment re-opened successfully") + }); + }, + }); +} \ No newline at end of file diff --git a/apps/client/src/ee/components/ldap-login-modal.tsx b/apps/client/src/ee/components/ldap-login-modal.tsx new file mode 100644 index 00000000..9360651d --- /dev/null +++ b/apps/client/src/ee/components/ldap-login-modal.tsx @@ -0,0 +1,124 @@ +import React, { useState } from "react"; +import { Modal, TextInput, PasswordInput, Button, Stack } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { z } from "zod"; +import { notifications } from "@mantine/notifications"; +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { IAuthProvider } from "@/ee/security/types/security.types"; +import APP_ROUTE from "@/lib/app-route"; +import { ldapLogin } from "@/ee/security/services/ldap-auth-service"; + +const formSchema = z.object({ + username: z.string().min(1, { message: "Username is required" }), + password: z.string().min(1, { message: "Password is required" }), +}); + +interface LdapLoginModalProps { + opened: boolean; + onClose: () => void; + provider: IAuthProvider; + workspaceId: string; +} + +export function LdapLoginModal({ + opened, + onClose, + provider, + workspaceId, +}: LdapLoginModalProps) { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const form = useForm({ + validate: zodResolver(formSchema), + initialValues: { + username: "", + password: "", + }, + }); + + const handleSubmit = async (values: { + username: string; + password: string; + }) => { + setIsLoading(true); + setError(null); + + try { + const response = await ldapLogin({ + username: values.username, + password: values.password, + providerId: provider.id, + workspaceId, + }); + + // Handle MFA like the regular login + if (response?.userHasMfa) { + onClose(); + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + } else if (response?.requiresMfaSetup) { + onClose(); + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + } else { + onClose(); + navigate(APP_ROUTE.HOME); + } + } catch (err: any) { + setIsLoading(false); + const errorMessage = + err.response?.data?.message || "Authentication failed"; + setError(errorMessage); + + notifications.show({ + message: errorMessage, + color: "red", + }); + } + }; + + const handleClose = () => { + form.reset(); + setError(null); + onClose(); + }; + + return ( + + + + + + + + + + + + ); +} diff --git a/apps/client/src/ee/components/posthog-user.tsx b/apps/client/src/ee/components/posthog-user.tsx new file mode 100644 index 00000000..893b0de9 --- /dev/null +++ b/apps/client/src/ee/components/posthog-user.tsx @@ -0,0 +1,41 @@ +import { usePostHog } from "posthog-js/react"; +import { useEffect } from "react"; +import { useAtom } from "jotai"; +import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts"; + +export function PosthogUser() { + const posthog = usePostHog(); + const [currentUser] = useAtom(currentUserAtom); + + useEffect(() => { + if (currentUser) { + const user = currentUser?.user; + const workspace = currentUser?.workspace; + if (!user || !workspace) return; + + posthog?.identify(user.id, { + name: user.name, + email: user.email, + workspaceId: user.workspaceId, + workspaceHostname: workspace.hostname, + lastActiveAt: new Date().toISOString(), + createdAt: user.createdAt, + source: "docmost-app", + }); + posthog?.group("workspace", workspace.id, { + name: workspace.name, + hostname: workspace.hostname, + plan: workspace?.plan, + status: workspace.status, + isOnTrial: !!workspace.trialEndAt, + hasStripeCustomerId: !!workspace.stripeCustomerId, + memberCount: workspace.memberCount, + lastActiveAt: new Date().toISOString(), + createdAt: workspace.createdAt, + source: "docmost-app", + }); + } + }, [posthog, currentUser]); + + return null; +} diff --git a/apps/client/src/ee/components/sso-login.tsx b/apps/client/src/ee/components/sso-login.tsx index 8de93c29..8c96d9c5 100644 --- a/apps/client/src/ee/components/sso-login.tsx +++ b/apps/client/src/ee/components/sso-login.tsx @@ -1,29 +1,62 @@ +import { useState } from "react"; import { useWorkspacePublicDataQuery } from "@/features/workspace/queries/workspace-query.ts"; import { Button, Divider, Stack } from "@mantine/core"; -import { IconLock } from "@tabler/icons-react"; +import { IconLock, IconServer } from "@tabler/icons-react"; import { IAuthProvider } from "@/ee/security/types/security.types.ts"; import { buildSsoLoginUrl } from "@/ee/security/sso.utils.ts"; import { SSO_PROVIDER } from "@/ee/security/contants.ts"; import { GoogleIcon } from "@/components/icons/google-icon.tsx"; import { isCloud } from "@/lib/config.ts"; +import { LdapLoginModal } from "@/ee/components/ldap-login-modal.tsx"; export default function SsoLogin() { const { data, isLoading } = useWorkspacePublicDataQuery(); + const [ldapModalOpened, setLdapModalOpened] = useState(false); + const [selectedLdapProvider, setSelectedLdapProvider] = useState(null); if (!data?.authProviders || data?.authProviders?.length === 0) { return null; } const handleSsoLogin = (provider: IAuthProvider) => { - window.location.href = buildSsoLoginUrl({ - providerId: provider.id, - type: provider.type, - workspaceId: data.id, - }); + if (provider.type === SSO_PROVIDER.LDAP) { + // Open modal for LDAP instead of redirecting + setSelectedLdapProvider(provider); + setLdapModalOpened(true); + } else { + // Redirect for other SSO providers + window.location.href = buildSsoLoginUrl({ + providerId: provider.id, + type: provider.type, + workspaceId: data.id, + }); + } + }; + + const getProviderIcon = (provider: IAuthProvider) => { + if (provider.type === SSO_PROVIDER.GOOGLE) { + return ; + } else if (provider.type === SSO_PROVIDER.LDAP) { + return ; + } else { + return ; + } }; return ( <> + {selectedLdapProvider && ( + { + setLdapModalOpened(false); + setSelectedLdapProvider(null); + }} + provider={selectedLdapProvider} + workspaceId={data.id} + /> + )} + {(isCloud() || data.hasLicenseKey) && ( <> @@ -31,13 +64,7 @@ export default function SsoLogin() {
+ + + + + ); +} \ No newline at end of file diff --git a/apps/client/src/ee/mfa/components/mfa-backup-codes-modal.tsx b/apps/client/src/ee/mfa/components/mfa-backup-codes-modal.tsx new file mode 100644 index 00000000..c24638fe --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa-backup-codes-modal.tsx @@ -0,0 +1,208 @@ +import React, { useState } from "react"; +import { + Modal, + Stack, + Text, + Button, + Paper, + Group, + List, + Code, + CopyButton, + Alert, + PasswordInput, +} from "@mantine/core"; +import { + IconRefresh, + IconCopy, + IconCheck, + IconAlertCircle, +} from "@tabler/icons-react"; +import { useMutation } from "@tanstack/react-query"; +import { notifications } from "@mantine/notifications"; +import { useTranslation } from "react-i18next"; +import { regenerateBackupCodes } from "@/ee/mfa"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { z } from "zod"; +import useCurrentUser from "@/features/user/hooks/use-current-user"; + +interface MfaBackupCodesModalProps { + opened: boolean; + onClose: () => void; +} + +export function MfaBackupCodesModal({ + opened, + onClose, +}: MfaBackupCodesModalProps) { + const { t } = useTranslation(); + const { data: currentUser } = useCurrentUser(); + const [backupCodes, setBackupCodes] = useState([]); + const [showNewCodes, setShowNewCodes] = useState(false); + const requiresPassword = !currentUser?.user?.hasGeneratedPassword; + + const formSchema = requiresPassword + ? z.object({ + confirmPassword: z.string().min(1, { message: "Password is required" }), + }) + : z.object({ + confirmPassword: z.string().optional(), + }); + + const form = useForm({ + validate: zodResolver(formSchema), + initialValues: { + confirmPassword: "", + }, + }); + + const regenerateMutation = useMutation({ + mutationFn: (data: { confirmPassword?: string }) => + regenerateBackupCodes(data), + onSuccess: (data) => { + setBackupCodes(data.backupCodes); + setShowNewCodes(true); + form.reset(); + notifications.show({ + title: t("Success"), + message: t("New backup codes have been generated"), + }); + }, + onError: (error: any) => { + notifications.show({ + title: t("Error"), + message: + error.response?.data?.message || + t("Failed to regenerate backup codes"), + color: "red", + }); + }, + }); + + const handleRegenerate = (values: { confirmPassword?: string }) => { + // Only send confirmPassword if it's required (non-SSO users) + const payload = requiresPassword + ? { confirmPassword: values.confirmPassword } + : {}; + regenerateMutation.mutate(payload); + }; + + const handleClose = () => { + setShowNewCodes(false); + setBackupCodes([]); + form.reset(); + onClose(); + }; + + return ( + + + {!showNewCodes ? ( +
+ + } + title={t("About backup codes")} + color="blue" + variant="light" + > + + {t( + "Backup codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.", + )} + + + + + {t( + "You can regenerate new backup codes at any time. This will invalidate all existing codes.", + )} + + + {requiresPassword && ( + + )} + + + +
+ ) : ( + <> + } + title={t("Save your new backup codes")} + color="yellow" + > + + {t( + "Make sure to save these codes in a secure place. Your old backup codes are no longer valid.", + )} + + + + + + + {t("Your new backup codes")} + + + {({ copied, copy }) => ( + + )} + + + + {backupCodes.map((code, index) => ( + + {code} + + ))} + + + + + + )} +
+
+ ); +} diff --git a/apps/client/src/ee/mfa/components/mfa-challenge.module.css b/apps/client/src/ee/mfa/components/mfa-challenge.module.css new file mode 100644 index 00000000..45eb5df9 --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa-challenge.module.css @@ -0,0 +1,12 @@ +.container { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; +} + +.paper { + width: 100%; + box-shadow: var(--mantine-shadow-lg); +} \ No newline at end of file diff --git a/apps/client/src/ee/mfa/components/mfa-challenge.tsx b/apps/client/src/ee/mfa/components/mfa-challenge.tsx new file mode 100644 index 00000000..8a9bef53 --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa-challenge.tsx @@ -0,0 +1,161 @@ +import React, { useState } from "react"; +import { + Container, + Title, + Text, + PinInput, + Button, + Stack, + Anchor, + Paper, + Center, + ThemeIcon, +} from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { IconDeviceMobile, IconLock } from "@tabler/icons-react"; +import { useNavigate } from "react-router-dom"; +import { notifications } from "@mantine/notifications"; +import classes from "./mfa-challenge.module.css"; +import { verifyMfa } from "@/ee/mfa"; +import APP_ROUTE from "@/lib/app-route"; +import { useTranslation } from "react-i18next"; +import * as z from "zod"; +import { MfaBackupCodeInput } from "./mfa-backup-code-input"; + +const formSchema = z.object({ + code: z + .string() + .refine( + (val) => (val.length === 6 && /^\d{6}$/.test(val)) || val.length === 8, + { + message: "Enter a 6-digit code or 8-character backup code", + }, + ), +}); + +type MfaChallengeFormValues = z.infer; + +export function MfaChallenge() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(false); + const [useBackupCode, setUseBackupCode] = useState(false); + + const form = useForm({ + validate: zodResolver(formSchema), + initialValues: { + code: "", + }, + }); + + const handleSubmit = async (values: MfaChallengeFormValues) => { + setIsLoading(true); + try { + await verifyMfa(values.code); + navigate(APP_ROUTE.HOME); + } catch (error: any) { + setIsLoading(false); + notifications.show({ + message: + error.response?.data?.message || t("Invalid verification code"), + color: "red", + }); + form.setFieldValue("code", ""); + } + }; + + return ( + + + +
+ + + +
+ + + + {t("Two-factor authentication")} + + + {useBackupCode + ? t("Enter one of your backup codes") + : t("Enter the 6-digit code found in your authenticator app")} + + + + {!useBackupCode ? ( +
+ +
+ +
+ {form.errors.code && ( + + {form.errors.code} + + )} + + + + { + setUseBackupCode(true); + form.setFieldValue("code", ""); + form.clearErrors(); + }} + > + {t("Use backup code")} + +
+
+ ) : ( + form.setFieldValue("code", value)} + error={form.errors.code?.toString()} + onSubmit={() => handleSubmit(form.values)} + onCancel={() => { + setUseBackupCode(false); + form.setFieldValue("code", ""); + form.clearErrors(); + }} + isLoading={isLoading} + /> + )} +
+
+
+ ); +} diff --git a/apps/client/src/ee/mfa/components/mfa-disable-modal.tsx b/apps/client/src/ee/mfa/components/mfa-disable-modal.tsx new file mode 100644 index 00000000..72bcbeca --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa-disable-modal.tsx @@ -0,0 +1,140 @@ +import React from "react"; +import { + Modal, + Stack, + Text, + Button, + PasswordInput, + Alert, +} from "@mantine/core"; +import { IconShieldOff, IconAlertTriangle } from "@tabler/icons-react"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { useMutation } from "@tanstack/react-query"; +import { notifications } from "@mantine/notifications"; +import { useTranslation } from "react-i18next"; +import { z } from "zod"; +import { disableMfa } from "@/ee/mfa"; +import useCurrentUser from "@/features/user/hooks/use-current-user"; + +interface MfaDisableModalProps { + opened: boolean; + onClose: () => void; + onComplete: () => void; +} + +export function MfaDisableModal({ + opened, + onClose, + onComplete, +}: MfaDisableModalProps) { + const { t } = useTranslation(); + const { data: currentUser } = useCurrentUser(); + const requiresPassword = !currentUser?.user?.hasGeneratedPassword; + + const formSchema = requiresPassword + ? z.object({ + confirmPassword: z.string().min(1, { message: "Password is required" }), + }) + : z.object({ + confirmPassword: z.string().optional(), + }); + + const form = useForm({ + validate: zodResolver(formSchema), + initialValues: { + confirmPassword: "", + }, + }); + + const disableMutation = useMutation({ + mutationFn: disableMfa, + onSuccess: () => { + onComplete(); + }, + onError: (error: any) => { + notifications.show({ + title: t("Error"), + message: error.response?.data?.message || t("Failed to disable MFA"), + color: "red", + }); + }, + }); + + const handleSubmit = async (values: { confirmPassword?: string }) => { + // Only send confirmPassword if it's required (non-SSO users) + const payload = requiresPassword + ? { confirmPassword: values.confirmPassword } + : {}; + await disableMutation.mutateAsync(payload); + }; + + const handleClose = () => { + form.reset(); + onClose(); + }; + + return ( + +
+ + } + title={t("Warning")} + color="red" + variant="light" + > + + {t( + "Disabling two-factor authentication will make your account less secure. You'll only need your password to sign in.", + )} + + + + {requiresPassword && ( + <> + + {t( + "Please enter your password to disable two-factor authentication:", + )} + + + + + )} + + + + + + +
+
+ ); +} diff --git a/apps/client/src/ee/mfa/components/mfa-settings.tsx b/apps/client/src/ee/mfa/components/mfa-settings.tsx new file mode 100644 index 00000000..73d9247d --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa-settings.tsx @@ -0,0 +1,126 @@ +import React, { useState } from "react"; +import { Group, Text, Button, Tooltip } from "@mantine/core"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { notifications } from "@mantine/notifications"; +import { useTranslation } from "react-i18next"; +import { getMfaStatus } from "@/ee/mfa"; +import { MfaSetupModal } from "@/ee/mfa"; +import { MfaDisableModal } from "@/ee/mfa"; +import { MfaBackupCodesModal } from "@/ee/mfa"; +import { isCloud } from "@/lib/config.ts"; +import useLicense from "@/ee/hooks/use-license.tsx"; +import { ResponsiveSettingsRow, ResponsiveSettingsContent, ResponsiveSettingsControl } from "@/components/ui/responsive-settings-row"; + +export function MfaSettings() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const [setupModalOpen, setSetupModalOpen] = useState(false); + const [disableModalOpen, setDisableModalOpen] = useState(false); + const [backupCodesModalOpen, setBackupCodesModalOpen] = useState(false); + const { hasLicenseKey } = useLicense(); + + const { data: mfaStatus, isLoading } = useQuery({ + queryKey: ["mfa-status"], + queryFn: getMfaStatus, + }); + + if (isLoading || !mfaStatus) { + return null; + } + + const canUseMfa = isCloud() || hasLicenseKey; + + // Check if MFA is truly enabled + const isMfaEnabled = mfaStatus?.isEnabled === true; + + const handleSetupComplete = () => { + setSetupModalOpen(false); + queryClient.invalidateQueries({ queryKey: ["mfa-status"] }); + notifications.show({ + title: t("Success"), + message: t("Two-factor authentication has been enabled"), + }); + }; + + const handleDisableComplete = () => { + setDisableModalOpen(false); + queryClient.invalidateQueries({ queryKey: ["mfa-status"] }); + notifications.show({ + title: t("Success"), + message: t("Two-factor authentication has been disabled"), + color: "blue", + }); + }; + + return ( + <> + + + {t("2-step verification")} + + {!isMfaEnabled + ? t( + "Protect your account with an additional verification layer when signing in.", + ) + : t("Two-factor authentication is active on your account.")} + + + + + {!isMfaEnabled ? ( + + + + ) : ( + + + + + )} + + + + setSetupModalOpen(false)} + onComplete={handleSetupComplete} + /> + + setDisableModalOpen(false)} + onComplete={handleDisableComplete} + /> + + setBackupCodesModalOpen(false)} + /> + + ); +} diff --git a/apps/client/src/ee/mfa/components/mfa-setup-modal.tsx b/apps/client/src/ee/mfa/components/mfa-setup-modal.tsx new file mode 100644 index 00000000..d01f2c9f --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa-setup-modal.tsx @@ -0,0 +1,348 @@ +import React, { useState } from "react"; +import { + Modal, + Stack, + Text, + Button, + Group, + Stepper, + Center, + Image, + PinInput, + Alert, + List, + CopyButton, + ActionIcon, + Tooltip, + Paper, + Code, + Loader, + Collapse, + UnstyledButton, +} from "@mantine/core"; +import { + IconQrcode, + IconShieldCheck, + IconKey, + IconCopy, + IconCheck, + IconAlertCircle, + IconChevronDown, + IconChevronRight, + IconPrinter, +} from "@tabler/icons-react"; +import { useForm } from "@mantine/form"; +import { useMutation } from "@tanstack/react-query"; +import { notifications } from "@mantine/notifications"; +import { useTranslation } from "react-i18next"; +import { setupMfa, enableMfa } from "@/ee/mfa"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { z } from "zod"; + +interface MfaSetupModalProps { + opened: boolean; + onClose?: () => void; + onComplete: () => void; + isRequired?: boolean; +} + +interface SetupData { + secret: string; + qrCode: string; + manualKey: string; +} + +const formSchema = z.object({ + verificationCode: z + .string() + .length(6, { message: "Please enter a 6-digit code" }), +}); + +export function MfaSetupModal({ + opened, + onClose, + onComplete, + isRequired = false, +}: MfaSetupModalProps) { + const { t } = useTranslation(); + const [active, setActive] = useState(0); + const [setupData, setSetupData] = useState(null); + const [backupCodes, setBackupCodes] = useState([]); + const [manualEntryOpen, setManualEntryOpen] = useState(false); + + const form = useForm({ + validate: zodResolver(formSchema), + initialValues: { + verificationCode: "", + }, + }); + + const setupMutation = useMutation({ + mutationFn: () => setupMfa({ method: "totp" }), + onSuccess: (data) => { + setSetupData(data); + }, + onError: (error: any) => { + notifications.show({ + title: t("Error"), + message: error.response?.data?.message || t("Failed to setup MFA"), + color: "red", + }); + }, + }); + + // Generate QR code when modal opens + React.useEffect(() => { + if (opened && !setupData && !setupMutation.isPending) { + setupMutation.mutate(); + } + }, [opened]); + + const enableMutation = useMutation({ + mutationFn: (verificationCode: string) => + enableMfa({ + secret: setupData!.secret, + verificationCode, + }), + onSuccess: (data) => { + setBackupCodes(data.backupCodes); + setActive(1); // Move to backup codes step + }, + onError: (error: any) => { + notifications.show({ + title: t("Error"), + message: + error.response?.data?.message || t("Invalid verification code"), + color: "red", + }); + form.setFieldValue("verificationCode", ""); + }, + }); + + const handleClose = () => { + if (active === 1 && backupCodes.length > 0) { + onComplete(); + } + onClose(); + // Reset state + setTimeout(() => { + setActive(0); + setSetupData(null); + setBackupCodes([]); + setManualEntryOpen(false); + form.reset(); + }, 200); + }; + + const handleVerify = async (values: { verificationCode: string }) => { + await enableMutation.mutateAsync(values.verificationCode); + }; + + const handlePrintBackupCodes = () => { + window.print(); + }; + + return ( + + + } + > +
+ + {setupMutation.isPending ? ( +
+ +
+ ) : setupData ? ( + <> + + {t("1. Scan this QR code with your authenticator app")} + + +
+ + MFA QR Code + +
+ + setManualEntryOpen(!manualEntryOpen)} + > + + {manualEntryOpen ? ( + + ) : ( + + )} + + {t("Can't scan the code?")} + + + + + + } + color="gray" + variant="light" + > + + {t( + "Enter this code manually in your authenticator app:", + )} + + + {setupData.manualKey} + + {({ copied, copy }) => ( + + + {copied ? ( + + ) : ( + + )} + + + )} + + + + + + + {t("2. Enter the 6-digit code from your authenticator")} + + + + + {form.errors.verificationCode && ( + + {form.errors.verificationCode} + + )} + + + + + ) : ( +
+ + {t("Failed to generate QR code. Please try again.")} + +
+ )} +
+
+
+ + } + > + + } + title={t("Save your backup codes")} + color="yellow" + > + + {t( + "These codes can be used to access your account if you lose access to your authenticator app. Each code can only be used once.", + )} + + + + + + + {t("Backup codes")} + + + + {({ copied, copy }) => ( + + )} + + + + + + {backupCodes.map((code, index) => ( + + {code} + + ))} + + + + + + +
+
+ ); +} diff --git a/apps/client/src/ee/mfa/components/mfa-setup-required.tsx b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx new file mode 100644 index 00000000..c657abe9 --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { Container, Paper, Title, Text, Alert, Stack } from "@mantine/core"; +import { IconAlertCircle } from "@tabler/icons-react"; +import { useTranslation } from "react-i18next"; +import { MfaSetupModal } from "@/ee/mfa"; +import APP_ROUTE from "@/lib/app-route.ts"; +import { useNavigate } from "react-router-dom"; + +export default function MfaSetupRequired() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const handleSetupComplete = () => { + navigate(APP_ROUTE.HOME); + }; + + return ( + + + + + {t("Two-factor authentication required")} + + + } color="yellow"> + + {t( + "Your workspace requires two-factor authentication. Please set it up to continue.", + )} + + + + + {t( + "This adds an extra layer of security to your account by requiring a verification code from your authenticator app.", + )} + + + + + + + ); +} diff --git a/apps/client/src/ee/mfa/components/mfa.module.css b/apps/client/src/ee/mfa/components/mfa.module.css new file mode 100644 index 00000000..535704a5 --- /dev/null +++ b/apps/client/src/ee/mfa/components/mfa.module.css @@ -0,0 +1,31 @@ +.qrCodeContainer { + background-color: white; + padding: 1rem; + border-radius: var(--mantine-radius-md); + display: inline-block; +} + +.backupCodesList { + font-family: var(--mantine-font-family-monospace); + background-color: var(--mantine-color-gray-0); + padding: 1rem; + border-radius: var(--mantine-radius-md); + + @mixin dark { + background-color: var(--mantine-color-dark-7); + } +} + +.codeItem { + padding: 0.25rem 0; + font-size: 0.875rem; +} + +.setupStep { + min-height: 400px; +} + +.verificationInput { + max-width: 320px; + margin: 0 auto; +} \ No newline at end of file diff --git a/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts b/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts new file mode 100644 index 00000000..9200cac7 --- /dev/null +++ b/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts @@ -0,0 +1,51 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import APP_ROUTE from "@/lib/app-route"; +import { validateMfaAccess } from "@/ee/mfa"; + +export function useMfaPageProtection() { + const navigate = useNavigate(); + const location = useLocation(); + const [isValidating, setIsValidating] = useState(true); + const [isValid, setIsValid] = useState(false); + + useEffect(() => { + const checkAccess = async () => { + const result = await validateMfaAccess(); + + if (!result.valid) { + navigate(APP_ROUTE.AUTH.LOGIN); + return; + } + + // Check if user is on the correct page based on their MFA state + const isOnChallengePage = + location.pathname === APP_ROUTE.AUTH.MFA_CHALLENGE; + const isOnSetupPage = + location.pathname === APP_ROUTE.AUTH.MFA_SETUP_REQUIRED; + + if (result.requiresMfaSetup && !isOnSetupPage) { + // User needs to set up MFA but is on challenge page + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + } else if ( + !result.requiresMfaSetup && + result.userHasMfa && + !isOnChallengePage + ) { + // User has MFA and should be on challenge page + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + } else if (!result.isTransferToken) { + // User has a regular auth token, shouldn't be on MFA pages + navigate(APP_ROUTE.HOME); + } else { + setIsValid(true); + } + + setIsValidating(false); + }; + + checkAccess(); + }, [navigate, location.pathname]); + + return { isValidating, isValid }; +} diff --git a/apps/client/src/ee/mfa/index.ts b/apps/client/src/ee/mfa/index.ts new file mode 100644 index 00000000..047b0a8d --- /dev/null +++ b/apps/client/src/ee/mfa/index.ts @@ -0,0 +1,19 @@ +// Components +export { MfaChallenge } from "./components/mfa-challenge"; +export { MfaSettings } from "./components/mfa-settings"; +export { MfaSetupModal } from "./components/mfa-setup-modal"; +export { MfaDisableModal } from "./components/mfa-disable-modal"; +export { MfaBackupCodesModal } from "./components/mfa-backup-codes-modal"; + +// Pages +export { MfaChallengePage } from "./pages/mfa-challenge-page"; +export { MfaSetupRequiredPage } from "./pages/mfa-setup-required-page"; + +// Services +export * from "./services/mfa-service"; + +// Types +export * from "./types/mfa.types"; + +// Hooks +export { useMfaPageProtection } from "./hooks/use-mfa-page-protection.ts"; diff --git a/apps/client/src/ee/mfa/pages/mfa-challenge-page.tsx b/apps/client/src/ee/mfa/pages/mfa-challenge-page.tsx new file mode 100644 index 00000000..40949fc7 --- /dev/null +++ b/apps/client/src/ee/mfa/pages/mfa-challenge-page.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { MfaChallenge } from "@/ee/mfa"; +import { useMfaPageProtection } from "@/ee/mfa"; + +export function MfaChallengePage() { + const { isValid } = useMfaPageProtection(); + + if (!isValid) { + return null; + } + + return ; +} diff --git a/apps/client/src/ee/mfa/pages/mfa-setup-required-page.tsx b/apps/client/src/ee/mfa/pages/mfa-setup-required-page.tsx new file mode 100644 index 00000000..0b5f756d --- /dev/null +++ b/apps/client/src/ee/mfa/pages/mfa-setup-required-page.tsx @@ -0,0 +1,113 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { + Container, + Title, + Text, + Button, + Stack, + Paper, + Alert, + Center, + ThemeIcon, +} from "@mantine/core"; +import { IconShieldCheck, IconAlertCircle } from "@tabler/icons-react"; +import { useTranslation } from "react-i18next"; +import APP_ROUTE from "@/lib/app-route"; +import { MfaSetupModal } from "@/ee/mfa"; +import classes from "@/features/auth/components/auth.module.css"; +import { notifications } from "@mantine/notifications"; +import { useMfaPageProtection } from "@/ee/mfa"; + +export function MfaSetupRequiredPage() { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [setupModalOpen, setSetupModalOpen] = useState(false); + const { isValid } = useMfaPageProtection(); + + const handleSetupComplete = async () => { + setSetupModalOpen(false); + + notifications.show({ + title: t("Success"), + message: t( + "Two-factor authentication has been set up. Please log in again.", + ), + }); + + navigate(APP_ROUTE.AUTH.LOGIN); + }; + + const handleLogout = () => { + navigate(APP_ROUTE.AUTH.LOGIN); + }; + + if (!isValid) { + return null; + } + + return ( + + + +
+ + + +
+ + + + {t("Two-factor authentication required")} + + + {t( + "Your workspace requires two-factor authentication for all users", + )} + + + + } + color="blue" + variant="light" + w="100%" + > + + {t( + "To continue accessing your workspace, you must set up two-factor authentication. This adds an extra layer of security to your account.", + )} + + + + + + + + +
+
+ + setSetupModalOpen(false)} + onComplete={handleSetupComplete} + isRequired={true} + /> +
+ ); +} diff --git a/apps/client/src/ee/mfa/services/mfa-service.ts b/apps/client/src/ee/mfa/services/mfa-service.ts new file mode 100644 index 00000000..2c4956e1 --- /dev/null +++ b/apps/client/src/ee/mfa/services/mfa-service.ts @@ -0,0 +1,61 @@ +import api from "@/lib/api-client"; +import { + MfaBackupCodesResponse, + MfaDisableRequest, + MfaEnableRequest, + MfaEnableResponse, + MfaSetupRequest, + MfaSetupResponse, + MfaStatusResponse, + MfaAccessValidationResponse, +} from "@/ee/mfa"; + +export async function getMfaStatus(): Promise { + const req = await api.post("/mfa/status"); + return req.data; +} + +export async function setupMfa( + data: MfaSetupRequest, +): Promise { + const req = await api.post("/mfa/setup", data); + return req.data; +} + +export async function enableMfa( + data: MfaEnableRequest, +): Promise { + const req = await api.post("/mfa/enable", data); + return req.data; +} + +export async function disableMfa( + data: MfaDisableRequest, +): Promise<{ success: boolean }> { + const req = await api.post<{ success: boolean }>("/mfa/disable", data); + return req.data; +} + +export async function regenerateBackupCodes(data: { + confirmPassword?: string; +}): Promise { + const req = await api.post( + "/mfa/generate-backup-codes", + data, + ); + return req.data; +} + +export async function verifyMfa(code: string): Promise { + const req = await api.post("/mfa/verify", { code }); + return req.data; +} + +export async function validateMfaAccess(): Promise { + try { + const res = await api.post("/mfa/validate-access"); + return res.data; + } catch { + return { valid: false }; + } +} diff --git a/apps/client/src/ee/mfa/types/mfa.types.ts b/apps/client/src/ee/mfa/types/mfa.types.ts new file mode 100644 index 00000000..9f3bbe7c --- /dev/null +++ b/apps/client/src/ee/mfa/types/mfa.types.ts @@ -0,0 +1,62 @@ +export interface MfaMethod { + type: 'totp' | 'email'; + isEnabled: boolean; +} + +export interface MfaSettings { + isEnabled: boolean; + methods: MfaMethod[]; + backupCodesCount: number; + lastUpdated?: string; +} + +export interface MfaSetupState { + method: 'totp' | 'email'; + secret?: string; + qrCode?: string; + manualEntry?: string; + backupCodes?: string[]; +} + +export interface MfaStatusResponse { + isEnabled?: boolean; + method?: string | null; + backupCodesCount?: number; +} + +export interface MfaSetupRequest { + method: 'totp'; +} + +export interface MfaSetupResponse { + method: string; + qrCode: string; + secret: string; + manualKey: string; +} + +export interface MfaEnableRequest { + secret: string; + verificationCode: string; +} + +export interface MfaEnableResponse { + success: boolean; + backupCodes: string[]; +} + +export interface MfaDisableRequest { + confirmPassword?: string; +} + +export interface MfaBackupCodesResponse { + backupCodes: string[]; +} + +export interface MfaAccessValidationResponse { + valid: boolean; + isTransferToken?: boolean; + requiresMfaSetup?: boolean; + userHasMfa?: boolean; + isMfaEnforced?: boolean; +} diff --git a/apps/client/src/ee/security/components/allowed-domains.tsx b/apps/client/src/ee/security/components/allowed-domains.tsx index c50825fe..d1050975 100644 --- a/apps/client/src/ee/security/components/allowed-domains.tsx +++ b/apps/client/src/ee/security/components/allowed-domains.tsx @@ -1,6 +1,7 @@ import { useAtom } from "jotai"; import * as z from "zod"; -import { useForm, zodResolver } from "@mantine/form"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; import React, { useState } from "react"; import { Button, Text, TagsInput } from "@mantine/core"; @@ -54,9 +55,11 @@ export default function AllowedDomains() { return ( <>
- Allowed email domains + {t("Allowed email domains")} - Only users with email addresses from these domains can signup via SSO. + {t( + "Only users with email addresses from these domains can signup via SSO.", + )}
diff --git a/apps/client/src/ee/security/components/create-sso-provider.tsx b/apps/client/src/ee/security/components/create-sso-provider.tsx index 8a6162e9..3f213bb9 100644 --- a/apps/client/src/ee/security/components/create-sso-provider.tsx +++ b/apps/client/src/ee/security/components/create-sso-provider.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { useDisclosure } from "@mantine/hooks"; import { Button, Menu, Group } from "@mantine/core"; -import { IconChevronDown, IconLock } from "@tabler/icons-react"; +import { IconChevronDown, IconLock, IconServer } from "@tabler/icons-react"; import { useCreateSsoProviderMutation } from "@/ee/security/queries/security-query.ts"; import { SSO_PROVIDER } from "@/ee/security/contants.ts"; import { IAuthProvider } from "@/ee/security/types/security.types.ts"; @@ -40,6 +40,19 @@ export default function CreateSsoProvider() { } }; + const handleCreateLDAP = async () => { + try { + const newProvider = await createSsoProviderMutation.mutateAsync({ + type: SSO_PROVIDER.LDAP, + name: "LDAP", + }); + setProvider(newProvider); + open(); + } catch (error) { + console.error("Failed to create LDAP provider", error); + } + }; + return ( <> @@ -71,6 +84,13 @@ export default function CreateSsoProvider() { > OpenID (OIDC) + + } + > + LDAP / Active Directory + diff --git a/apps/client/src/ee/security/components/enforce-mfa.tsx b/apps/client/src/ee/security/components/enforce-mfa.tsx new file mode 100644 index 00000000..37cf5152 --- /dev/null +++ b/apps/client/src/ee/security/components/enforce-mfa.tsx @@ -0,0 +1,66 @@ +import { Group, Text, Switch, MantineSize, Title } from "@mantine/core"; +import { useAtom } from "jotai"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; +import { notifications } from "@mantine/notifications"; + +export default function EnforceMfa() { + const { t } = useTranslation(); + + return ( + <> + + MFA + + +
+ {t("Enforce two-factor authentication")} + + {t( + "Once enforced, all members must enable two-factor authentication to access the workspace.", + )} + +
+ + +
+ + ); +} + +interface EnforceMfaToggleProps { + size?: MantineSize; + label?: string; +} +export function EnforceMfaToggle({ size, label }: EnforceMfaToggleProps) { + const { t } = useTranslation(); + const [workspace, setWorkspace] = useAtom(workspaceAtom); + const [checked, setChecked] = useState(workspace?.enforceMfa); + + const handleChange = async (event: React.ChangeEvent) => { + const value = event.currentTarget.checked; + try { + const updatedWorkspace = await updateWorkspace({ enforceMfa: value }); + setChecked(value); + setWorkspace(updatedWorkspace); + } catch (err) { + notifications.show({ + message: err?.response?.data?.message, + color: "red", + }); + } + }; + + return ( + + ); +} diff --git a/apps/client/src/ee/security/components/sso-google-form.tsx b/apps/client/src/ee/security/components/sso-google-form.tsx index 97a39a3b..ddd14b0f 100644 --- a/apps/client/src/ee/security/components/sso-google-form.tsx +++ b/apps/client/src/ee/security/components/sso-google-form.tsx @@ -1,6 +1,7 @@ import React from "react"; import { z } from "zod"; -import { useForm, zodResolver } from "@mantine/form"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; import { Box, Button, Group, Stack, Switch, TextInput } from "@mantine/core"; import classes from "@/ee/security/components/sso.module.css"; import { IAuthProvider } from "@/ee/security/types/security.types.ts"; diff --git a/apps/client/src/ee/security/components/sso-ldap-form.tsx b/apps/client/src/ee/security/components/sso-ldap-form.tsx new file mode 100644 index 00000000..662e8458 --- /dev/null +++ b/apps/client/src/ee/security/components/sso-ldap-form.tsx @@ -0,0 +1,228 @@ +import React from "react"; +import { z } from "zod"; +import { useForm } from "@mantine/form"; +import { zodResolver } from "mantine-form-zod-resolver"; +import { + Box, + Button, + Group, + Stack, + Switch, + TextInput, + Textarea, + Text, + Accordion, +} from "@mantine/core"; +import classes from "@/ee/security/components/sso.module.css"; +import { IAuthProvider } from "@/ee/security/types/security.types.ts"; +import { useTranslation } from "react-i18next"; +import { useUpdateSsoProviderMutation } from "@/ee/security/queries/security-query.ts"; +import { IconInfoCircle } from "@tabler/icons-react"; + +const ssoSchema = z.object({ + name: z.string().min(1, "Display name is required"), + ldapUrl: z.string().url().startsWith("ldap", "Must be an LDAP URL"), + ldapBindDn: z.string().min(1, "Bind DN is required"), + ldapBindPassword: z.string().min(1, "Bind password is required"), + ldapBaseDn: z.string().min(1, "Base DN is required"), + ldapUserSearchFilter: z.string().optional(), + ldapTlsEnabled: z.boolean(), + ldapTlsCaCert: z.string().optional(), + isEnabled: z.boolean(), + allowSignup: z.boolean(), + groupSync: z.boolean(), +}); + +type SSOFormValues = z.infer; + +interface SsoFormProps { + provider: IAuthProvider; + onClose?: () => void; +} + +export function SsoLDAPForm({ provider, onClose }: SsoFormProps) { + const { t } = useTranslation(); + const updateSsoProviderMutation = useUpdateSsoProviderMutation(); + + const form = useForm({ + initialValues: { + name: provider.name || "", + ldapUrl: provider.ldapUrl || "", + ldapBindDn: provider.ldapBindDn || "", + ldapBindPassword: provider.ldapBindPassword || "", + ldapBaseDn: provider.ldapBaseDn || "", + ldapUserSearchFilter: + provider.ldapUserSearchFilter || "(mail={{username}})", + ldapTlsEnabled: provider.ldapTlsEnabled || false, + ldapTlsCaCert: provider.ldapTlsCaCert || "", + isEnabled: provider.isEnabled, + allowSignup: provider.allowSignup, + groupSync: provider.groupSync || false, + }, + validate: zodResolver(ssoSchema), + }); + + const handleSubmit = async (values: SSOFormValues) => { + const ssoData: Partial = { + providerId: provider.id, + }; + if (form.isDirty("name")) { + ssoData.name = values.name; + } + if (form.isDirty("ldapUrl")) { + ssoData.ldapUrl = values.ldapUrl; + } + if (form.isDirty("ldapBindDn")) { + ssoData.ldapBindDn = values.ldapBindDn; + } + if (form.isDirty("ldapBindPassword")) { + ssoData.ldapBindPassword = values.ldapBindPassword; + } + if (form.isDirty("ldapBaseDn")) { + ssoData.ldapBaseDn = values.ldapBaseDn; + } + if (form.isDirty("ldapUserSearchFilter")) { + ssoData.ldapUserSearchFilter = values.ldapUserSearchFilter; + } + if (form.isDirty("ldapTlsEnabled")) { + ssoData.ldapTlsEnabled = values.ldapTlsEnabled; + } + if (form.isDirty("ldapTlsCaCert")) { + ssoData.ldapTlsCaCert = values.ldapTlsCaCert; + } + if (form.isDirty("isEnabled")) { + ssoData.isEnabled = values.isEnabled; + } + if (form.isDirty("allowSignup")) { + ssoData.allowSignup = values.allowSignup; + } + if (form.isDirty("groupSync")) { + ssoData.groupSync = values.groupSync; + } + + await updateSsoProviderMutation.mutateAsync(ssoData); + form.resetDirty(); + onClose(); + }; + + return ( + + + + + + + + + + + + + + + + + + }> + {t("Advanced Settings")} + + + + +
+ {t("Enable TLS/SSL")} + + Use secure connection to LDAP server + +
+ +
+ + {form.values.ldapTlsEnabled && ( +