Compare commits

...

46 Commits

Author SHA1 Message Date
Philip Okugbe 2de00568d3 New translations translation.json (Portuguese, Brazilian)
[ci skip]
2026-05-20 17:56:41 +01:00
Philip Okugbe 6c647865ef New translations translation.json (Chinese Simplified)
[ci skip]
2026-05-20 17:56:40 +01:00
Philip Okugbe 576c769093 New translations translation.json (Ukrainian)
[ci skip]
2026-05-20 17:56:38 +01:00
Philip Okugbe 3331cd12cc New translations translation.json (Russian)
[ci skip]
2026-05-20 17:56:37 +01:00
Philip Okugbe 6410b67bdd New translations translation.json (Dutch)
[ci skip]
2026-05-20 17:56:35 +01:00
Philip Okugbe dfa38c704e New translations translation.json (Korean)
[ci skip]
2026-05-20 17:56:34 +01:00
Philip Okugbe 6f89789ac6 New translations translation.json (Japanese)
[ci skip]
2026-05-20 17:56:32 +01:00
Philip Okugbe f5907d5d34 New translations translation.json (Italian)
[ci skip]
2026-05-20 17:56:30 +01:00
Philip Okugbe 63c87e52fc New translations translation.json (Spanish)
[ci skip]
2026-05-20 17:56:29 +01:00
Philip Okugbe 2d78fa297a New translations translation.json (French)
[ci skip]
2026-05-20 17:56:27 +01:00
Philip Okugbe 29187f66fb New translations translation.json (German)
[ci skip]
2026-05-20 17:56:25 +01:00
Philipinho e02f0acc65 fix: add i18next_json type to crowdin 2026-05-20 17:34:34 +01:00
Philipinho adb1f27767 v0.90.0 2026-05-20 16:55:23 +01:00
Philip Okugbe 92c0e36e46 fix(a11y): WCAG 2.1 AA fixes (#2219) 2026-05-20 16:47:25 +01:00
Olivier Lambert 1c166c4736 feat(editor): add alt text support for images (#2097)
* feat(editor): add alt text support for images
* feat:  extend alt text support to videos and diagrams

---------
Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
2026-05-20 16:45:59 +01:00
Philip Okugbe 66a754c9eb Revert "fix: prevent browser tab fallback in editor (#2123)" (#2216)
This reverts commit 1d2486455f.
2026-05-19 14:07:07 +01:00
Philip Okugbe 6cf8101ab3 feat(ee): templates (#2215)
* feat(ee): templates
* fix tree
* fix
2026-05-19 02:41:52 +01:00
Philipinho 0d6538ab1a feat: iframe configuration 2026-05-18 22:02:31 +01:00
Philip Okugbe b7b99cb3b2 fix: code splitting and editor fixes (#2211)
* fix table

* fix code splitting

* fix: editor ready check

* fix codeblock/mermaid gap cursor

* fix callout
2026-05-15 02:46:54 +01:00
Philipinho 03c1e8c4ed fix collab module 2026-05-14 15:06:51 +01:00
Philipinho e41518a93d fix type 2026-05-14 14:49:02 +01:00
Peter Tripp 932c1ad5b7 Better trash (#2190)
* Better trash

I recently lost a bunch of time editing and searching for pages that were actually in the Trash. Docmost intentionally tries to not link to Trashed pages, but the url of that Trashed page and any inbound links still work.  This makes it clearer when a page you are interacting with is in the Trash.

- /trash
  - Refactored banner into `trash-banner.tsx`
  - Refactored "Restore" modal into `use-restore-page-modal.tsx`
- Page (when isDeleted)
  - Add: `trash-banner.tsx`
  - Add breadcrumbs: `Parent / Child / Page (Deleted)`
  - Change: Deleted Pages are read-only
  - Replace "Move to Trash" with "Restore" in page menu (invokes `use-restore-page-modal`)

I tried very hard to keep this simple and re-use existing translation strings wherever possible.

* cleanup

---------

Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
2026-05-14 14:41:10 +01:00
Julien Fontanet 82d065669d fix: page mode toggle no longer overwrites default preference (#1996)
The header edit/read toggle now controls only the current session's mode
without saving it as the user's preference. The saved preference (set in
profile settings) is applied once on initial load and sticks across page
navigations within the session, so navigating to a new page no longer
resets the mode mid-session.

Fixes #1693
2026-05-14 13:15:03 +01:00
Philip Okugbe f758091b2a perf(permissions): cache space role and page edit lookups (#2208) 2026-05-14 13:11:28 +01:00
Philip Okugbe f4af4c3fc0 feat(editor): add page break node (#2202) 2026-05-14 03:48:13 +01:00
Philipinho 3b983a27f6 sync 2026-05-14 03:01:55 +01:00
Philip Okugbe 299a9ca3c8 fix: bug fixes (#2201)
* fix(editor): hide transclusion borders and reset spacing in read-only mode

* feat(share): add full width toggle for shared pages

* feat(share): support resizing sidebar on shared pages

* fix: auto redirect if there is only one SSO provider.
- fix tighten sso redirect
- fix share tree margin

* sync

* package overrides
2026-05-14 02:54:00 +01:00
Philip Okugbe cea9be7926 feat: table enhancement (#2191) 2026-05-14 00:37:44 +01:00
Philip Okugbe 31ed0df3f7 feat(tree): replace sidebar tree (react-aborist) with custom tree implementation (#2199)
* feat(tree): replace react-arborist with custom tree implementation

* feat(tree): keyboard arrow navigation between rows

* feat(emoji-picker): focus search input on open

* refactor(emoji): switch to @slidoapp/emoji-mart fork for accessibility

* feat(tree): Home/End and typeahead keyboard navigation

* feat(tree): roving tabindex and * to expand sibling subtrees

* feat(tree): Space activation and ARIA refinements

* fix(tree): move treeitem role to focusable row + aria-current
2026-05-13 23:01:04 +01:00
Philip Okugbe a689cca7a0 feat: page labels/tags (#2188)
* feat: labels (WIP)
* full implementation
2026-05-10 18:14:15 +01:00
Philip Okugbe 537e45bc11 feat: page details section and backlinks (#2186)
* feat: page details section and backlinks
2026-05-09 17:03:08 +01:00
Philip Okugbe bdc369fce0 feat(editor): fixed toolbar preference (#2185)
* feat(editor): fixed toolbar preference

* remove key

* cleanup translation strings

* update axios
2026-05-09 13:27:03 +01:00
Philip Okugbe 2d8b470495 feat(editor): indentation (#2174)
* switch to default codeblock tab handling

* feat(editor): indentation
2026-05-08 21:40:37 +01:00
David Gallardo c66c08fa78 fix: ignore emoji when deriving avatar initials (#2167) 2026-05-08 21:36:10 +01:00
David Gallardo 6046d04375 feat(editor): replace emoji picker with browse + search (#2171)
* feat(editor): show emoji name in suggestion list

Replace the fixed-column emoji grid with a vertical list that displays
each emoji alongside its :shortcode: name. This makes the picker more
discoverable—users can see and learn shortcodes without prior knowledge.

Changes:
- EmojiList: switch from SimpleGrid/ActionIcon to UnstyledButton list
  rows showing emoji glyph + monospace 🆔 label
- Navigation simplified to ArrowUp/ArrowDown (list has no columns)
- Results capped at 8 items for a focused, scannable dropdown
- CSS module: rename menuBtn -> menuItem, tighten padding

* feat(editor): replace SearchIndex with name/id includes search

Port the exact search algorithm from the original extension:
- Build a flat index from @emoji-mart/data: { id, name (lowercase), native }
- Filter with name.includes(q) || id.includes(q) — predictable, no
  keyword indirection
- Results capped at 5 (same as extension)
- Frequently-used emojis (sorted by usage) shown when query is empty
- Remove emoji-mart init() / SearchIndex / getEmojiDataFromNative
  dependencies; index is built lazily and cached in memory
- Remove unused GRID_COLUMNS constant

* feat(editor): emoji picker with browse and search modes

When the query is empty the picker shows a category bar with 8 tabs
(people, nature, food…) and a scrollable emoji grid. Typing after ':'
switches to a compact list that shows the glyph and :shortcode: side by
side, making it easy to discover emoji names while you type.

- Category data is loaded lazily from @emoji-mart/data and cached, so
  opening the picker more than once has no overhead
- Grid keyboard nav: arrow keys move by cell/row, Enter picks
- List keyboard nav: up/down through results, Enter picks
- Mouse hover syncs the keyboard selection index in both modes
- incrementEmojiUsage tracks picks so frequently used ones bubble up
  in future sessions

* fix(editor): polish emoji picker copy and loading

* feat: add emoji to slash command

* Add keyboard support to emoji group navigation

---------

Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
2026-05-08 21:33:43 +01:00
David Gallardo 5d8c11e741 fix: sync html lang with current user locale (#2165) 2026-05-08 21:15:04 +01:00
Philip Okugbe de60aa7e61 feat: synced blocks (transclusion) (#2163)
* feat: synced blocks (transclusion)

* fix:remove name

* make placeholders smaller

* feat: enforce strict transclusion schema

* fix: scope synced blocks to workspace, gate unsync on edit permission

* fix collab module error
2026-05-08 13:23:16 +01:00
Peter Tripp c9fa6e20b3 Add alias: /toc and /ol (#2161) 2026-05-08 01:20:27 +01:00
Philipinho ec51ca7815 fix request ip 2026-05-07 22:09:32 +01:00
Philipinho 2b63137217 mail 2026-05-07 18:13:24 +01:00
Philipinho 3227bc6059 fix: a11y 2026-05-04 23:04:26 +01:00
Philip Okugbe 73dc62bca3 update react-email (#2149) 2026-05-04 22:26:53 +01:00
Philipinho 3c74bb3dee update package 2026-05-04 22:09:19 +01:00
Philip Okugbe dbe6c2d6ba feat: A11y fixes (#2148) 2026-05-04 21:21:37 +01:00
Sarthak Chaturvedi fe18f22dc6 fix: prevent code block deletion when adding inline comments in read mode (#2146) 2026-05-04 21:14:21 +01:00
Philipinho fcef0c6b96 fix: S3 2026-05-04 20:57:35 +01:00
432 changed files with 23709 additions and 5131 deletions
+7
View File
@@ -48,6 +48,13 @@ GOTENBERG_URL=
DISABLE_TELEMETRY=false DISABLE_TELEMETRY=false
# Allow other sites to embed Docmost in an iframe.
IFRAME_EMBED_ALLOWED=false
# Only used when IFRAME_EMBED_ALLOWED=true. When empty, any origin is allowed.
# Example: https://intranet.example.com,https://portal.example.com
IFRAME_ALLOWED_ORIGINS=
# Enable debug logging in production (default: false) # Enable debug logging in production (default: false)
DEBUG_MODE=false DEBUG_MODE=false
+70 -60
View File
@@ -1,85 +1,95 @@
{ {
"name": "client", "name": "client",
"private": true, "private": true,
"version": "0.80.1", "version": "0.90.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "preview": "vite preview",
"format": "prettier --write \"src/**/*.tsx\" \"src/**/*.ts\"" "format": "prettier --write \"src/**/*.tsx\" \"src/**/*.ts\"",
"test": "vitest run",
"test:watch": "vitest"
}, },
"dependencies": { "dependencies": {
"@casl/react": "^5.0.1", "@atlaskit/pragmatic-drag-and-drop": "1.8.1",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "2.1.5",
"@atlaskit/pragmatic-drag-and-drop-flourish": "2.0.15",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
"@atlaskit/pragmatic-drag-and-drop-live-region": "1.3.4",
"@casl/react": "5.0.1",
"@docmost/editor-ext": "workspace:*", "@docmost/editor-ext": "workspace:*",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@excalidraw/excalidraw": "0.18.0-3a5ef40", "@excalidraw/excalidraw": "0.18.0-3a5ef40",
"@mantine/core": "^8.3.18", "@mantine/core": "8.3.18",
"@mantine/dates": "^8.3.18", "@mantine/dates": "8.3.18",
"@mantine/form": "^8.3.18", "@mantine/form": "8.3.18",
"@mantine/hooks": "^8.3.18", "@mantine/hooks": "8.3.18",
"@mantine/modals": "^8.3.18", "@mantine/modals": "8.3.18",
"@mantine/notifications": "^8.3.18", "@mantine/notifications": "8.3.18",
"@mantine/spotlight": "^8.3.18", "@mantine/spotlight": "8.3.18",
"@tabler/icons-react": "^3.40.0", "@slidoapp/emoji-mart": "5.8.7",
"@slidoapp/emoji-mart-data": "1.2.4",
"@slidoapp/emoji-mart-react": "1.1.5",
"@tabler/icons-react": "3.40.0",
"@tanstack/react-query": "5.90.17", "@tanstack/react-query": "5.90.17",
"alfaaz": "^1.1.0", "@tanstack/react-virtual": "3.13.24",
"axios": "1.15.0", "alfaaz": "1.1.0",
"blueimp-load-image": "^5.16.0", "axios": "1.16.0",
"clsx": "^2.1.1", "blueimp-load-image": "5.16.0",
"emoji-mart": "^5.6.0", "clsx": "2.1.1",
"file-saver": "^2.0.5", "file-saver": "2.0.5",
"highlightjs-sap-abap": "^0.3.0", "highlightjs-sap-abap": "0.3.0",
"i18next": "25.10.1", "i18next": "25.10.1",
"i18next-http-backend": "3.0.6", "i18next-http-backend": "3.0.6",
"jotai": "^2.18.1", "jotai": "2.18.1",
"jotai-optics": "^0.4.0", "jotai-optics": "0.4.0",
"js-cookie": "^3.0.5", "js-cookie": "3.0.5",
"jwt-decode": "^4.0.0", "jwt-decode": "4.0.0",
"katex": "0.16.40", "katex": "0.16.40",
"lowlight": "^3.3.0", "lowlight": "3.3.0",
"mantine-form-zod-resolver": "^1.3.0", "mantine-form-zod-resolver": "1.3.0",
"mermaid": "^11.13.0", "mermaid": "11.15.0",
"mitt": "^3.0.1", "mitt": "3.0.1",
"posthog-js": "1.372.2", "posthog-js": "1.372.2",
"react": "^18.3.1", "react": "18.3.1",
"react-arborist": "3.4.0",
"react-clear-modal": "^2.0.18", "react-clear-modal": "^2.0.18",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-drawio": "^1.0.7", "react-drawio": "1.0.7",
"react-error-boundary": "^6.1.1", "react-error-boundary": "6.1.1",
"react-helmet-async": "^3.0.0", "react-helmet-async": "3.0.0",
"react-i18next": "16.5.8", "react-i18next": "16.5.8",
"react-router-dom": "^7.13.1", "react-router-dom": "7.13.1",
"semver": "^7.7.4", "semver": "7.7.4",
"socket.io-client": "^4.8.3", "socket.io-client": "4.8.3",
"tiptap-extension-global-drag-handle": "^0.1.18", "zod": "4.3.6"
"zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.28.0", "@eslint/js": "9.28.0",
"@tanstack/eslint-plugin-query": "^5.94.4", "@tanstack/eslint-plugin-query": "5.94.4",
"@types/blueimp-load-image": "^5.16.6", "@testing-library/jest-dom": "6.6.0",
"@types/file-saver": "^2.0.7", "@testing-library/react": "16.1.0",
"@types/js-cookie": "^3.0.6", "@types/blueimp-load-image": "5.16.6",
"@types/katex": "^0.16.8", "@types/file-saver": "2.0.7",
"@types/js-cookie": "3.0.6",
"@types/katex": "0.16.8",
"@types/node": "22.19.1", "@types/node": "22.19.1",
"@types/react": "^18.3.12", "@types/react": "18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "18.3.1",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "6.0.1",
"eslint": "^9.28.0", "eslint": "9.28.0",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "7.0.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "0.5.2",
"globals": "^15.13.0", "globals": "15.13.0",
"optics-ts": "^2.4.1", "jsdom": "25.0.0",
"postcss": "^8.5.12", "optics-ts": "2.4.1",
"postcss-preset-mantine": "^1.18.0", "postcss": "8.5.14",
"postcss-simple-vars": "^7.0.1", "postcss-preset-mantine": "1.18.0",
"prettier": "^3.8.1", "postcss-simple-vars": "7.0.1",
"typescript": "^5.9.3", "prettier": "3.8.1",
"typescript-eslint": "^8.57.1", "typescript": "5.9.3",
"vite": "8.0.5" "typescript-eslint": "8.57.1",
"vite": "8.0.5",
"vitest": "4.1.6"
} }
} }
@@ -71,6 +71,7 @@
"Export": "Exportieren", "Export": "Exportieren",
"Failed to create page": "Erstellung der Seite fehlgeschlagen", "Failed to create page": "Erstellung der Seite fehlgeschlagen",
"Failed to delete page": "Löschen der Seite fehlgeschlagen", "Failed to delete page": "Löschen der Seite fehlgeschlagen",
"Failed to restore page": "Seite konnte nicht wiederhergestellt werden",
"Failed to fetch recent pages": "Fehler beim Abrufen der letzten Seiten", "Failed to fetch recent pages": "Fehler beim Abrufen der letzten Seiten",
"Failed to import pages": "Import der Seiten fehlgeschlagen", "Failed to import pages": "Import der Seiten fehlgeschlagen",
"Failed to load page. An error occurred.": "Seite konnte nicht geladen werden. Es ist ein Fehler aufgetreten.", "Failed to load page. An error occurred.": "Seite konnte nicht geladen werden. Es ist ein Fehler aufgetreten.",
@@ -111,7 +112,7 @@
"Member": "Mitglied", "Member": "Mitglied",
"members": "Mitglieder", "members": "Mitglieder",
"Members": "Mitglieder", "Members": "Mitglieder",
"My preferences": "Meine Voreinstellungen", "My preferences": "Meine Einstellungen",
"My Profile": "Mein Profil", "My Profile": "Mein Profil",
"My profile": "Mein Profil", "My profile": "Mein Profil",
"Name": "Name", "Name": "Name",
@@ -139,7 +140,7 @@
"People": "Personen", "People": "Personen",
"Pending": "Ausstehend", "Pending": "Ausstehend",
"Please confirm your action": "Bitte bestätigen Sie Ihre Aktion", "Please confirm your action": "Bitte bestätigen Sie Ihre Aktion",
"Preferences": "Vorlieben", "Preferences": "Einstellungen",
"Print PDF": "PDF drucken", "Print PDF": "PDF drucken",
"Profile": "Profil", "Profile": "Profil",
"Recently updated": "Kürzlich aktualisiert", "Recently updated": "Kürzlich aktualisiert",
@@ -276,6 +277,9 @@
"Align left": "Links ausrichten", "Align left": "Links ausrichten",
"Align right": "Rechts ausrichten", "Align right": "Rechts ausrichten",
"Align center": "Zentrieren", "Align center": "Zentrieren",
"Alt text": "Alternativtext",
"Describe this for accessibility.": "Beschreiben Sie dies für die Barrierefreiheit.",
"Add a description": "Beschreibung hinzufügen",
"Justify": "Blocksatz", "Justify": "Blocksatz",
"Merge cells": "Zellen zusammenführen", "Merge cells": "Zellen zusammenführen",
"Split cell": "Zelle teilen", "Split cell": "Zelle teilen",
@@ -286,6 +290,19 @@
"Add row above": "Zeile oben hinzufügen", "Add row above": "Zeile oben hinzufügen",
"Add row below": "Zeile unten hinzufügen", "Add row below": "Zeile unten hinzufügen",
"Delete table": "Tabelle löschen", "Delete table": "Tabelle löschen",
"Add column left": "Spalte links hinzufügen",
"Add column right": "Spalte rechts hinzufügen",
"Clear cell": "Zelle leeren",
"Clear cells": "Zellen leeren",
"Toggle header cell": "Kopfzelle umschalten",
"Toggle header column": "Kopfspalte umschalten",
"Toggle header row": "Kopfzeile umschalten",
"Move column left": "Spalte nach links verschieben",
"Move column right": "Spalte nach rechts verschieben",
"Move row down": "Zeile nach unten verschieben",
"Move row up": "Zeile nach oben verschieben",
"Sort A → Z": "A → Z sortieren",
"Sort Z → A": "Z → A sortieren",
"Info": "Info", "Info": "Info",
"Note": "Hinweis", "Note": "Hinweis",
"Success": "Erfolg", "Success": "Erfolg",
@@ -348,6 +365,8 @@
"Create block quote.": "Erstellen Sie ein Blockzitat.", "Create block quote.": "Erstellen Sie ein Blockzitat.",
"Insert code snippet.": "Code-Snippet einfügen.", "Insert code snippet.": "Code-Snippet einfügen.",
"Insert horizontal rule divider": "Horizontale Trennlinie einfügen", "Insert horizontal rule divider": "Horizontale Trennlinie einfügen",
"Page break": "Seitenumbruch",
"Insert a page break for printing.": "Einen Seitenumbruch zum Drucken einfügen.",
"Upload any image from your device.": "Laden Sie ein beliebiges Bild von Ihrem Gerät hoch.", "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 video from your device.": "Laden Sie ein beliebiges Video von Ihrem Gerät hoch.",
"Upload any audio from your device.": "Laden Sie beliebige Audiodateien von Ihrem Gerät hoch.", "Upload any audio from your device.": "Laden Sie beliebige Audiodateien von Ihrem Gerät hoch.",
@@ -392,6 +411,10 @@
"Write...": "\"Schreiben...\"", "Write...": "\"Schreiben...\"",
"Column count": "Spaltenanzahl", "Column count": "Spaltenanzahl",
"{{count}} Columns": "{{count}} Spalten", "{{count}} Columns": "{{count}} Spalten",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Gleich breite Spalten", "Equal columns": "Gleich breite Spalten",
"Left sidebar": "Linke Seitenleiste", "Left sidebar": "Linke Seitenleiste",
"Right sidebar": "Rechte Seitenleiste", "Right sidebar": "Rechte Seitenleiste",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} ist verfügbar", "{{latestVersion}} is available": "{{latestVersion}} ist verfügbar",
"Default page edit mode": "Standard-Bearbeitungsmodus für Seiten", "Default page edit mode": "Standard-Bearbeitungsmodus für Seiten",
"Choose your preferred page edit mode. Avoid accidental edits.": "Wählen Sie Ihren bevorzugten Seitenbearbeitungsmodus. Vermeiden Sie versehentliche Bearbeitungen.", "Choose your preferred page edit mode. Avoid accidental edits.": "Wählen Sie Ihren bevorzugten Seitenbearbeitungsmodus. Vermeiden Sie versehentliche Bearbeitungen.",
"Choose {{format}} file": "{{format}}-Datei auswählen",
"Reading": "Lesen", "Reading": "Lesen",
"Delete member": "Mitglied löschen", "Delete member": "Mitglied löschen",
"Member deleted successfully": "Mitglied erfolgreich gelöscht", "Member deleted successfully": "Mitglied erfolgreich gelöscht",
@@ -565,6 +589,8 @@
"Move to trash": "In den Papierkorb verschieben", "Move to trash": "In den Papierkorb verschieben",
"Move this page to trash?": "Diese Seite in den Papierkorb verschieben?", "Move this page to trash?": "Diese Seite in den Papierkorb verschieben?",
"Restore page": "Seite wiederherstellen", "Restore page": "Seite wiederherstellen",
"Permanently delete": "Endgültig löschen",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> hat diese Seite {{time}} in den Papierkorb verschoben.",
"Page moved to trash": "Seite in den Papierkorb verschoben", "Page moved to trash": "Seite in den Papierkorb verschoben",
"Page restored successfully": "Seite erfolgreich wiederhergestellt", "Page restored successfully": "Seite erfolgreich wiederhergestellt",
"Deleted by": "Gelöscht von", "Deleted by": "Gelöscht von",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "Bild überschreitet das Limit von 10 MB.", "Image exceeds 10MB limit.": "Bild überschreitet das Limit von 10 MB.",
"Image removed successfully": "Bild erfolgreich entfernt", "Image removed successfully": "Bild erfolgreich entfernt",
"API key": "API-Schlüssel", "API key": "API-Schlüssel",
"API key created successfully": "API-Schlüssel erfolgreich erstellt",
"API keys": "API-Schlüssel", "API keys": "API-Schlüssel",
"API management": "API-Verwaltung", "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", "Custom expiration date": "Benutzerdefiniertes Ablaufdatum",
"Enter a descriptive token name": "Geben Sie einen beschreibenden Token-Namen ein", "Enter a descriptive token name": "Geben Sie einen beschreibenden Token-Namen ein",
"Expiration": "Ablauf", "Expiration": "Ablauf",
"Expired": "Abgelaufen", "Expired": "Abgelaufen",
"Expires": "Läuft ab", "Expires": "Läuft ab",
"I've saved my API key": "Ich habe meinen API-Schlüssel gespeichert",
"Last use": "Zuletzt verwendet", "Last use": "Zuletzt verwendet",
"No API keys found": "Keine API-Schlüssel gefunden", "No API keys found": "Keine API-Schlüssel gefunden",
"No expiration": "Kein Ablauf", "No expiration": "Kein Ablauf",
"Revoke API key": "API-Schlüssel widerrufen",
"Revoked successfully": "Erfolgreich widerrufen", "Revoked successfully": "Erfolgreich widerrufen",
"Select expiration date": "Ablaufdatum wählen", "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.", "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", "Update": "Aktualisieren",
"Update {{credential}}": "{{credential}} aktualisieren",
"Manage API keys for all users in the workspace": "Verwalten Sie API-Schlüssel für alle Benutzer im Arbeitsbereich", "Manage API keys for all users in the workspace": "Verwalten Sie API-Schlüssel für alle Benutzer im Arbeitsbereich",
"Restrict API key creation to admins": "API-Schlüsselerstellung auf Administratoren beschränken", "Restrict API key creation to admins": "API-Schlüsselerstellung auf Administratoren beschränken",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Nur Administratoren und Eigentümer können neue API-Schlüssel erstellen. Bestehende Mitgliederschlüssel funktionieren weiterhin.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Nur Administratoren und Eigentümer können neue API-Schlüssel erstellen. Bestehende Mitgliederschlüssel funktionieren weiterhin.",
@@ -858,9 +880,12 @@
"AI Chat": "KI-Chat", "AI Chat": "KI-Chat",
"Analyze for insights": "Für Erkenntnisse analysieren", "Analyze for insights": "Für Erkenntnisse analysieren",
"Ask anything...": "Fragen Sie irgendetwas...", "Ask anything...": "Fragen Sie irgendetwas...",
"Assistant said:": "Assistent sagte:",
"Chat history": "Chatverlauf", "Chat history": "Chatverlauf",
"Chat name": "Chatname", "Chat name": "Chatname",
"Chat transcript": "Chatprotokoll",
"Close": "Schließen", "Close": "Schließen",
"Copy assistant response": "Antwort des Assistenten kopieren",
"Docmost AI": "Docmost KI", "Docmost AI": "Docmost KI",
"Failed to load chat. An error occurred.": "Chat konnte nicht geladen werden. Ein Fehler ist aufgetreten.", "Failed to load chat. An error occurred.": "Chat konnte nicht geladen werden. Ein Fehler ist aufgetreten.",
"Failed to render this message.": "Diese Nachricht konnte nicht dargestellt werden.", "Failed to render this message.": "Diese Nachricht konnte nicht dargestellt werden.",
@@ -870,9 +895,17 @@
"No chats found": "Keine Chats gefunden", "No chats found": "Keine Chats gefunden",
"No conversations yet": "Noch keine Unterhaltungen", "No conversations yet": "Noch keine Unterhaltungen",
"Open full page": "Ganze Seite öffnen", "Open full page": "Ganze Seite öffnen",
"Scroll to bottom": "Nach unten scrollen",
"You said:": "Sie sagten:",
"Previous 7 days": "Letzte 7 Tage", "Previous 7 days": "Letzte 7 Tage",
"Previous 30 days": "Letzte 30 Tage", "Previous 30 days": "Letzte 30 Tage",
"Search chats...": "Chats durchsuchen...", "Search chats...": "Chats durchsuchen...",
"Search chats": "Chats durchsuchen",
"Ask anything... Use @ to mention pages": "Frag etwas ... Verwende @, um Seiten zu erwähnen",
"Ask anything or search your workspace": "Fragen Sie etwas oder durchsuchen Sie Ihren Workspace",
"Welcome to {{name}}": "Willkommen bei {{name}}",
"Add files": "Dateien hinzufügen",
"Mention a page": "Eine Seite einfügen",
"Start a new chat to see it here.": "Starten Sie einen neuen Chat, damit er hier angezeigt wird.", "Start a new chat to see it here.": "Starten Sie einen neuen Chat, damit er hier angezeigt wird.",
"Summarize this page": "Diese Seite zusammenfassen", "Summarize this page": "Diese Seite zusammenfassen",
"Toggle AI Chat": "KI-Chat umschalten", "Toggle AI Chat": "KI-Chat umschalten",
@@ -880,5 +913,176 @@
"Try a different search term.": "Versuchen Sie einen anderen Suchbegriff.", "Try a different search term.": "Versuchen Sie einen anderen Suchbegriff.",
"Try again": "Erneut versuchen", "Try again": "Erneut versuchen",
"Untitled chat": "Chat ohne Titel", "Untitled chat": "Chat ohne Titel",
"What can I help you with?": "Womit kann ich Ihnen helfen?" "What can I help you with?": "Womit kann ich Ihnen helfen?",
"Are you sure you want to revoke this {{credential}}": "Sind Sie sicher, dass Sie diese(n) {{credential}} widerrufen möchten?",
"Automatically provision users and groups from your identity provider via SCIM.": "Stellen Sie Benutzer und Gruppen automatisch über SCIM von Ihrem Identitätsanbieter bereit.",
"Configure your identity provider with this URL to provision users and groups.": "Konfigurieren Sie Ihren Identitätsanbieter mit dieser URL, um Benutzer und Gruppen bereitzustellen.",
"Create {{credential}}": "{{credential}} erstellen",
"{{credential}} created": "{{credential}} erstellt",
"{{credential}} created successfully": "{{credential}} erfolgreich erstellt",
"Created by": "Erstellt von",
"Custom": "Benutzerdefiniert",
"Enable SCIM": "SCIM aktivieren",
"Enter a descriptive name": "Geben Sie einen beschreibenden Namen ein",
"I've saved my {{credential}}": "Ich habe meine(n) {{credential}} gespeichert",
"Important": "Wichtig",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Stellen Sie sicher, dass Sie Ihre(n) {{credential}} jetzt kopieren. Sie können sie/ihn später nicht erneut anzeigen!",
"Never": "Nie",
"Revoke {{credential}}": "{{credential}} widerrufen",
"SCIM endpoint URL": "SCIM-Endpunkt-URL",
"SCIM provisioning": "SCIM-Bereitstellung",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM hat Vorrang vor der SSO-Gruppensynchronisierung, solange es aktiviert ist.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Sie haben die maximale Anzahl von {{max}} SCIM-Token erreicht. Löschen Sie ein vorhandenes Token, um ein neues zu erstellen.",
"SCIM token": "SCIM-Token",
"SCIM tokens": "SCIM-Token",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Diese Aktion kann nicht rückgängig gemacht werden. Ihr Identitätsanbieter wird die Synchronisierung sofort beenden.",
"Toggle SCIM provisioning": "SCIM-Bereitstellung umschalten",
"Token": "Token",
"Page menu": "Seitenmenü",
"Expand": "Erweitern",
"Collapse": "Reduzieren",
"Comment menu": "Kommentarmenü",
"Group menu": "Gruppenmenü",
"Show hidden breadcrumbs": "Ausgeblendete Breadcrumbs anzeigen",
"Breadcrumbs": "Navigationspfade",
"Page actions": "Seitenaktionen",
"Pick emoji": "Emoji auswählen",
"Template menu": "Vorlagenmenü",
"Use": "Verwenden",
"Use template": "Vorlage verwenden",
"Preview template: {{title}}": "Vorlage anzeigen: {{title}}",
"Use a template": "Eine Vorlage verwenden",
"Search templates...": "Vorlagen suchen...",
"Search spaces...": "Bereiche suchen...",
"No templates found": "Keine Vorlagen gefunden",
"No spaces found": "Keine Bereiche gefunden",
"Browse all templates": "Alle Vorlagen durchsuchen",
"This space": "Dieser Bereich",
"All templates": "Alle Vorlagen",
"Global": "Global",
"New template": "Neue Vorlage",
"Edit template": "Vorlage bearbeiten",
"Are you sure you want to delete this template?": "Sind Sie sicher, dass Sie diese Vorlage löschen möchten?",
"Template scope updated": "Vorlagenbereich aktualisiert",
"Choose which space this template belongs to": "Wählen Sie den Bereich aus, zu dem diese Vorlage gehört",
"Scope": "Bereich",
"Select scope": "Bereich auswählen",
"Title": "Titel",
"Saving...": "Wird gespeichert...",
"Saved": "Gespeichert",
"Save failed. Retry": "Speichern fehlgeschlagen. Erneut versuchen",
"By {{name}}": "Von {{name}}",
"Updated {{time}}": "Aktualisiert {{time}}",
"Choose destination": "Ziel auswählen",
"Search pages and spaces...": "Seiten und Bereiche suchen...",
"No results found": "Keine Ergebnisse gefunden",
"You don't have permission to create pages here": "Sie haben hier keine Berechtigung, Seiten zu erstellen",
"Chat menu": "Chatmenü",
"API key menu": "API-Schlüssel-Menü",
"Jump to comment selection": "Zur Kommentarauswahl springen",
"Slash commands": "Slash-Befehle",
"Mention suggestions": "Erwähnungsvorschläge",
"Link suggestions": "Linkvorschläge",
"Diagram editor": "Diagrammeditor",
"Add comment": "Kommentar hinzufügen",
"Find and replace": "Suchen und ersetzen",
"Main navigation": "Hauptnavigation",
"Space navigation": "Bereichsnavigation",
"Settings navigation": "Einstellungsnavigation",
"AI navigation": "KI-Navigation",
"Breadcrumb": "Navigationspfad",
"Synced block": "Synchronisierter Block",
"Create a block that stays in sync across pages.": "Erstellt einen Block der über mehrere Seiten synchronisiert wird",
"Editing original": "Original bearbeiten",
"Copy synced block": "Synchronisierten Block kopieren",
"Unsync": "Synchronisierung aufheben",
"Delete synced block": "Synchronisierten Block löschen",
"Synced to {{count}} other page_one": "Mit {{count}} anderer Seite synchronisiert",
"Synced to {{count}} other page_other": "Mit {{count}} anderen Seiten synchronisiert",
"ORIGINAL": "ORIGINAL",
"THIS PAGE": "DIESE SEITE",
"No pages": "Keine Seiten",
"The original synced block no longer exists": "Der originale synchronisierte Block existiert nicht mehr",
"You don't have access to this synced block": "Sie haben keinen Zugriff auf diesen synchronisierten Block",
"Failed to load this synced block": "Dieser synchronisierte Block konnte nicht geladen werden",
"Fixed editor toolbar": "Fixierte Editor-Symbolleiste",
"Show a formatting toolbar above the editor with quick access to common actions.": "Anzeige einer Formatierungs-Symbolleiste über dem Editor für schnellen Zugriff auf Aktionen.",
"Toggle fixed editor toolbar": "Fixierte Editor-Symbolleiste ein/aus",
"Normal text": "Normaler Text",
"More inline formatting": "Weitere Formatierung",
"Subscript": "Tiefgestellt",
"Superscript": "Hochgestellt",
"Inline code": "Inline-Code",
"Insert media": "Medien einfügen",
"Mention": "Erwähnung",
"Emoji": "Emoji",
"Columns": "Spalten",
"More inserts": "Weiteren Inhalt einfügen",
"Embeds": "Einbettungen",
"Diagrams": "Diagramme",
"Advanced": "Erweitert",
"Utility": "Dienstprogramme",
"Decrease indent": "Einzug verkleinern",
"Increase indent": "Einzug vergrößern",
"Clear formatting": "Formatierung zurücksetzen",
"Code block": "Codeblock",
"Experimental": "Experimentell",
"Strikethrough": "Durchgestrichen",
"Undo": "Rückgängig",
"Redo": "Wiederholen",
"Backlinks": "Rückverweise",
"Last updated by": "Zuletzt aktualisiert von",
"Last updated": "Zuletzt aktualisiert",
"Stats": "Statistiken",
"Word count": "Wörter",
"Characters": "Zeichen",
"Incoming links": "Eingehende Links",
"Outgoing links": "Ausgehende Links",
"Incoming links ({{count}})": "Eingehende Links ({{count}})",
"Outgoing links ({{count}})": "Ausgehende Links ({{count}})",
"No pages link here yet.": "Aktuell verlinken keine Seiten hierher.",
"This page doesn't link to other pages yet.": "Diese Seite verlinkt noch nicht auf andere Seiten.",
"Verified until {{date}}": "Verifiziert bis zum {{date}}",
"Labels": "Beschriftungen",
"Add label": "Beschriftung hinzufügen",
"No labels yet": "Noch keine Beschriftungen",
"Already added": "Bereits hinzugefügt",
"Invalid label name": "Ungültiger Beschriftungsname",
"No matches": "Keine Treffer",
"Search or create…": "Suchen oder erstellen…",
"Remove label {{name}}": "Beschriftung {{name}} entfernen",
"Failed to add label": "Beschriftung konnte nicht hinzugefügt werden",
"Failed to remove label": "Beschriftung konnte nicht entfernt werden",
"No pages with this label": "Keine Seiten mit dieser Beschriftung",
"Pages tagged with this label will appear here.": "Hier werden Seiten angezeigt, die mit dieser Beschriftung versehen sind.",
"No pages match your search.": "Es konnten keine Seiten gefunden werden, die mit Ihrer Suche übereinstimmen.",
"Updated {{date}}": "Aktualisiert am {{date}}",
"Cell actions": "Zellaktionen",
"Column actions": "Spaltenaktionen",
"Row actions": "Zeilenaktionen",
"Filter": "Filter",
"Page title": "Seitentitel",
"Page content": "Seiteninhalt",
"Member actions": "Mitgliederaktionen",
"Toggle password visibility": "Passwortsichtbarkeit umschalten",
"Send comment": "Kommentar senden",
"Token actions": "Token-Aktionen",
"Template settings": "Vorlageneinstellungen",
"Edit diagram": "Diagramm bearbeiten",
"Edit embed": "Einbettung bearbeiten",
"Edit drawing": "Zeichnung bearbeiten",
"Delete equation": "Gleichung löschen",
"Invite actions": "Einladungsaktionen",
"Get started": "Erste Schritte",
"* indicates required fields": "* kennzeichnet Pflichtfelder",
"List of spaces in this workspace": "Liste der Bereiche in diesem Workspace",
"Active sessions": "Aktive Sitzungen",
"Add {{name}} to favorites": "{{name}} zu Favoriten hinzufügen",
"Remove {{name}} from favorites": "{{name}} aus Favoriten entfernen",
"Added to favorites": "Zu Favoriten hinzugefügt",
"Removed from favorites": "Aus Favoriten entfernt",
"Added {{name}} to favorites": "{{name}} zu Favoriten hinzugefügt",
"Removed {{name}} from favorites": "{{name}} aus Favoriten entfernt",
"Page menu for {{name}}": "Seitenmenü für {{name}}",
"Create subpage of {{name}}": "Unterseite von {{name}} erstellen"
} }
@@ -71,6 +71,7 @@
"Export": "Export", "Export": "Export",
"Failed to create page": "Failed to create page", "Failed to create page": "Failed to create page",
"Failed to delete page": "Failed to delete page", "Failed to delete page": "Failed to delete page",
"Failed to restore page": "Failed to restore page",
"Failed to fetch recent pages": "Failed to fetch recent pages", "Failed to fetch recent pages": "Failed to fetch recent pages",
"Failed to import pages": "Failed to import pages", "Failed to import pages": "Failed to import pages",
"Failed to load page. An error occurred.": "Failed to load page. An error occurred.", "Failed to load page. An error occurred.": "Failed to load page. An error occurred.",
@@ -276,6 +277,9 @@
"Align left": "Align left", "Align left": "Align left",
"Align right": "Align right", "Align right": "Align right",
"Align center": "Align center", "Align center": "Align center",
"Alt text": "Alt text",
"Describe this for accessibility.": "Describe this for accessibility.",
"Add a description": "Add a description",
"Justify": "Justify", "Justify": "Justify",
"Merge cells": "Merge cells", "Merge cells": "Merge cells",
"Split cell": "Split cell", "Split cell": "Split cell",
@@ -286,6 +290,19 @@
"Add row above": "Add row above", "Add row above": "Add row above",
"Add row below": "Add row below", "Add row below": "Add row below",
"Delete table": "Delete table", "Delete table": "Delete table",
"Add column left": "Add column left",
"Add column right": "Add column right",
"Clear cell": "Clear cell",
"Clear cells": "Clear cells",
"Toggle header cell": "Toggle header cell",
"Toggle header column": "Toggle header column",
"Toggle header row": "Toggle header row",
"Move column left": "Move column left",
"Move column right": "Move column right",
"Move row down": "Move row down",
"Move row up": "Move row up",
"Sort A → Z": "Sort A → Z",
"Sort Z → A": "Sort Z → A",
"Info": "Info", "Info": "Info",
"Note": "Note", "Note": "Note",
"Success": "Success", "Success": "Success",
@@ -348,6 +365,8 @@
"Create block quote.": "Create block quote.", "Create block quote.": "Create block quote.",
"Insert code snippet.": "Insert code snippet.", "Insert code snippet.": "Insert code snippet.",
"Insert horizontal rule divider": "Insert horizontal rule divider", "Insert horizontal rule divider": "Insert horizontal rule divider",
"Page break": "Page break",
"Insert a page break for printing.": "Insert a page break for printing.",
"Upload any image from your device.": "Upload any image from your device.", "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 video from your device.": "Upload any video from your device.",
"Upload any audio from your device.": "Upload any audio from your device.", "Upload any audio from your device.": "Upload any audio from your device.",
@@ -392,6 +411,10 @@
"Write...": "Write...", "Write...": "Write...",
"Column count": "Column count", "Column count": "Column count",
"{{count}} Columns": "{{count}} Columns", "{{count}} Columns": "{{count}} Columns",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Equal columns", "Equal columns": "Equal columns",
"Left sidebar": "Left sidebar", "Left sidebar": "Left sidebar",
"Right sidebar": "Right sidebar", "Right sidebar": "Right sidebar",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} is available", "{{latestVersion}} is available": "{{latestVersion}} is available",
"Default page edit mode": "Default page edit mode", "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.", "Choose your preferred page edit mode. Avoid accidental edits.": "Choose your preferred page edit mode. Avoid accidental edits.",
"Choose {{format}} file": "Choose {{format}} file",
"Reading": "Reading", "Reading": "Reading",
"Delete member": "Delete member", "Delete member": "Delete member",
"Member deleted successfully": "Member deleted successfully", "Member deleted successfully": "Member deleted successfully",
@@ -565,6 +589,8 @@
"Move to trash": "Move to trash", "Move to trash": "Move to trash",
"Move this page to trash?": "Move this page to trash?", "Move this page to trash?": "Move this page to trash?",
"Restore page": "Restore page", "Restore page": "Restore page",
"Permanently delete": "Permanently delete",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moved this page to Trash {{time}}.",
"Page moved to trash": "Page moved to trash", "Page moved to trash": "Page moved to trash",
"Page restored successfully": "Page restored successfully", "Page restored successfully": "Page restored successfully",
"Deleted by": "Deleted by", "Deleted by": "Deleted by",
@@ -854,9 +880,12 @@
"AI Chat": "AI Chat", "AI Chat": "AI Chat",
"Analyze for insights": "Analyze for insights", "Analyze for insights": "Analyze for insights",
"Ask anything...": "Ask anything...", "Ask anything...": "Ask anything...",
"Assistant said:": "Assistant said:",
"Chat history": "Chat history", "Chat history": "Chat history",
"Chat name": "Chat name", "Chat name": "Chat name",
"Chat transcript": "Chat transcript",
"Close": "Close", "Close": "Close",
"Copy assistant response": "Copy assistant response",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "Failed to load chat. An error occurred.", "Failed to load chat. An error occurred.": "Failed to load chat. An error occurred.",
"Failed to render this message.": "Failed to render this message.", "Failed to render this message.": "Failed to render this message.",
@@ -866,9 +895,17 @@
"No chats found": "No chats found", "No chats found": "No chats found",
"No conversations yet": "No conversations yet", "No conversations yet": "No conversations yet",
"Open full page": "Open full page", "Open full page": "Open full page",
"Scroll to bottom": "Scroll to bottom",
"You said:": "You said:",
"Previous 7 days": "Previous 7 days", "Previous 7 days": "Previous 7 days",
"Previous 30 days": "Previous 30 days", "Previous 30 days": "Previous 30 days",
"Search chats...": "Search chats...", "Search chats...": "Search chats...",
"Search chats": "Search chats",
"Ask anything... Use @ to mention pages": "Ask anything... Use @ to mention pages",
"Ask anything or search your workspace": "Ask anything or search your workspace",
"Welcome to {{name}}": "Welcome to {{name}}",
"Add files": "Add files",
"Mention a page": "Mention a page",
"Start a new chat to see it here.": "Start a new chat to see it here.", "Start a new chat to see it here.": "Start a new chat to see it here.",
"Summarize this page": "Summarize this page", "Summarize this page": "Summarize this page",
"Toggle AI Chat": "Toggle AI Chat", "Toggle AI Chat": "Toggle AI Chat",
@@ -900,5 +937,152 @@
"SCIM tokens": "SCIM tokens", "SCIM tokens": "SCIM tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.", "This action cannot be undone. Your identity provider will stop syncing immediately.": "This action cannot be undone. Your identity provider will stop syncing immediately.",
"Toggle SCIM provisioning": "Toggle SCIM provisioning", "Toggle SCIM provisioning": "Toggle SCIM provisioning",
"Token": "Token" "Token": "Token",
"Page menu": "Page menu",
"Expand": "Expand",
"Collapse": "Collapse",
"Comment menu": "Comment menu",
"Group menu": "Group menu",
"Show hidden breadcrumbs": "Show hidden breadcrumbs",
"Breadcrumbs": "Breadcrumbs",
"Page actions": "Page actions",
"Pick emoji": "Pick emoji",
"Template menu": "Template menu",
"Use": "Use",
"Use template": "Use template",
"Preview template: {{title}}": "Preview template: {{title}}",
"Use a template": "Use a template",
"Search templates...": "Search templates...",
"Search spaces...": "Search spaces...",
"No templates found": "No templates found",
"No spaces found": "No spaces found",
"Browse all templates": "Browse all templates",
"This space": "This space",
"All templates": "All templates",
"Global": "Global",
"New template": "New template",
"Edit template": "Edit template",
"Are you sure you want to delete this template?": "Are you sure you want to delete this template?",
"Template scope updated": "Template scope updated",
"Choose which space this template belongs to": "Choose which space this template belongs to",
"Scope": "Scope",
"Select scope": "Select scope",
"Title": "Title",
"Saving...": "Saving...",
"Saved": "Saved",
"Save failed. Retry": "Save failed. Retry",
"By {{name}}": "By {{name}}",
"Updated {{time}}": "Updated {{time}}",
"Choose destination": "Choose destination",
"Search pages and spaces...": "Search pages and spaces...",
"No results found": "No results found",
"You don't have permission to create pages here": "You don't have permission to create pages here",
"Chat menu": "Chat menu",
"API key menu": "API key menu",
"Jump to comment selection": "Jump to comment selection",
"Slash commands": "Slash commands",
"Mention suggestions": "Mention suggestions",
"Link suggestions": "Link suggestions",
"Diagram editor": "Diagram editor",
"Add comment": "Add comment",
"Find and replace": "Find and replace",
"Main navigation": "Main navigation",
"Space navigation": "Space navigation",
"Settings navigation": "Settings navigation",
"AI navigation": "AI navigation",
"Breadcrumb": "Breadcrumb",
"Synced block": "Synced block",
"Create a block that stays in sync across pages.": "Create a block that stays in sync across pages.",
"Editing original": "Editing original",
"Copy synced block": "Copy synced block",
"Unsync": "Unsync",
"Delete synced block": "Delete synced block",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "ORIGINAL",
"THIS PAGE": "THIS PAGE",
"No pages": "No pages",
"The original synced block no longer exists": "The original synced block no longer exists",
"You don't have access to this synced block": "You don't have access to this synced block",
"Failed to load this synced block": "Failed to load this synced block",
"Fixed editor toolbar": "Fixed editor toolbar",
"Show a formatting toolbar above the editor with quick access to common actions.": "Show a formatting toolbar above the editor with quick access to common actions.",
"Toggle fixed editor toolbar": "Toggle fixed editor toolbar",
"Normal text": "Normal text",
"More inline formatting": "More inline formatting",
"Subscript": "Subscript",
"Superscript": "Superscript",
"Inline code": "Inline code",
"Insert media": "Insert media",
"Mention": "Mention",
"Emoji": "Emoji",
"Columns": "Columns",
"More inserts": "More inserts",
"Embeds": "Embeds",
"Diagrams": "Diagrams",
"Advanced": "Advanced",
"Utility": "Utility",
"Decrease indent": "Decrease indent",
"Increase indent": "Increase indent",
"Clear formatting": "Clear formatting",
"Code block": "Code block",
"Experimental": "Experimental",
"Strikethrough": "Strikethrough",
"Undo": "Undo",
"Redo": "Redo",
"Backlinks": "Backlinks",
"Last updated by": "Last updated by",
"Last updated": "Last updated",
"Stats": "Stats",
"Word count": "Word count",
"Characters": "Characters",
"Incoming links": "Incoming links",
"Outgoing links": "Outgoing links",
"Incoming links ({{count}})": "Incoming links ({{count}})",
"Outgoing links ({{count}})": "Outgoing links ({{count}})",
"No pages link here yet.": "No pages link here yet.",
"This page doesn't link to other pages yet.": "This page doesn't link to other pages yet.",
"Verified until {{date}}": "Verified until {{date}}",
"Labels": "Labels",
"Add label": "Add label",
"No labels yet": "No labels yet",
"Already added": "Already added",
"Invalid label name": "Invalid label name",
"No matches": "No matches",
"Search or create…": "Search or create…",
"Remove label {{name}}": "Remove label {{name}}",
"Failed to add label": "Failed to add label",
"Failed to remove label": "Failed to remove label",
"No pages with this label": "No pages with this label",
"Pages tagged with this label will appear here.": "Pages tagged with this label will appear here.",
"No pages match your search.": "No pages match your search.",
"Updated {{date}}": "Updated {{date}}",
"Cell actions": "Cell actions",
"Column actions": "Column actions",
"Row actions": "Row actions",
"Filter": "Filter",
"Page title": "Page title",
"Page content": "Page content",
"Member actions": "Member actions",
"Toggle password visibility": "Toggle password visibility",
"Send comment": "Send comment",
"Token actions": "Token actions",
"Template settings": "Template settings",
"Edit diagram": "Edit diagram",
"Edit embed": "Edit embed",
"Edit drawing": "Edit drawing",
"Delete equation": "Delete equation",
"Invite actions": "Invite actions",
"Get started": "Get started",
"* indicates required fields": "* indicates required fields",
"List of spaces in this workspace": "List of spaces in this workspace",
"Active sessions": "Active sessions",
"Add {{name}} to favorites": "Add {{name}} to favorites",
"Remove {{name}} from favorites": "Remove {{name}} from favorites",
"Added to favorites": "Added to favorites",
"Removed from favorites": "Removed from favorites",
"Added {{name}} to favorites": "Added {{name}} to favorites",
"Removed {{name}} from favorites": "Removed {{name}} from favorites",
"Page menu for {{name}}": "Page menu for {{name}}",
"Create subpage of {{name}}": "Create subpage of {{name}}"
} }
@@ -71,6 +71,7 @@
"Export": "Exportar", "Export": "Exportar",
"Failed to create page": "No se pudo crear la página", "Failed to create page": "No se pudo crear la página",
"Failed to delete page": "No se pudo eliminar la página", "Failed to delete page": "No se pudo eliminar la página",
"Failed to restore page": "No se pudo restaurar la página",
"Failed to fetch recent pages": "Error al obtener las páginas recientes", "Failed to fetch recent pages": "Error al obtener las páginas recientes",
"Failed to import pages": "No se pudieron importar las páginas", "Failed to import pages": "No se pudieron importar las páginas",
"Failed to load page. An error occurred.": "Error al cargar la página. Se produjo un error.", "Failed to load page. An error occurred.": "Error al cargar la página. Se produjo un error.",
@@ -276,6 +277,9 @@
"Align left": "Alinear a la izquierda", "Align left": "Alinear a la izquierda",
"Align right": "Alinear a la derecha", "Align right": "Alinear a la derecha",
"Align center": "Alinear al centro", "Align center": "Alinear al centro",
"Alt text": "Texto alternativo",
"Describe this for accessibility.": "Describe esto para la accesibilidad.",
"Add a description": "Agregar una descripción",
"Justify": "Justificar", "Justify": "Justificar",
"Merge cells": "Combinar celdas", "Merge cells": "Combinar celdas",
"Split cell": "Dividir celda", "Split cell": "Dividir celda",
@@ -286,6 +290,19 @@
"Add row above": "Agregar fila arriba", "Add row above": "Agregar fila arriba",
"Add row below": "Agregar fila debajo", "Add row below": "Agregar fila debajo",
"Delete table": "Eliminar tabla", "Delete table": "Eliminar tabla",
"Add column left": "Agregar columna a la izquierda",
"Add column right": "Agregar columna a la derecha",
"Clear cell": "Borrar celda",
"Clear cells": "Borrar celdas",
"Toggle header cell": "Alternar celda de encabezado",
"Toggle header column": "Alternar columna de encabezado",
"Toggle header row": "Alternar fila de encabezado",
"Move column left": "Mover columna a la izquierda",
"Move column right": "Mover columna a la derecha",
"Move row down": "Mover fila hacia abajo",
"Move row up": "Mover fila hacia arriba",
"Sort A → Z": "Ordenar de A → Z",
"Sort Z → A": "Ordenar de Z → A",
"Info": "Información", "Info": "Información",
"Note": "Nota", "Note": "Nota",
"Success": "Satisfactorio", "Success": "Satisfactorio",
@@ -348,6 +365,8 @@
"Create block quote.": "Crear una cita en bloque.", "Create block quote.": "Crear una cita en bloque.",
"Insert code snippet.": "Insertar fragmento de código.", "Insert code snippet.": "Insertar fragmento de código.",
"Insert horizontal rule divider": "Insertar regla horizontal", "Insert horizontal rule divider": "Insertar regla horizontal",
"Page break": "Salto de página",
"Insert a page break for printing.": "Inserta un salto de página para imprimir.",
"Upload any image from your device.": "Sube cualquier imagen desde tu dispositivo.", "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 video from your device.": "Sube cualquier video desde tu dispositivo.",
"Upload any audio from your device.": "Sube cualquier audio desde tu dispositivo.", "Upload any audio from your device.": "Sube cualquier audio desde tu dispositivo.",
@@ -392,6 +411,10 @@
"Write...": "Escribe...", "Write...": "Escribe...",
"Column count": "Número de columnas", "Column count": "Número de columnas",
"{{count}} Columns": "{count, plural, one {# columna} other {# columnas}}", "{{count}} Columns": "{count, plural, one {# columna} other {# columnas}}",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Columnas iguales", "Equal columns": "Columnas iguales",
"Left sidebar": "Barra lateral izquierda", "Left sidebar": "Barra lateral izquierda",
"Right sidebar": "Barra lateral derecha", "Right sidebar": "Barra lateral derecha",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} está disponible", "{{latestVersion}} is available": "{{latestVersion}} está disponible",
"Default page edit mode": "Modo de edición predeterminado de la página", "Default page edit mode": "Modo de edición predeterminado de la página",
"Choose your preferred page edit mode. Avoid accidental edits.": "Elige tu modo de edición de página preferido. Evita ediciones accidentales.", "Choose your preferred page edit mode. Avoid accidental edits.": "Elige tu modo de edición de página preferido. Evita ediciones accidentales.",
"Choose {{format}} file": "Elegir archivo {{format}}",
"Reading": "Lectura", "Reading": "Lectura",
"Delete member": "Eliminar miembro", "Delete member": "Eliminar miembro",
"Member deleted successfully": "Miembro eliminado correctamente", "Member deleted successfully": "Miembro eliminado correctamente",
@@ -565,6 +589,8 @@
"Move to trash": "Mover a la papelera", "Move to trash": "Mover a la papelera",
"Move this page to trash?": "¿Mover esta página a la papelera?", "Move this page to trash?": "¿Mover esta página a la papelera?",
"Restore page": "Restaurar página", "Restore page": "Restaurar página",
"Permanently delete": "Eliminar permanentemente",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> movió esta página a la Papelera {{time}}.",
"Page moved to trash": "Página movida a la papelera", "Page moved to trash": "Página movida a la papelera",
"Page restored successfully": "Página restaurada correctamente", "Page restored successfully": "Página restaurada correctamente",
"Deleted by": "Eliminado por", "Deleted by": "Eliminado por",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "La imagen excede del límite de 10 MB", "Image exceeds 10MB limit.": "La imagen excede del límite de 10 MB",
"Image removed successfully": "Imagen eliminada correctamente", "Image removed successfully": "Imagen eliminada correctamente",
"API key": "Clave API", "API key": "Clave API",
"API key created successfully": "Clave API creada correctamente",
"API keys": "Claves API", "API keys": "Claves API",
"API management": "Gestión de 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", "Custom expiration date": "Fecha de vencimiento personalizada",
"Enter a descriptive token name": "Introduce un nombre descriptivo del token", "Enter a descriptive token name": "Introduce un nombre descriptivo del token",
"Expiration": "Vencimiento", "Expiration": "Vencimiento",
"Expired": "Vencido", "Expired": "Vencido",
"Expires": "Vence", "Expires": "Vence",
"I've saved my API key": "He guardado mi clave API",
"Last use": "Último uso", "Last use": "Último uso",
"No API keys found": "No se han encontrado claves API", "No API keys found": "No se han encontrado claves API",
"No expiration": "Sin vencimiento", "No expiration": "Sin vencimiento",
"Revoke API key": "Revocar clave API",
"Revoked successfully": "Revocada correctamente", "Revoked successfully": "Revocada correctamente",
"Select expiration date": "Seleccionar fecha de vencimiento", "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.", "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", "Update": "Actualizar",
"Update {{credential}}": "Actualizar {{credential}}",
"Manage API keys for all users in the workspace": "Gestionar claves API para todos los usuarios en el espacio de trabajo", "Manage API keys for all users in the workspace": "Gestionar claves API para todos los usuarios en el espacio de trabajo",
"Restrict API key creation to admins": "Restringir la creación de claves API a administradores", "Restrict API key creation to admins": "Restringir la creación de claves API a administradores",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo los administradores y propietarios pueden crear nuevas claves API. Las claves de miembros existentes seguirán funcionando.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo los administradores y propietarios pueden crear nuevas claves API. Las claves de miembros existentes seguirán funcionando.",
@@ -858,9 +880,12 @@
"AI Chat": "Chat de IA", "AI Chat": "Chat de IA",
"Analyze for insights": "Analizar para obtener información", "Analyze for insights": "Analizar para obtener información",
"Ask anything...": "Pregunta lo que quieras...", "Ask anything...": "Pregunta lo que quieras...",
"Assistant said:": "El asistente dijo:",
"Chat history": "Historial de chat", "Chat history": "Historial de chat",
"Chat name": "Nombre del chat", "Chat name": "Nombre del chat",
"Chat transcript": "Transcripción del chat",
"Close": "Cerrar", "Close": "Cerrar",
"Copy assistant response": "Copiar respuesta del asistente",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "No se pudo cargar el chat. Se produjo un error.", "Failed to load chat. An error occurred.": "No se pudo cargar el chat. Se produjo un error.",
"Failed to render this message.": "No se pudo mostrar este mensaje.", "Failed to render this message.": "No se pudo mostrar este mensaje.",
@@ -870,9 +895,17 @@
"No chats found": "No se encontraron chats", "No chats found": "No se encontraron chats",
"No conversations yet": "Aún no hay conversaciones", "No conversations yet": "Aún no hay conversaciones",
"Open full page": "Abrir página completa", "Open full page": "Abrir página completa",
"Scroll to bottom": "Desplazarse hasta abajo",
"You said:": "Dijiste:",
"Previous 7 days": "Últimos 7 días", "Previous 7 days": "Últimos 7 días",
"Previous 30 days": "Últimos 30 días", "Previous 30 days": "Últimos 30 días",
"Search chats...": "Buscar chats...", "Search chats...": "Buscar chats...",
"Search chats": "Buscar chats",
"Ask anything... Use @ to mention pages": "Pregunta lo que sea... Usa @ para mencionar páginas",
"Ask anything or search your workspace": "Pregunta cualquier cosa o busca en tu espacio de trabajo",
"Welcome to {{name}}": "Te damos la bienvenida a {{name}}",
"Add files": "Agregar archivos",
"Mention a page": "Mencionar una página",
"Start a new chat to see it here.": "Inicia un nuevo chat para verlo aquí.", "Start a new chat to see it here.": "Inicia un nuevo chat para verlo aquí.",
"Summarize this page": "Resumir esta página", "Summarize this page": "Resumir esta página",
"Toggle AI Chat": "Alternar chat de IA", "Toggle AI Chat": "Alternar chat de IA",
@@ -880,5 +913,176 @@
"Try a different search term.": "Prueba con otro término de búsqueda.", "Try a different search term.": "Prueba con otro término de búsqueda.",
"Try again": "Intentar de nuevo", "Try again": "Intentar de nuevo",
"Untitled chat": "Chat sin título", "Untitled chat": "Chat sin título",
"What can I help you with?": "¿En qué puedo ayudarte?" "What can I help you with?": "¿En qué puedo ayudarte?",
"Are you sure you want to revoke this {{credential}}": "¿Está seguro de que desea revocar esta {{credential}}?",
"Automatically provision users and groups from your identity provider via SCIM.": "Aprovisione automáticamente usuarios y grupos desde su proveedor de identidad mediante SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure su proveedor de identidad con esta URL para aprovisionar usuarios y grupos.",
"Create {{credential}}": "Crear {{credential}}",
"{{credential}} created": "{{credential}} creada",
"{{credential}} created successfully": "{{credential}} creada con éxito",
"Created by": "Creado por",
"Custom": "Personalizado",
"Enable SCIM": "Habilitar SCIM",
"Enter a descriptive name": "Introduzca un nombre descriptivo",
"I've saved my {{credential}}": "He guardado mi {{credential}}",
"Important": "Importante",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Asegúrese de copiar su {{credential}} ahora. ¡No podrá volver a verla!",
"Never": "Nunca",
"Revoke {{credential}}": "Revocar {{credential}}",
"SCIM endpoint URL": "URL del endpoint de SCIM",
"SCIM provisioning": "Aprovisionamiento SCIM",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM tiene prioridad sobre la sincronización de grupos de SSO mientras esté habilitado.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Ha alcanzado el máximo de {{max}} tokens SCIM. Elimine un token existente para crear uno nuevo.",
"SCIM token": "Token SCIM",
"SCIM tokens": "Tokens SCIM",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Esta acción no se puede deshacer. Su proveedor de identidad dejará de sincronizarse inmediatamente.",
"Toggle SCIM provisioning": "Activar o desactivar el aprovisionamiento SCIM",
"Token": "Token",
"Page menu": "Menú de la página",
"Expand": "Expandir",
"Collapse": "Contraer",
"Comment menu": "Menú de comentarios",
"Group menu": "Menú del grupo",
"Show hidden breadcrumbs": "Mostrar rutas de navegación ocultas",
"Breadcrumbs": "Rutas de navegación",
"Page actions": "Acciones de la página",
"Pick emoji": "Elegir emoji",
"Template menu": "Menú de plantillas",
"Use": "Usar",
"Use template": "Usar plantilla",
"Preview template: {{title}}": "Vista previa de la plantilla: {{title}}",
"Use a template": "Usar una plantilla",
"Search templates...": "Buscar plantillas...",
"Search spaces...": "Buscar espacios...",
"No templates found": "No se encontraron plantillas",
"No spaces found": "No se encontraron espacios",
"Browse all templates": "Ver todas las plantillas",
"This space": "Este espacio",
"All templates": "Todas las plantillas",
"Global": "Global",
"New template": "Nueva plantilla",
"Edit template": "Editar plantilla",
"Are you sure you want to delete this template?": "¿Seguro que quieres eliminar esta plantilla?",
"Template scope updated": "Alcance de la plantilla actualizado",
"Choose which space this template belongs to": "Elige a qué espacio pertenece esta plantilla",
"Scope": "Alcance",
"Select scope": "Seleccionar alcance",
"Title": "Título",
"Saving...": "Guardando...",
"Saved": "Guardado",
"Save failed. Retry": "Error al guardar. Reintentar",
"By {{name}}": "Por {{name}}",
"Updated {{time}}": "Actualizado {{time}}",
"Choose destination": "Elegir destino",
"Search pages and spaces...": "Buscar páginas y espacios...",
"No results found": "No se encontraron resultados",
"You don't have permission to create pages here": "No tienes permiso para crear páginas aquí",
"Chat menu": "Menú del chat",
"API key menu": "Menú de la clave API",
"Jump to comment selection": "Ir a la selección de comentarios",
"Slash commands": "Comandos de barra",
"Mention suggestions": "Sugerencias de menciones",
"Link suggestions": "Sugerencias de enlaces",
"Diagram editor": "Editor de diagramas",
"Add comment": "Agregar comentario",
"Find and replace": "Buscar y reemplazar",
"Main navigation": "Navegación principal",
"Space navigation": "Navegación del espacio",
"Settings navigation": "Navegación de configuración",
"AI navigation": "Navegación de IA",
"Breadcrumb": "Ruta de navegación",
"Synced block": "Bloque sincronizado",
"Create a block that stays in sync across pages.": "Crea un bloque que se mantenga sincronizado entre páginas.",
"Editing original": "Editando original",
"Copy synced block": "Copiar bloque sincronizado",
"Unsync": "Desincronizar",
"Delete synced block": "Eliminar bloque sincronizado",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "ORIGINAL",
"THIS PAGE": "ESTA PÁGINA",
"No pages": "No hay páginas",
"The original synced block no longer exists": "El bloque sincronizado original ya no existe",
"You don't have access to this synced block": "No tienes acceso a este bloque sincronizado",
"Failed to load this synced block": "No se pudo cargar este bloque sincronizado",
"Fixed editor toolbar": "Barra de herramientas fija del editor",
"Show a formatting toolbar above the editor with quick access to common actions.": "Muestra una barra de herramientas de formato sobre el editor con acceso rápido a acciones comunes.",
"Toggle fixed editor toolbar": "Alternar barra de herramientas fija del editor",
"Normal text": "Texto normal",
"More inline formatting": "Más formato en línea",
"Subscript": "Subíndice",
"Superscript": "Superíndice",
"Inline code": "Código en línea",
"Insert media": "Insertar contenido multimedia",
"Mention": "Mención",
"Emoji": "Emojis",
"Columns": "Columnas",
"More inserts": "Más inserciones",
"Embeds": "Integraciones",
"Diagrams": "Diagramas",
"Advanced": "Avanzado",
"Utility": "Utilidad",
"Decrease indent": "Disminuir sangría",
"Increase indent": "Aumentar sangría",
"Clear formatting": "Borrar formato",
"Code block": "Bloque de código",
"Experimental": "Experimental",
"Strikethrough": "Tachado",
"Undo": "Deshacer",
"Redo": "Rehacer",
"Backlinks": "Enlaces entrantes",
"Last updated by": "Última actualización por",
"Last updated": "Última actualización",
"Stats": "Estadísticas",
"Word count": "Recuento de palabras",
"Characters": "Caracteres",
"Incoming links": "Enlaces entrantes",
"Outgoing links": "Enlaces salientes",
"Incoming links ({{count}})": "Enlaces entrantes ({{count}})",
"Outgoing links ({{count}})": "Enlaces salientes ({{count}})",
"No pages link here yet.": "Todavía no hay páginas que enlacen aquí.",
"This page doesn't link to other pages yet.": "Esta página todavía no enlaza a otras páginas.",
"Verified until {{date}}": "Verificado hasta {{date}}",
"Labels": "Etiquetas",
"Add label": "Agregar etiqueta",
"No labels yet": "Todavía no hay etiquetas",
"Already added": "Ya agregado",
"Invalid label name": "Nombre de etiqueta no válido",
"No matches": "Sin coincidencias",
"Search or create…": "Buscar o crear…",
"Remove label {{name}}": "Eliminar etiqueta {{name}}",
"Failed to add label": "No se pudo agregar la etiqueta",
"Failed to remove label": "No se pudo eliminar la etiqueta",
"No pages with this label": "No hay páginas con esta etiqueta",
"Pages tagged with this label will appear here.": "Las páginas etiquetadas con esta etiqueta aparecerán aquí.",
"No pages match your search.": "Ninguna página coincide con tu búsqueda.",
"Updated {{date}}": "Actualizado el {{date}}",
"Cell actions": "Acciones de celda",
"Column actions": "Acciones de columna",
"Row actions": "Acciones de fila",
"Filter": "Filtrar",
"Page title": "Título de la página",
"Page content": "Contenido de la página",
"Member actions": "Acciones de miembro",
"Toggle password visibility": "Alternar visibilidad de la contraseña",
"Send comment": "Enviar comentario",
"Token actions": "Acciones de token",
"Template settings": "Configuración de la plantilla",
"Edit diagram": "Editar diagrama",
"Edit embed": "Editar contenido integrado",
"Edit drawing": "Editar dibujo",
"Delete equation": "Eliminar ecuación",
"Invite actions": "Acciones de invitación",
"Get started": "Comenzar",
"* indicates required fields": "* indica los campos obligatorios",
"List of spaces in this workspace": "Lista de espacios en este espacio de trabajo",
"Active sessions": "Sesiones activas",
"Add {{name}} to favorites": "Agregar {{name}} a favoritos",
"Remove {{name}} from favorites": "Quitar {{name}} de favoritos",
"Added to favorites": "Agregado a favoritos",
"Removed from favorites": "Quitado de favoritos",
"Added {{name}} to favorites": "Se agregó {{name}} a favoritos",
"Removed {{name}} from favorites": "Se quitó {{name}} de favoritos",
"Page menu for {{name}}": "Menú de página para {{name}}",
"Create subpage of {{name}}": "Crear subpágina de {{name}}"
} }
@@ -71,6 +71,7 @@
"Export": "Exporter", "Export": "Exporter",
"Failed to create page": "Échec de la création de la page", "Failed to create page": "Échec de la création de la page",
"Failed to delete page": "Échec de la suppression de la page", "Failed to delete page": "Échec de la suppression de la page",
"Failed to restore page": "Échec de la restauration de la page",
"Failed to fetch recent pages": "Échec de la récupération des pages récentes", "Failed to fetch recent pages": "Échec de la récupération des pages récentes",
"Failed to import pages": "Échec de l'importation des pages", "Failed to import pages": "Échec de l'importation des pages",
"Failed to load page. An error occurred.": "Échec du chargement de la page. Une erreur s'est produite.", "Failed to load page. An error occurred.": "Échec du chargement de la page. Une erreur s'est produite.",
@@ -276,6 +277,9 @@
"Align left": "Aligner à gauche", "Align left": "Aligner à gauche",
"Align right": "Aligner à droite", "Align right": "Aligner à droite",
"Align center": "Aligner au centre", "Align center": "Aligner au centre",
"Alt text": "Texte alternatif",
"Describe this for accessibility.": "Décrivez ceci pour laccessibilité.",
"Add a description": "Ajouter une description",
"Justify": "Justifier", "Justify": "Justifier",
"Merge cells": "Fusionner les cellules", "Merge cells": "Fusionner les cellules",
"Split cell": "Diviser la cellule", "Split cell": "Diviser la cellule",
@@ -286,6 +290,19 @@
"Add row above": "Ajouter une ligne au-dessus", "Add row above": "Ajouter une ligne au-dessus",
"Add row below": "Ajouter une ligne en dessous", "Add row below": "Ajouter une ligne en dessous",
"Delete table": "Supprimer le tableau", "Delete table": "Supprimer le tableau",
"Add column left": "Ajouter une colonne à gauche",
"Add column right": "Ajouter une colonne à droite",
"Clear cell": "Effacer la cellule",
"Clear cells": "Effacer les cellules",
"Toggle header cell": "Activer/désactiver la cellule den-tête",
"Toggle header column": "Activer/désactiver la colonne den-tête",
"Toggle header row": "Activer/désactiver la ligne den-tête",
"Move column left": "Déplacer la colonne vers la gauche",
"Move column right": "Déplacer la colonne vers la droite",
"Move row down": "Déplacer la ligne vers le bas",
"Move row up": "Déplacer la ligne vers le haut",
"Sort A → Z": "Trier de A à Z",
"Sort Z → A": "Trier de Z à A",
"Info": "Info", "Info": "Info",
"Note": "Remarque", "Note": "Remarque",
"Success": "Succès", "Success": "Succès",
@@ -348,6 +365,8 @@
"Create block quote.": "Créez un bloc de citation.", "Create block quote.": "Créez un bloc de citation.",
"Insert code snippet.": "Insérez un extrait de code.", "Insert code snippet.": "Insérez un extrait de code.",
"Insert horizontal rule divider": "Insérer un séparateur de règle horizontale", "Insert horizontal rule divider": "Insérer un séparateur de règle horizontale",
"Page break": "Saut de page",
"Insert a page break for printing.": "Insérer un saut de page pour limpression.",
"Upload any image from your device.": "Téléchargez n'importe quelle image depuis votre appareil.", "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 video from your device.": "Téléchargez n'importe quelle vidéo depuis votre appareil.",
"Upload any audio from your device.": "Téléchargez n'importe quel fichier audio depuis votre appareil.", "Upload any audio from your device.": "Téléchargez n'importe quel fichier audio depuis votre appareil.",
@@ -392,6 +411,10 @@
"Write...": "Écrire...", "Write...": "Écrire...",
"Column count": "Nombre de colonnes", "Column count": "Nombre de colonnes",
"{{count}} Columns": "{count, plural, one {# colonne} other {# colonnes}}", "{{count}} Columns": "{count, plural, one {# colonne} other {# colonnes}}",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Colonnes égales", "Equal columns": "Colonnes égales",
"Left sidebar": "Barre latérale gauche", "Left sidebar": "Barre latérale gauche",
"Right sidebar": "Barre latérale droite", "Right sidebar": "Barre latérale droite",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} est disponible", "{{latestVersion}} is available": "{{latestVersion}} est disponible",
"Default page edit mode": "Mode d’édition par défaut de la page", "Default page edit mode": "Mode d’édition par défaut de la page",
"Choose your preferred page edit mode. Avoid accidental edits.": "Choisissez votre mode d'édition de page préféré. Évitez les modifications accidentelles.", "Choose your preferred page edit mode. Avoid accidental edits.": "Choisissez votre mode d'édition de page préféré. Évitez les modifications accidentelles.",
"Choose {{format}} file": "Choisir un fichier {{format}}",
"Reading": "Lecture", "Reading": "Lecture",
"Delete member": "Supprimer le membre", "Delete member": "Supprimer le membre",
"Member deleted successfully": "Membre supprimé avec succès", "Member deleted successfully": "Membre supprimé avec succès",
@@ -565,6 +589,8 @@
"Move to trash": "Déplacer vers la corbeille", "Move to trash": "Déplacer vers la corbeille",
"Move this page to trash?": "Déplacer cette page vers la corbeille ?", "Move this page to trash?": "Déplacer cette page vers la corbeille ?",
"Restore page": "Restaurer la page", "Restore page": "Restaurer la page",
"Permanently delete": "Supprimer définitivement",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> a déplacé cette page vers la corbeille {{time}}.",
"Page moved to trash": "Page déplacée vers la corbeille", "Page moved to trash": "Page déplacée vers la corbeille",
"Page restored successfully": "Page restaurée avec succès", "Page restored successfully": "Page restaurée avec succès",
"Deleted by": "Supprimé par", "Deleted by": "Supprimé par",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "L'image dépasse la limite de 10 Mo.", "Image exceeds 10MB limit.": "L'image dépasse la limite de 10 Mo.",
"Image removed successfully": "Image supprimée avec succès", "Image removed successfully": "Image supprimée avec succès",
"API key": "Clé API", "API key": "Clé API",
"API key created successfully": "Clé API créée avec succès",
"API keys": "Clés API", "API keys": "Clés API",
"API management": "Gestion des 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", "Custom expiration date": "Date d'expiration personnalisée",
"Enter a descriptive token name": "Entrez un nom descriptif pour le jeton", "Enter a descriptive token name": "Entrez un nom descriptif pour le jeton",
"Expiration": "Expiration", "Expiration": "Expiration",
"Expired": "Expiré(e)", "Expired": "Expiré(e)",
"Expires": "Expire", "Expires": "Expire",
"I've saved my API key": "J'ai enregistré ma clé API",
"Last use": "Dernière utilisation", "Last use": "Dernière utilisation",
"No API keys found": "Aucune clé API trouvée", "No API keys found": "Aucune clé API trouvée",
"No expiration": "Pas d'expiration", "No expiration": "Pas d'expiration",
"Revoke API key": "Révoquer la clé API",
"Revoked successfully": "Révoqué(e) avec succès", "Revoked successfully": "Révoqué(e) avec succès",
"Select expiration date": "Sélectionnez la date d'expiration", "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.", "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", "Update": "Mettre à jour",
"Update {{credential}}": "Mettre à jour {{credential}}",
"Manage API keys for all users in the workspace": "Gérer les clés API pour tous les utilisateurs dans l'espace de travail", "Manage API keys for all users in the workspace": "Gérer les clés API pour tous les utilisateurs dans l'espace de travail",
"Restrict API key creation to admins": "Restreindre la création de clés API aux administrateurs", "Restrict API key creation to admins": "Restreindre la création de clés API aux administrateurs",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Seuls les administrateurs et les propriétaires peuvent créer de nouvelles clés API. Les clés des membres existants continueront de fonctionner.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Seuls les administrateurs et les propriétaires peuvent créer de nouvelles clés API. Les clés des membres existants continueront de fonctionner.",
@@ -858,9 +880,12 @@
"AI Chat": "Chat IA", "AI Chat": "Chat IA",
"Analyze for insights": "Analyser pour obtenir des informations", "Analyze for insights": "Analyser pour obtenir des informations",
"Ask anything...": "Posez nimporte quelle question...", "Ask anything...": "Posez nimporte quelle question...",
"Assistant said:": "Lassistant a dit :",
"Chat history": "Historique des discussions", "Chat history": "Historique des discussions",
"Chat name": "Nom de la discussion", "Chat name": "Nom de la discussion",
"Chat transcript": "Transcription du chat",
"Close": "Fermer", "Close": "Fermer",
"Copy assistant response": "Copier la réponse de lassistant",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "Échec du chargement de la discussion. Une erreur sest produite.", "Failed to load chat. An error occurred.": "Échec du chargement de la discussion. Une erreur sest produite.",
"Failed to render this message.": "Échec de laffichage de ce message.", "Failed to render this message.": "Échec de laffichage de ce message.",
@@ -870,9 +895,17 @@
"No chats found": "Aucune discussion trouvée", "No chats found": "Aucune discussion trouvée",
"No conversations yet": "Aucune conversation pour le moment", "No conversations yet": "Aucune conversation pour le moment",
"Open full page": "Ouvrir la page complète", "Open full page": "Ouvrir la page complète",
"Scroll to bottom": "Faire défiler jusquen bas",
"You said:": "Vous avez dit :",
"Previous 7 days": "7 derniers jours", "Previous 7 days": "7 derniers jours",
"Previous 30 days": "30 derniers jours", "Previous 30 days": "30 derniers jours",
"Search chats...": "Rechercher des discussions...", "Search chats...": "Rechercher des discussions...",
"Search chats": "Rechercher des discussions",
"Ask anything... Use @ to mention pages": "Demandez nimporte quoi… Utilisez @ pour mentionner des pages",
"Ask anything or search your workspace": "Posez nimporte quelle question ou recherchez dans votre espace de travail",
"Welcome to {{name}}": "Bienvenue sur {{name}}",
"Add files": "Ajouter des fichiers",
"Mention a page": "Mentionner une page",
"Start a new chat to see it here.": "Commencez une nouvelle discussion pour la voir ici.", "Start a new chat to see it here.": "Commencez une nouvelle discussion pour la voir ici.",
"Summarize this page": "Résumer cette page", "Summarize this page": "Résumer cette page",
"Toggle AI Chat": "Basculer le chat IA", "Toggle AI Chat": "Basculer le chat IA",
@@ -880,5 +913,176 @@
"Try a different search term.": "Essayez un autre terme de recherche.", "Try a different search term.": "Essayez un autre terme de recherche.",
"Try again": "Réessayer", "Try again": "Réessayer",
"Untitled chat": "Discussion sans titre", "Untitled chat": "Discussion sans titre",
"What can I help you with?": "Que puis-je faire pour vous aider ?" "What can I help you with?": "Que puis-je faire pour vous aider ?",
"Are you sure you want to revoke this {{credential}}": "Êtes-vous sûr de vouloir révoquer ce/cette {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Provisionnez automatiquement les utilisateurs et les groupes depuis votre fournisseur didentité via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configurez votre fournisseur didentité avec cette URL pour provisionner les utilisateurs et les groupes.",
"Create {{credential}}": "Créer {{credential}}",
"{{credential}} created": "{{credential}} créé",
"{{credential}} created successfully": "{{credential}} créé avec succès",
"Created by": "Créé par",
"Custom": "Personnalisé",
"Enable SCIM": "Activer SCIM",
"Enter a descriptive name": "Saisissez un nom descriptif",
"I've saved my {{credential}}": "Jai enregistré mon/ma {{credential}}",
"Important": "Important",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Assurez-vous de copier votre {{credential}} maintenant. Vous ne pourrez plus le/la voir ensuite !",
"Never": "Jamais",
"Revoke {{credential}}": "Révoquer {{credential}}",
"SCIM endpoint URL": "URL du point de terminaison SCIM",
"SCIM provisioning": "Provisionnement SCIM",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM a priorité sur la synchronisation des groupes SSO lorsquil est activé.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Vous avez atteint le maximum de {{max}} jetons SCIM. Supprimez un jeton existant pour en créer un nouveau.",
"SCIM token": "Jeton SCIM",
"SCIM tokens": "Jetons SCIM",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Cette action est irréversible. Votre fournisseur didentité cessera immédiatement la synchronisation.",
"Toggle SCIM provisioning": "Activer/désactiver le provisionnement SCIM",
"Token": "Jeton",
"Page menu": "Menu de la page",
"Expand": "Développer",
"Collapse": "Réduire",
"Comment menu": "Menu du commentaire",
"Group menu": "Menu du groupe",
"Show hidden breadcrumbs": "Afficher les fils dAriane masqués",
"Breadcrumbs": "Fils dAriane",
"Page actions": "Actions de la page",
"Pick emoji": "Choisir un emoji",
"Template menu": "Menu du modèle",
"Use": "Utiliser",
"Use template": "Utiliser le modèle",
"Preview template: {{title}}": "Aperçu du modèle : {{title}}",
"Use a template": "Utiliser un modèle",
"Search templates...": "Rechercher des modèles...",
"Search spaces...": "Rechercher des espaces...",
"No templates found": "Aucun modèle trouvé",
"No spaces found": "Aucun espace trouvé",
"Browse all templates": "Parcourir tous les modèles",
"This space": "Cet espace",
"All templates": "Tous les modèles",
"Global": "Global",
"New template": "Nouveau modèle",
"Edit template": "Modifier le modèle",
"Are you sure you want to delete this template?": "Êtes-vous sûr de vouloir supprimer ce modèle ?",
"Template scope updated": "Portée du modèle mise à jour",
"Choose which space this template belongs to": "Choisissez à quel espace appartient ce modèle",
"Scope": "Portée",
"Select scope": "Sélectionner la portée",
"Title": "Titre",
"Saving...": "Enregistrement...",
"Saved": "Enregistré",
"Save failed. Retry": "Échec de lenregistrement. Réessayer",
"By {{name}}": "Par {{name}}",
"Updated {{time}}": "Mis à jour {{time}}",
"Choose destination": "Choisir la destination",
"Search pages and spaces...": "Rechercher des pages et des espaces...",
"No results found": "Aucun résultat trouvé",
"You don't have permission to create pages here": "Vous navez pas lautorisation de créer des pages ici",
"Chat menu": "Menu du chat",
"API key menu": "Menu de la clé API",
"Jump to comment selection": "Aller à la sélection de commentaires",
"Slash commands": "Commandes slash",
"Mention suggestions": "Suggestions de mention",
"Link suggestions": "Suggestions de liens",
"Diagram editor": "Éditeur de diagrammes",
"Add comment": "Ajouter un commentaire",
"Find and replace": "Rechercher et remplacer",
"Main navigation": "Navigation principale",
"Space navigation": "Navigation de lespace",
"Settings navigation": "Navigation des paramètres",
"AI navigation": "Navigation IA",
"Breadcrumb": "Fil dAriane",
"Synced block": "Bloc synchronisé",
"Create a block that stays in sync across pages.": "Créez un bloc qui reste synchronisé entre les pages.",
"Editing original": "Modification de loriginal",
"Copy synced block": "Copier le bloc synchronisé",
"Unsync": "Désynchroniser",
"Delete synced block": "Supprimer le bloc synchronisé",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "ORIGINAL",
"THIS PAGE": "CETTE PAGE",
"No pages": "Aucune page",
"The original synced block no longer exists": "Le bloc synchronisé dorigine nexiste plus",
"You don't have access to this synced block": "Vous navez pas accès à ce bloc synchronisé",
"Failed to load this synced block": "Échec du chargement de ce bloc synchronisé",
"Fixed editor toolbar": "Barre doutils de l’éditeur fixe",
"Show a formatting toolbar above the editor with quick access to common actions.": "Afficher une barre doutils de mise en forme au-dessus de l’éditeur avec un accès rapide aux actions courantes.",
"Toggle fixed editor toolbar": "Activer/désactiver la barre doutils de l’éditeur fixe",
"Normal text": "Texte normal",
"More inline formatting": "Plus de mise en forme en ligne",
"Subscript": "Indice",
"Superscript": "Exposant",
"Inline code": "Code en ligne",
"Insert media": "Insérer un média",
"Mention": "Mention",
"Emoji": "Emoji",
"Columns": "Colonnes",
"More inserts": "Plus dinsertions",
"Embeds": "Intégrations",
"Diagrams": "Diagrammes",
"Advanced": "Avancé",
"Utility": "Utilitaire",
"Decrease indent": "Réduire le retrait",
"Increase indent": "Augmenter le retrait",
"Clear formatting": "Effacer la mise en forme",
"Code block": "Bloc de code",
"Experimental": "Expérimental",
"Strikethrough": "Barré",
"Undo": "Annuler",
"Redo": "Rétablir",
"Backlinks": "Liens retour",
"Last updated by": "Dernière mise à jour par",
"Last updated": "Dernière mise à jour",
"Stats": "Statistiques",
"Word count": "Nombre de mots",
"Characters": "Caractères",
"Incoming links": "Liens entrants",
"Outgoing links": "Liens sortants",
"Incoming links ({{count}})": "Liens entrants ({{count}})",
"Outgoing links ({{count}})": "Liens sortants ({{count}})",
"No pages link here yet.": "Aucune page ne pointe encore ici.",
"This page doesn't link to other pages yet.": "Cette page ne renvoie pas encore vers dautres pages.",
"Verified until {{date}}": "Vérifié jusquau {{date}}",
"Labels": "Étiquettes",
"Add label": "Ajouter une étiquette",
"No labels yet": "Aucune étiquette pour linstant",
"Already added": "Déjà ajouté",
"Invalid label name": "Nom d’étiquette invalide",
"No matches": "Aucune correspondance",
"Search or create…": "Rechercher ou créer…",
"Remove label {{name}}": "Supprimer l’étiquette {{name}}",
"Failed to add label": "Échec de lajout de l’étiquette",
"Failed to remove label": "Échec de la suppression de l’étiquette",
"No pages with this label": "Aucune page avec cette étiquette",
"Pages tagged with this label will appear here.": "Les pages portant cette étiquette apparaîtront ici.",
"No pages match your search.": "Aucune page ne correspond à votre recherche.",
"Updated {{date}}": "Mis à jour le {{date}}",
"Cell actions": "Actions de cellule",
"Column actions": "Actions de colonne",
"Row actions": "Actions de ligne",
"Filter": "Filtrer",
"Page title": "Titre de la page",
"Page content": "Contenu de la page",
"Member actions": "Actions des membres",
"Toggle password visibility": "Afficher/masquer le mot de passe",
"Send comment": "Envoyer le commentaire",
"Token actions": "Actions du jeton",
"Template settings": "Paramètres du modèle",
"Edit diagram": "Modifier le diagramme",
"Edit embed": "Modifier lintégration",
"Edit drawing": "Modifier le dessin",
"Delete equation": "Supprimer l’équation",
"Invite actions": "Actions dinvitation",
"Get started": "Commencer",
"* indicates required fields": "* indique les champs obligatoires",
"List of spaces in this workspace": "Liste des espaces de cet espace de travail",
"Active sessions": "Sessions actives",
"Add {{name}} to favorites": "Ajouter {{name}} aux favoris",
"Remove {{name}} from favorites": "Retirer {{name}} des favoris",
"Added to favorites": "Ajouté aux favoris",
"Removed from favorites": "Retiré des favoris",
"Added {{name}} to favorites": "{{name}} a été ajouté aux favoris",
"Removed {{name}} from favorites": "{{name}} a été retiré des favoris",
"Page menu for {{name}}": "Menu de la page pour {{name}}",
"Create subpage of {{name}}": "Créer une sous-page de {{name}}"
} }
@@ -71,6 +71,7 @@
"Export": "Esporta", "Export": "Esporta",
"Failed to create page": "Impossibile creare la pagina", "Failed to create page": "Impossibile creare la pagina",
"Failed to delete page": "Impossibile eliminare la pagina", "Failed to delete page": "Impossibile eliminare la pagina",
"Failed to restore page": "Impossibile ripristinare la pagina",
"Failed to fetch recent pages": "Impossibile recuperare le pagine recenti", "Failed to fetch recent pages": "Impossibile recuperare le pagine recenti",
"Failed to import pages": "Impossibile importare le pagine", "Failed to import pages": "Impossibile importare le pagine",
"Failed to load page. An error occurred.": "Il caricamento della pagina è fallito. Si è verificato un errore.", "Failed to load page. An error occurred.": "Il caricamento della pagina è fallito. Si è verificato un errore.",
@@ -276,6 +277,9 @@
"Align left": "Allinea a sinistra", "Align left": "Allinea a sinistra",
"Align right": "Allinea a destra", "Align right": "Allinea a destra",
"Align center": "Allinea al centro", "Align center": "Allinea al centro",
"Alt text": "Testo alternativo",
"Describe this for accessibility.": "Descrivi questo contenuto per l'accessibilità.",
"Add a description": "Aggiungi una descrizione",
"Justify": "Giustifica", "Justify": "Giustifica",
"Merge cells": "Unisci celle", "Merge cells": "Unisci celle",
"Split cell": "Dividi cella", "Split cell": "Dividi cella",
@@ -286,6 +290,19 @@
"Add row above": "Aggiungi riga sopra", "Add row above": "Aggiungi riga sopra",
"Add row below": "Aggiungi riga sotto", "Add row below": "Aggiungi riga sotto",
"Delete table": "Elimina tabella", "Delete table": "Elimina tabella",
"Add column left": "Aggiungi colonna a sinistra",
"Add column right": "Aggiungi colonna a destra",
"Clear cell": "Cancella cella",
"Clear cells": "Cancella celle",
"Toggle header cell": "Attiva/disattiva cella di intestazione",
"Toggle header column": "Attiva/disattiva colonna di intestazione",
"Toggle header row": "Attiva/disattiva riga di intestazione",
"Move column left": "Sposta colonna a sinistra",
"Move column right": "Sposta colonna a destra",
"Move row down": "Sposta riga in basso",
"Move row up": "Sposta riga in alto",
"Sort A → Z": "Ordina A → Z",
"Sort Z → A": "Ordina Z → A",
"Info": "Informazioni", "Info": "Informazioni",
"Note": "Nota", "Note": "Nota",
"Success": "Successo", "Success": "Successo",
@@ -348,6 +365,8 @@
"Create block quote.": "Crea blocco citazione.", "Create block quote.": "Crea blocco citazione.",
"Insert code snippet.": "Inserisci frammento di codice.", "Insert code snippet.": "Inserisci frammento di codice.",
"Insert horizontal rule divider": "Inserisci divisore di regola orizzontale", "Insert horizontal rule divider": "Inserisci divisore di regola orizzontale",
"Page break": "Interruzione di pagina",
"Insert a page break for printing.": "Inserisci un'interruzione di pagina per la stampa.",
"Upload any image from your device.": "Carica un'immagine dal tuo dispositivo.", "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 video from your device.": "Carica qualsiasi video dal tuo dispositivo.",
"Upload any audio from your device.": "Carica qualsiasi audio dal tuo dispositivo.", "Upload any audio from your device.": "Carica qualsiasi audio dal tuo dispositivo.",
@@ -392,6 +411,10 @@
"Write...": "Scrivi...", "Write...": "Scrivi...",
"Column count": "Numero di colonne", "Column count": "Numero di colonne",
"{{count}} Columns": "{{count}} colonne", "{{count}} Columns": "{{count}} colonne",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Colonne uguali", "Equal columns": "Colonne uguali",
"Left sidebar": "Barra laterale sinistra", "Left sidebar": "Barra laterale sinistra",
"Right sidebar": "Barra laterale destra", "Right sidebar": "Barra laterale destra",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} è disponibile", "{{latestVersion}} is available": "{{latestVersion}} è disponibile",
"Default page edit mode": "Modalità di modifica predefinita della pagina", "Default page edit mode": "Modalità di modifica predefinita della pagina",
"Choose your preferred page edit mode. Avoid accidental edits.": "Scegli la tua modalità di modifica della pagina preferita. Evita modifiche accidentali.", "Choose your preferred page edit mode. Avoid accidental edits.": "Scegli la tua modalità di modifica della pagina preferita. Evita modifiche accidentali.",
"Choose {{format}} file": "Scegli file {{format}}",
"Reading": "Lettura", "Reading": "Lettura",
"Delete member": "Elimina membro", "Delete member": "Elimina membro",
"Member deleted successfully": "Membro eliminato con successo", "Member deleted successfully": "Membro eliminato con successo",
@@ -565,6 +589,8 @@
"Move to trash": "Sposta nel cestino", "Move to trash": "Sposta nel cestino",
"Move this page to trash?": "Spostare questa pagina nel cestino?", "Move this page to trash?": "Spostare questa pagina nel cestino?",
"Restore page": "Ripristina pagina", "Restore page": "Ripristina pagina",
"Permanently delete": "Elimina definitivamente",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> ha spostato questa pagina nel Cestino {{time}}.",
"Page moved to trash": "Pagina spostata nel cestino", "Page moved to trash": "Pagina spostata nel cestino",
"Page restored successfully": "Pagina ripristinata con successo", "Page restored successfully": "Pagina ripristinata con successo",
"Deleted by": "Eliminato da", "Deleted by": "Eliminato da",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "L'immagine supera il limite di 10MB.", "Image exceeds 10MB limit.": "L'immagine supera il limite di 10MB.",
"Image removed successfully": "Immagine rimossa con successo", "Image removed successfully": "Immagine rimossa con successo",
"API key": "Chiave API", "API key": "Chiave API",
"API key created successfully": "Chiave API creata con successo",
"API keys": "Chiavi API", "API keys": "Chiavi API",
"API management": "Gestione 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", "Custom expiration date": "Data di scadenza personalizzata",
"Enter a descriptive token name": "Inserisci un nome descrittivo del token", "Enter a descriptive token name": "Inserisci un nome descrittivo del token",
"Expiration": "Scadenza", "Expiration": "Scadenza",
"Expired": "Scaduto", "Expired": "Scaduto",
"Expires": "Scade", "Expires": "Scade",
"I've saved my API key": "Ho salvato la mia chiave API",
"Last use": "Ultimo utilizzo", "Last use": "Ultimo utilizzo",
"No API keys found": "Nessuna chiave API trovata", "No API keys found": "Nessuna chiave API trovata",
"No expiration": "Nessuna scadenza", "No expiration": "Nessuna scadenza",
"Revoke API key": "Revoca chiave API",
"Revoked successfully": "Revocata con successo", "Revoked successfully": "Revocata con successo",
"Select expiration date": "Seleziona la data di scadenza", "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.", "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", "Update": "Aggiorna",
"Update {{credential}}": "Aggiorna {{credential}}",
"Manage API keys for all users in the workspace": "Gestisci le chiavi API per tutti gli utenti nell'area di lavoro", "Manage API keys for all users in the workspace": "Gestisci le chiavi API per tutti gli utenti nell'area di lavoro",
"Restrict API key creation to admins": "Limita la creazione delle chiavi API agli amministratori", "Restrict API key creation to admins": "Limita la creazione delle chiavi API agli amministratori",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo gli amministratori e i proprietari possono creare nuove chiavi API. Le chiavi dei membri esistenti continueranno a funzionare.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Solo gli amministratori e i proprietari possono creare nuove chiavi API. Le chiavi dei membri esistenti continueranno a funzionare.",
@@ -858,9 +880,12 @@
"AI Chat": "Chat IA", "AI Chat": "Chat IA",
"Analyze for insights": "Analizza per ottenere approfondimenti", "Analyze for insights": "Analizza per ottenere approfondimenti",
"Ask anything...": "Chiedi qualsiasi cosa...", "Ask anything...": "Chiedi qualsiasi cosa...",
"Assistant said:": "L'assistente ha detto:",
"Chat history": "Cronologia chat", "Chat history": "Cronologia chat",
"Chat name": "Nome chat", "Chat name": "Nome chat",
"Chat transcript": "Trascrizione della chat",
"Close": "Chiudi", "Close": "Chiudi",
"Copy assistant response": "Copia risposta dell'assistente",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "Caricamento della chat non riuscito. Si è verificato un errore.", "Failed to load chat. An error occurred.": "Caricamento della chat non riuscito. Si è verificato un errore.",
"Failed to render this message.": "Impossibile visualizzare questo messaggio.", "Failed to render this message.": "Impossibile visualizzare questo messaggio.",
@@ -870,9 +895,17 @@
"No chats found": "Nessuna chat trovata", "No chats found": "Nessuna chat trovata",
"No conversations yet": "Nessuna conversazione al momento", "No conversations yet": "Nessuna conversazione al momento",
"Open full page": "Apri pagina completa", "Open full page": "Apri pagina completa",
"Scroll to bottom": "Scorri in basso",
"You said:": "Hai detto:",
"Previous 7 days": "Ultimi 7 giorni", "Previous 7 days": "Ultimi 7 giorni",
"Previous 30 days": "Ultimi 30 giorni", "Previous 30 days": "Ultimi 30 giorni",
"Search chats...": "Cerca nelle chat...", "Search chats...": "Cerca nelle chat...",
"Search chats": "Cerca nelle chat",
"Ask anything... Use @ to mention pages": "Chiedi qualsiasi cosa... Usa @ per menzionare le pagine",
"Ask anything or search your workspace": "Chiedi qualsiasi cosa o cerca nel tuo spazio di lavoro",
"Welcome to {{name}}": "Benvenuto in {{name}}",
"Add files": "Aggiungi file",
"Mention a page": "Menziona una pagina",
"Start a new chat to see it here.": "Avvia una nuova chat per vederla qui.", "Start a new chat to see it here.": "Avvia una nuova chat per vederla qui.",
"Summarize this page": "Riassumi questa pagina", "Summarize this page": "Riassumi questa pagina",
"Toggle AI Chat": "Attiva/disattiva Chat IA", "Toggle AI Chat": "Attiva/disattiva Chat IA",
@@ -880,5 +913,176 @@
"Try a different search term.": "Prova un termine di ricerca diverso.", "Try a different search term.": "Prova un termine di ricerca diverso.",
"Try again": "Riprova", "Try again": "Riprova",
"Untitled chat": "Chat senza titolo", "Untitled chat": "Chat senza titolo",
"What can I help you with?": "Con cosa posso aiutarti?" "What can I help you with?": "Con cosa posso aiutarti?",
"Are you sure you want to revoke this {{credential}}": "Sei sicuro di voler revocare questa {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Esegui automaticamente il provisioning di utenti e gruppi dal tuo provider di identità tramite SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configura il tuo provider di identità con questo URL per eseguire il provisioning di utenti e gruppi.",
"Create {{credential}}": "Crea {{credential}}",
"{{credential}} created": "{{credential}} creata",
"{{credential}} created successfully": "{{credential}} creata con successo",
"Created by": "Creata da",
"Custom": "Personalizzato",
"Enable SCIM": "Abilita SCIM",
"Enter a descriptive name": "Inserisci un nome descrittivo",
"I've saved my {{credential}}": "Ho salvato la mia {{credential}}",
"Important": "Importante",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Assicurati di copiare subito la tua {{credential}}. Non potrai più visualizzarla!",
"Never": "Mai",
"Revoke {{credential}}": "Revoca {{credential}}",
"SCIM endpoint URL": "URL dell'endpoint SCIM",
"SCIM provisioning": "Provisioning SCIM",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM ha la precedenza sulla sincronizzazione dei gruppi SSO quando è abilitato.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Hai raggiunto il numero massimo di {{max}} token SCIM. Elimina un token esistente per crearne uno nuovo.",
"SCIM token": "Token SCIM",
"SCIM tokens": "Token SCIM",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Questa azione non può essere annullata. Il tuo provider di identità smetterà di sincronizzarsi immediatamente.",
"Toggle SCIM provisioning": "Attiva/disattiva il provisioning SCIM",
"Token": "Token",
"Page menu": "Menu della pagina",
"Expand": "Espandi",
"Collapse": "Comprimi",
"Comment menu": "Menu dei commenti",
"Group menu": "Menu del gruppo",
"Show hidden breadcrumbs": "Mostra breadcrumb nascosti",
"Breadcrumbs": "Breadcrumb",
"Page actions": "Azioni della pagina",
"Pick emoji": "Scegli emoji",
"Template menu": "Menu del modello",
"Use": "Usa",
"Use template": "Usa modello",
"Preview template: {{title}}": "Anteprima modello: {{title}}",
"Use a template": "Usa un modello",
"Search templates...": "Cerca modelli...",
"Search spaces...": "Cerca spazi...",
"No templates found": "Nessun modello trovato",
"No spaces found": "Nessuno spazio trovato",
"Browse all templates": "Sfoglia tutti i modelli",
"This space": "Questo spazio",
"All templates": "Tutti i modelli",
"Global": "Globale",
"New template": "Nuovo modello",
"Edit template": "Modifica modello",
"Are you sure you want to delete this template?": "Sei sicuro di voler eliminare questo modello?",
"Template scope updated": "Ambito del modello aggiornato",
"Choose which space this template belongs to": "Scegli a quale spazio appartiene questo modello",
"Scope": "Ambito",
"Select scope": "Seleziona ambito",
"Title": "Titolo",
"Saving...": "Salvataggio...",
"Saved": "Salvato",
"Save failed. Retry": "Salvataggio non riuscito. Riprova",
"By {{name}}": "Di {{name}}",
"Updated {{time}}": "Aggiornato {{time}}",
"Choose destination": "Scegli destinazione",
"Search pages and spaces...": "Cerca pagine e spazi...",
"No results found": "Nessun risultato trovato",
"You don't have permission to create pages here": "Non hai l'autorizzazione per creare pagine qui",
"Chat menu": "Menu della chat",
"API key menu": "Menu della chiave API",
"Jump to comment selection": "Vai alla selezione dei commenti",
"Slash commands": "Comandi slash",
"Mention suggestions": "Suggerimenti di menzione",
"Link suggestions": "Suggerimenti di link",
"Diagram editor": "Editor di diagrammi",
"Add comment": "Aggiungi commento",
"Find and replace": "Trova e sostituisci",
"Main navigation": "Navigazione principale",
"Space navigation": "Navigazione dello spazio",
"Settings navigation": "Navigazione delle impostazioni",
"AI navigation": "Navigazione AI",
"Breadcrumb": "Percorso di navigazione",
"Synced block": "Blocco sincronizzato",
"Create a block that stays in sync across pages.": "Crea un blocco che rimanga sincronizzato tra le pagine.",
"Editing original": "Modifica originale",
"Copy synced block": "Copia blocco sincronizzato",
"Unsync": "Annulla sincronizzazione",
"Delete synced block": "Elimina blocco sincronizzato",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "ORIGINALE",
"THIS PAGE": "QUESTA PAGINA",
"No pages": "Nessuna pagina",
"The original synced block no longer exists": "Il blocco sincronizzato originale non esiste più",
"You don't have access to this synced block": "Non hai accesso a questo blocco sincronizzato",
"Failed to load this synced block": "Impossibile caricare questo blocco sincronizzato",
"Fixed editor toolbar": "Barra degli strumenti dell'editor fissa",
"Show a formatting toolbar above the editor with quick access to common actions.": "Mostra una barra degli strumenti di formattazione sopra l'editor con accesso rapido alle azioni comuni.",
"Toggle fixed editor toolbar": "Attiva/disattiva barra degli strumenti dell'editor fissa",
"Normal text": "Testo normale",
"More inline formatting": "Altra formattazione in linea",
"Subscript": "Pedice",
"Superscript": "Apice",
"Inline code": "Codice in linea",
"Insert media": "Inserisci contenuti multimediali",
"Mention": "Menzione",
"Emoji": "Emoji",
"Columns": "Colonne",
"More inserts": "Altri inserimenti",
"Embeds": "Incorporamenti",
"Diagrams": "Diagrammi",
"Advanced": "Avanzate",
"Utility": "Utilità",
"Decrease indent": "Riduci rientro",
"Increase indent": "Aumenta rientro",
"Clear formatting": "Cancella formattazione",
"Code block": "Blocco di codice",
"Experimental": "Sperimentale",
"Strikethrough": "Barrato",
"Undo": "Annulla",
"Redo": "Ripeti",
"Backlinks": "Backlink",
"Last updated by": "Ultimo aggiornamento di",
"Last updated": "Ultimo aggiornamento",
"Stats": "Statistiche",
"Word count": "Conteggio parole",
"Characters": "Caratteri",
"Incoming links": "Link in entrata",
"Outgoing links": "Link in uscita",
"Incoming links ({{count}})": "Link in entrata ({{count}})",
"Outgoing links ({{count}})": "Link in uscita ({{count}})",
"No pages link here yet.": "Nessuna pagina rimanda ancora qui.",
"This page doesn't link to other pages yet.": "Questa pagina non rimanda ancora ad altre pagine.",
"Verified until {{date}}": "Verificato fino al {{date}}",
"Labels": "Etichette",
"Add label": "Aggiungi etichetta",
"No labels yet": "Nessuna etichetta per ora",
"Already added": "Già aggiunto",
"Invalid label name": "Nome etichetta non valido",
"No matches": "Nessuna corrispondenza",
"Search or create…": "Cerca o crea…",
"Remove label {{name}}": "Rimuovi etichetta {{name}}",
"Failed to add label": "Impossibile aggiungere l'etichetta",
"Failed to remove label": "Impossibile rimuovere l'etichetta",
"No pages with this label": "Nessuna pagina con questa etichetta",
"Pages tagged with this label will appear here.": "Le pagine contrassegnate con questa etichetta appariranno qui.",
"No pages match your search.": "Nessuna pagina corrisponde alla tua ricerca.",
"Updated {{date}}": "Aggiornato il {{date}}",
"Cell actions": "Azioni cella",
"Column actions": "Azioni colonna",
"Row actions": "Azioni riga",
"Filter": "Filtro",
"Page title": "Titolo pagina",
"Page content": "Contenuto della pagina",
"Member actions": "Azioni membro",
"Toggle password visibility": "Attiva/disattiva visibilità password",
"Send comment": "Invia commento",
"Token actions": "Azioni token",
"Template settings": "Impostazioni modello",
"Edit diagram": "Modifica diagramma",
"Edit embed": "Modifica incorporamento",
"Edit drawing": "Modifica disegno",
"Delete equation": "Elimina equazione",
"Invite actions": "Azioni invito",
"Get started": "Inizia",
"* indicates required fields": "* indica i campi obbligatori",
"List of spaces in this workspace": "Elenco degli spazi in questo spazio di lavoro",
"Active sessions": "Sessioni attive",
"Add {{name}} to favorites": "Aggiungi {{name}} ai preferiti",
"Remove {{name}} from favorites": "Rimuovi {{name}} dai preferiti",
"Added to favorites": "Aggiunto ai preferiti",
"Removed from favorites": "Rimosso dai preferiti",
"Added {{name}} to favorites": "{{name}} aggiunto ai preferiti",
"Removed {{name}} from favorites": "{{name}} rimosso dai preferiti",
"Page menu for {{name}}": "Menu della pagina per {{name}}",
"Create subpage of {{name}}": "Crea sottopagina di {{name}}"
} }
@@ -71,6 +71,7 @@
"Export": "エクスポート", "Export": "エクスポート",
"Failed to create page": "ページの作成に失敗しました", "Failed to create page": "ページの作成に失敗しました",
"Failed to delete page": "ページの削除に失敗しました", "Failed to delete page": "ページの削除に失敗しました",
"Failed to restore page": "ページの復元に失敗しました",
"Failed to fetch recent pages": "最近のページを取得できませんでした", "Failed to fetch recent pages": "最近のページを取得できませんでした",
"Failed to import pages": "ページのインポートに失敗しました", "Failed to import pages": "ページのインポートに失敗しました",
"Failed to load page. An error occurred.": "ページの読み込みに失敗しました。エラーが発生しました。", "Failed to load page. An error occurred.": "ページの読み込みに失敗しました。エラーが発生しました。",
@@ -276,6 +277,9 @@
"Align left": "左揃え", "Align left": "左揃え",
"Align right": "右揃え", "Align right": "右揃え",
"Align center": "中央揃え", "Align center": "中央揃え",
"Alt text": "代替テキスト",
"Describe this for accessibility.": "アクセシビリティのために説明を追加してください。",
"Add a description": "説明を追加",
"Justify": "両端揃え", "Justify": "両端揃え",
"Merge cells": "セルを結合", "Merge cells": "セルを結合",
"Split cell": "セルを分割", "Split cell": "セルを分割",
@@ -286,6 +290,19 @@
"Add row above": "上に行を追加", "Add row above": "上に行を追加",
"Add row below": "下に行を追加", "Add row below": "下に行を追加",
"Delete table": "テーブルを削除", "Delete table": "テーブルを削除",
"Add column left": "左に列を追加",
"Add column right": "右に列を追加",
"Clear cell": "セルをクリア",
"Clear cells": "セルをクリア",
"Toggle header cell": "ヘッダーセルを切り替え",
"Toggle header column": "ヘッダー列を切り替え",
"Toggle header row": "ヘッダー行を切り替え",
"Move column left": "列を左に移動",
"Move column right": "列を右に移動",
"Move row down": "行を下に移動",
"Move row up": "行を上に移動",
"Sort A → Z": "A → Z で並べ替え",
"Sort Z → A": "Z → A で並べ替え",
"Info": "情報", "Info": "情報",
"Note": "ノート", "Note": "ノート",
"Success": "成功", "Success": "成功",
@@ -348,6 +365,8 @@
"Create block quote.": "引用ブロックを作成します", "Create block quote.": "引用ブロックを作成します",
"Insert code snippet.": "コードスニペットを挿入します", "Insert code snippet.": "コードスニペットを挿入します",
"Insert horizontal rule divider": "区切り線を挿入します", "Insert horizontal rule divider": "区切り線を挿入します",
"Page break": "改ページ",
"Insert a page break for printing.": "印刷用に改ページを挿入します。",
"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 audio from your device.": "デバイスから音声ファイルをアップロードします。", "Upload any audio from your device.": "デバイスから音声ファイルをアップロードします。",
@@ -392,6 +411,10 @@
"Write...": "ここに入力...", "Write...": "ここに入力...",
"Column count": "列数", "Column count": "列数",
"{{count}} Columns": "{{count}}列", "{{count}} Columns": "{{count}}列",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "均等な列", "Equal columns": "均等な列",
"Left sidebar": "左サイドバー", "Left sidebar": "左サイドバー",
"Right sidebar": "右サイドバー", "Right sidebar": "右サイドバー",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} が利用可能です", "{{latestVersion}} is available": "{{latestVersion}} が利用可能です",
"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.": "お好みのページ編集モードを選択してください(誤編集を防止します)",
"Choose {{format}} file": "{{format}} ファイルを選択",
"Reading": "閲覧", "Reading": "閲覧",
"Delete member": "メンバーを削除", "Delete member": "メンバーを削除",
"Member deleted successfully": "メンバーが正常に削除されました", "Member deleted successfully": "メンバーが正常に削除されました",
@@ -565,6 +589,8 @@
"Move to trash": "ゴミ箱に移動", "Move to trash": "ゴミ箱に移動",
"Move this page to trash?": "このページをごみ箱に移動しますか?", "Move this page to trash?": "このページをごみ箱に移動しますか?",
"Restore page": "ページを復元", "Restore page": "ページを復元",
"Permanently delete": "完全に削除",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> が {{time}} にこのページをゴミ箱に移動しました。",
"Page moved to trash": "ページをゴミ箱に移動しました", "Page moved to trash": "ページをゴミ箱に移動しました",
"Page restored successfully": "ページが正常に復元されました", "Page restored successfully": "ページが正常に復元されました",
"Deleted by": "削除者", "Deleted by": "削除者",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "画像が10MBの制限を超えています", "Image exceeds 10MB limit.": "画像が10MBの制限を超えています",
"Image removed successfully": "画像を削除しました", "Image removed successfully": "画像を削除しました",
"API key": "APIキー", "API key": "APIキー",
"API key created successfully": "APIキーを作成しました",
"API keys": "APIキー", "API keys": "APIキー",
"API management": "API管理", "API management": "API管理",
"Are you sure you want to revoke this API key": "このAPIキーを無効にしてもよろしいですか",
"Create API Key": "APIキーを作成",
"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": "APIキーを保存しました",
"Last use": "最終使用", "Last use": "最終使用",
"No API keys found": "APIキーが見つかりません", "No API keys found": "APIキーが見つかりません",
"No expiration": "期限なし", "No expiration": "期限なし",
"Revoke API key": "APIキーを無効にする",
"Revoked successfully": "無効にしました", "Revoked successfully": "無効にしました",
"Select expiration date": "有効期限を選択してください", "Select expiration date": "有効期限を選択してください",
"This action cannot be undone. Any applications using this API key will stop working.": "この操作は取り消せません。このAPIキーを使用しているアプリケーションは動作しなくなります", "This action cannot be undone. Any applications using this API key will stop working.": "この操作は取り消せません。このAPIキーを使用しているアプリケーションは動作しなくなります",
"Update API key": "APIキーを更新", "Update": "更新",
"Update {{credential}}": "{{credential}}を更新",
"Manage API keys for all users in the workspace": "ワークスペース内のすべてのユーザーのAPIキーを管理", "Manage API keys for all users in the workspace": "ワークスペース内のすべてのユーザーのAPIキーを管理",
"Restrict API key creation to admins": "APIキーの作成を管理者のみに制限する", "Restrict API key creation to admins": "APIキーの作成を管理者のみに制限する",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "新しいAPIキーを作成できるのは管理者とオーナーのみです。既存のメンバーキーは引き続き有効です。", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "新しいAPIキーを作成できるのは管理者とオーナーのみです。既存のメンバーキーは引き続き有効です。",
@@ -858,9 +880,12 @@
"AI Chat": "AI チャット", "AI Chat": "AI チャット",
"Analyze for insights": "分析してインサイトを得る", "Analyze for insights": "分析してインサイトを得る",
"Ask anything...": "何でも聞いてください...", "Ask anything...": "何でも聞いてください...",
"Assistant said:": "アシスタントの回答:",
"Chat history": "チャット履歴", "Chat history": "チャット履歴",
"Chat name": "チャット名", "Chat name": "チャット名",
"Chat transcript": "チャットの記録",
"Close": "閉じる", "Close": "閉じる",
"Copy assistant response": "アシスタントの回答をコピー",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "チャットの読み込みに失敗しました。エラーが発生しました。", "Failed to load chat. An error occurred.": "チャットの読み込みに失敗しました。エラーが発生しました。",
"Failed to render this message.": "このメッセージの表示に失敗しました。", "Failed to render this message.": "このメッセージの表示に失敗しました。",
@@ -870,9 +895,17 @@
"No chats found": "チャットが見つかりません", "No chats found": "チャットが見つかりません",
"No conversations yet": "会話はまだありません", "No conversations yet": "会話はまだありません",
"Open full page": "全ページで開く", "Open full page": "全ページで開く",
"Scroll to bottom": "一番下までスクロール",
"You said:": "あなたの発言:",
"Previous 7 days": "過去 7 日間", "Previous 7 days": "過去 7 日間",
"Previous 30 days": "過去 30 日間", "Previous 30 days": "過去 30 日間",
"Search chats...": "チャットを検索...", "Search chats...": "チャットを検索...",
"Search chats": "チャットを検索",
"Ask anything... Use @ to mention pages": "何でも質問してください… @ を使ってページにメンションできます",
"Ask anything or search your workspace": "何でも質問するか、ワークスペースを検索",
"Welcome to {{name}}": "{{name}} へようこそ",
"Add files": "ファイルを追加",
"Mention a page": "ページにメンション",
"Start a new chat to see it here.": "ここに表示するには新しいチャットを開始してください。", "Start a new chat to see it here.": "ここに表示するには新しいチャットを開始してください。",
"Summarize this page": "このページを要約", "Summarize this page": "このページを要約",
"Toggle AI Chat": "AI チャットを切り替え", "Toggle AI Chat": "AI チャットを切り替え",
@@ -880,5 +913,176 @@
"Try a different search term.": "別の検索語を試してください。", "Try a different search term.": "別の検索語を試してください。",
"Try again": "再試行", "Try again": "再試行",
"Untitled chat": "無題のチャット", "Untitled chat": "無題のチャット",
"What can I help you with?": "何をお手伝いしましょうか?" "What can I help you with?": "何をお手伝いしましょうか?",
"Are you sure you want to revoke this {{credential}}": "この{{credential}}を無効にしてもよろしいですか",
"Automatically provision users and groups from your identity provider via SCIM.": "SCIM を介して、ID プロバイダーからユーザーとグループを自動的にプロビジョニングします。",
"Configure your identity provider with this URL to provision users and groups.": "この URL を使用して ID プロバイダーを設定し、ユーザーとグループをプロビジョニングします。",
"Create {{credential}}": "{{credential}}を作成",
"{{credential}} created": "{{credential}}を作成しました",
"{{credential}} created successfully": "{{credential}}を正常に作成しました",
"Created by": "作成者",
"Custom": "カスタム",
"Enable SCIM": "SCIM を有効にする",
"Enter a descriptive name": "説明的な名前を入力してください",
"I've saved my {{credential}}": "{{credential}}を保存しました",
"Important": "重要",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "今すぐ {{credential}} をコピーしてください。後でもう一度表示することはできません!",
"Never": "なし",
"Revoke {{credential}}": "{{credential}}を無効にする",
"SCIM endpoint URL": "SCIM エンドポイント URL",
"SCIM provisioning": "SCIM プロビジョニング",
"SCIM takes precedence over SSO group sync while enabled.": "有効になっている間は、SCIM が SSO グループ同期より優先されます。",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "SCIM トークンの上限 {{max}} に達しました。新しいトークンを作成するには、既存のトークンを削除してください。",
"SCIM token": "SCIM トークン",
"SCIM tokens": "SCIM トークン",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "この操作は元に戻せません。ID プロバイダーは直ちに同期を停止します。",
"Toggle SCIM provisioning": "SCIM プロビジョニングを切り替える",
"Token": "トークン",
"Page menu": "ページメニュー",
"Expand": "展開",
"Collapse": "折りたたむ",
"Comment menu": "コメントメニュー",
"Group menu": "グループメニュー",
"Show hidden breadcrumbs": "非表示のパンくずリストを表示",
"Breadcrumbs": "パンくずリスト",
"Page actions": "ページアクション",
"Pick emoji": "絵文字を選択",
"Template menu": "テンプレートメニュー",
"Use": "使用",
"Use template": "テンプレートを使用",
"Preview template: {{title}}": "テンプレートをプレビュー: {{title}}",
"Use a template": "テンプレートを使用",
"Search templates...": "テンプレートを検索…",
"Search spaces...": "スペースを検索…",
"No templates found": "テンプレートが見つかりません",
"No spaces found": "スペースが見つかりません",
"Browse all templates": "すべてのテンプレートを表示",
"This space": "このスペース",
"All templates": "すべてのテンプレート",
"Global": "グローバル",
"New template": "新しいテンプレート",
"Edit template": "テンプレートを編集",
"Are you sure you want to delete this template?": "このテンプレートを削除してもよろしいですか?",
"Template scope updated": "テンプレートのスコープを更新しました",
"Choose which space this template belongs to": "このテンプレートを所属させるスペースを選択",
"Scope": "スコープ",
"Select scope": "スコープを選択",
"Title": "タイトル",
"Saving...": "保存中…",
"Saved": "保存しました",
"Save failed. Retry": "保存に失敗しました。再試行",
"By {{name}}": "{{name}} 作成",
"Updated {{time}}": "{{time}} に更新",
"Choose destination": "保存先を選択",
"Search pages and spaces...": "ページとスペースを検索…",
"No results found": "結果が見つかりません",
"You don't have permission to create pages here": "ここにページを作成する権限がありません",
"Chat menu": "チャットメニュー",
"API key menu": "API キーメニュー",
"Jump to comment selection": "コメント選択に移動",
"Slash commands": "スラッシュコマンド",
"Mention suggestions": "メンション候補",
"Link suggestions": "リンク候補",
"Diagram editor": "ダイアグラムエディター",
"Add comment": "コメントを追加",
"Find and replace": "検索と置換",
"Main navigation": "メインナビゲーション",
"Space navigation": "スペースナビゲーション",
"Settings navigation": "設定ナビゲーション",
"AI navigation": "AI ナビゲーション",
"Breadcrumb": "パンくずリスト",
"Synced block": "同期ブロック",
"Create a block that stays in sync across pages.": "ページ間で同期されたままになるブロックを作成します。",
"Editing original": "オリジナルを編集中",
"Copy synced block": "同期ブロックをコピー",
"Unsync": "同期解除",
"Delete synced block": "同期ブロックを削除",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "オリジナル",
"THIS PAGE": "このページ",
"No pages": "ページがありません",
"The original synced block no longer exists": "元の同期ブロックは存在しなくなりました",
"You don't have access to this synced block": "この同期ブロックにアクセスできません",
"Failed to load this synced block": "この同期ブロックの読み込みに失敗しました",
"Fixed editor toolbar": "固定エディターツールバー",
"Show a formatting toolbar above the editor with quick access to common actions.": "一般的な操作にすばやくアクセスできる書式設定ツールバーをエディターの上に表示します。",
"Toggle fixed editor toolbar": "固定エディターツールバーを切り替え",
"Normal text": "通常のテキスト",
"More inline formatting": "その他のインライン書式",
"Subscript": "下付き",
"Superscript": "上付き",
"Inline code": "インラインコード",
"Insert media": "メディアを挿入",
"Mention": "メンション",
"Emoji": "絵文字",
"Columns": "列",
"More inserts": "その他の挿入",
"Embeds": "埋め込み",
"Diagrams": "ダイアグラム",
"Advanced": "詳細",
"Utility": "ユーティリティ",
"Decrease indent": "インデントを減らす",
"Increase indent": "インデントを増やす",
"Clear formatting": "書式をクリア",
"Code block": "コードブロック",
"Experimental": "実験的",
"Strikethrough": "取り消し線",
"Undo": "元に戻す",
"Redo": "やり直す",
"Backlinks": "バックリンク",
"Last updated by": "最終更新者",
"Last updated": "最終更新",
"Stats": "統計",
"Word count": "単語数",
"Characters": "文字数",
"Incoming links": "被リンク",
"Outgoing links": "発リンク",
"Incoming links ({{count}})": "被リンク ({{count}})",
"Outgoing links ({{count}})": "発リンク ({{count}})",
"No pages link here yet.": "まだこのページにリンクしているページはありません。",
"This page doesn't link to other pages yet.": "このページはまだ他のページにリンクしていません。",
"Verified until {{date}}": "{{date}} まで検証済み",
"Labels": "ラベル",
"Add label": "ラベルを追加",
"No labels yet": "まだラベルはありません",
"Already added": "追加済み",
"Invalid label name": "無効なラベル名",
"No matches": "一致するものがありません",
"Search or create…": "検索または作成…",
"Remove label {{name}}": "ラベル {{name}} を削除",
"Failed to add label": "ラベルの追加に失敗しました",
"Failed to remove label": "ラベルの削除に失敗しました",
"No pages with this label": "このラベルが付いたページはありません",
"Pages tagged with this label will appear here.": "このラベルが付いたページがここに表示されます。",
"No pages match your search.": "検索に一致するページはありません。",
"Updated {{date}}": "{{date}} に更新",
"Cell actions": "セルの操作",
"Column actions": "列の操作",
"Row actions": "行の操作",
"Filter": "フィルター",
"Page title": "ページタイトル",
"Page content": "ページ内容",
"Member actions": "メンバーの操作",
"Toggle password visibility": "パスワードの表示を切り替え",
"Send comment": "コメントを送信",
"Token actions": "トークンの操作",
"Template settings": "テンプレート設定",
"Edit diagram": "ダイアグラムを編集",
"Edit embed": "埋め込みを編集",
"Edit drawing": "図を編集",
"Delete equation": "数式を削除",
"Invite actions": "招待の操作",
"Get started": "始める",
"* indicates required fields": "* は必須項目を示します",
"List of spaces in this workspace": "このワークスペース内のスペース一覧",
"Active sessions": "アクティブなセッション",
"Add {{name}} to favorites": "{{name}} をお気に入りに追加",
"Remove {{name}} from favorites": "{{name}} をお気に入りから削除",
"Added to favorites": "お気に入りに追加しました",
"Removed from favorites": "お気に入りから削除しました",
"Added {{name}} to favorites": "{{name}} をお気に入りに追加しました",
"Removed {{name}} from favorites": "{{name}} をお気に入りから削除しました",
"Page menu for {{name}}": "{{name}} のページメニュー",
"Create subpage of {{name}}": "{{name}} のサブページを作成"
} }
@@ -71,6 +71,7 @@
"Export": "내보내기", "Export": "내보내기",
"Failed to create page": "페이지 생성 실패", "Failed to create page": "페이지 생성 실패",
"Failed to delete page": "페이지 삭제 실패", "Failed to delete page": "페이지 삭제 실패",
"Failed to restore page": "페이지를 복원하지 못했습니다",
"Failed to fetch recent pages": "최근 페이지 불러오기 실패", "Failed to fetch recent pages": "최근 페이지 불러오기 실패",
"Failed to import pages": "페이지 가져오기 실패", "Failed to import pages": "페이지 가져오기 실패",
"Failed to load page. An error occurred.": "페이지 불러오기 실패. 오류가 발생했습니다.", "Failed to load page. An error occurred.": "페이지 불러오기 실패. 오류가 발생했습니다.",
@@ -276,6 +277,9 @@
"Align left": "왼쪽 정렬", "Align left": "왼쪽 정렬",
"Align right": "오른쪽 정렬", "Align right": "오른쪽 정렬",
"Align center": "가운데 정렬", "Align center": "가운데 정렬",
"Alt text": "대체 텍스트",
"Describe this for accessibility.": "접근성을 위해 이를 설명하세요.",
"Add a description": "설명 추가",
"Justify": "양쪽 정렬", "Justify": "양쪽 정렬",
"Merge cells": "셀 병합", "Merge cells": "셀 병합",
"Split cell": "셀 분할", "Split cell": "셀 분할",
@@ -286,6 +290,19 @@
"Add row above": "위에 행 추가", "Add row above": "위에 행 추가",
"Add row below": "아래에 행 추가", "Add row below": "아래에 행 추가",
"Delete table": "테이블 삭제", "Delete table": "테이블 삭제",
"Add column left": "왼쪽에 열 추가",
"Add column right": "오른쪽에 열 추가",
"Clear cell": "셀 지우기",
"Clear cells": "셀 지우기",
"Toggle header cell": "헤더 셀 전환",
"Toggle header column": "헤더 열 전환",
"Toggle header row": "헤더 행 전환",
"Move column left": "열 왼쪽으로 이동",
"Move column right": "열 오른쪽으로 이동",
"Move row down": "행 아래로 이동",
"Move row up": "행 위로 이동",
"Sort A → Z": "A → Z 정렬",
"Sort Z → A": "Z → A 정렬",
"Info": "정보", "Info": "정보",
"Note": "참고", "Note": "참고",
"Success": "완료", "Success": "완료",
@@ -348,6 +365,8 @@
"Create block quote.": "인용구 만들기.", "Create block quote.": "인용구 만들기.",
"Insert code snippet.": "코드 블록 삽입.", "Insert code snippet.": "코드 블록 삽입.",
"Insert horizontal rule divider": "가로 구분선 삽입", "Insert horizontal rule divider": "가로 구분선 삽입",
"Page break": "페이지 나누기",
"Insert a page break for printing.": "인쇄용 페이지 나누기를 삽입합니다.",
"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 audio from your device.": "기기에서 오디오를 업로드하세요.", "Upload any audio from your device.": "기기에서 오디오를 업로드하세요.",
@@ -392,6 +411,10 @@
"Write...": "작성...", "Write...": "작성...",
"Column count": "열 개수", "Column count": "열 개수",
"{{count}} Columns": "{{count}}열", "{{count}} Columns": "{{count}}열",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "열 너비 균등", "Equal columns": "열 너비 균등",
"Left sidebar": "왼쪽 사이드바", "Left sidebar": "왼쪽 사이드바",
"Right sidebar": "오른쪽 사이드바", "Right sidebar": "오른쪽 사이드바",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} 버전을 사용할 수 있습니다", "{{latestVersion}} is available": "{{latestVersion}} 버전을 사용할 수 있습니다",
"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.": "선호하는 페이지 편집 모드를 선택하세요. 실수로 인한 편집을 방지하세요.",
"Choose {{format}} file": "{{format}} 파일 선택",
"Reading": "읽기", "Reading": "읽기",
"Delete member": "멤버 삭제", "Delete member": "멤버 삭제",
"Member deleted successfully": "멤버가 성공적으로 삭제되었습니다", "Member deleted successfully": "멤버가 성공적으로 삭제되었습니다",
@@ -565,6 +589,8 @@
"Move to trash": "휴지통으로 이동", "Move to trash": "휴지통으로 이동",
"Move this page to trash?": "이 페이지를 휴지통으로 이동하시겠습니까?", "Move this page to trash?": "이 페이지를 휴지통으로 이동하시겠습니까?",
"Restore page": "페이지 복원", "Restore page": "페이지 복원",
"Permanently delete": "영구 삭제",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b>님이 {{time}}에 이 페이지를 휴지통으로 이동했습니다.",
"Page moved to trash": "페이지가 휴지통으로 이동되었습니다", "Page moved to trash": "페이지가 휴지통으로 이동되었습니다",
"Page restored successfully": "페이지가 성공적으로 복원되었습니다", "Page restored successfully": "페이지가 성공적으로 복원되었습니다",
"Deleted by": "삭제한 사람", "Deleted by": "삭제한 사람",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "이미지가 10MB 용량 제한을 초과합니다.", "Image exceeds 10MB limit.": "이미지가 10MB 용량 제한을 초과합니다.",
"Image removed successfully": "이미지가 성공적으로 제거되었습니다", "Image removed successfully": "이미지가 성공적으로 제거되었습니다",
"API key": "API 키", "API key": "API 키",
"API key created successfully": "API 키 생성 완료",
"API keys": "API 키", "API keys": "API 키",
"API management": "API 관리", "API management": "API 관리",
"Are you sure you want to revoke this API key": "이 API 키를 취소하시겠습니까?",
"Create API Key": "API 키 생성",
"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": "API 키를 저장했습니다",
"Last use": "최근 사용", "Last use": "최근 사용",
"No API keys found": "API 키를 찾을 수 없습니다", "No API keys found": "API 키를 찾을 수 없습니다",
"No expiration": "유효기간 없음", "No expiration": "유효기간 없음",
"Revoke API key": "API 키 취소",
"Revoked successfully": "성공적으로 취소되었습니다", "Revoked successfully": "성공적으로 취소되었습니다",
"Select expiration date": "만료일 선택", "Select expiration date": "만료일 선택",
"This action cannot be undone. Any applications using this API key will stop working.": "이 작업은 되돌릴 수 없습니다. 이 API 키를 사용하는 모든 응용 프로그램이 작동을 멈출 것입니다.", "This action cannot be undone. Any applications using this API key will stop working.": "이 작업은 되돌릴 수 없습니다. 이 API 키를 사용하는 모든 응용 프로그램이 작동을 멈출 것입니다.",
"Update API key": "API 키 갱신", "Update": "업데이트",
"Update {{credential}}": "{{credential}} 업데이트",
"Manage API keys for all users in the workspace": "워크스페이스 내 모든 사용자의 API 키 관리", "Manage API keys for all users in the workspace": "워크스페이스 내 모든 사용자의 API 키 관리",
"Restrict API key creation to admins": "API 키 생성 권한을 관리자에게만 제한합니다", "Restrict API key creation to admins": "API 키 생성 권한을 관리자에게만 제한합니다",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "새로운 API 키는 관리자와 소유자만 생성할 수 있습니다. 기존 멤버 키는 계속 사용할 수 있습니다.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "새로운 API 키는 관리자와 소유자만 생성할 수 있습니다. 기존 멤버 키는 계속 사용할 수 있습니다.",
@@ -858,9 +880,12 @@
"AI Chat": "AI 채팅", "AI Chat": "AI 채팅",
"Analyze for insights": "인사이트 분석", "Analyze for insights": "인사이트 분석",
"Ask anything...": "무엇이든 물어보세요...", "Ask anything...": "무엇이든 물어보세요...",
"Assistant said:": "어시스턴트의 답변:",
"Chat history": "채팅 기록", "Chat history": "채팅 기록",
"Chat name": "채팅 이름", "Chat name": "채팅 이름",
"Chat transcript": "채팅 기록",
"Close": "닫기", "Close": "닫기",
"Copy assistant response": "어시스턴트 응답 복사",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "채팅을 불러오지 못했습니다. 오류가 발생했습니다.", "Failed to load chat. An error occurred.": "채팅을 불러오지 못했습니다. 오류가 발생했습니다.",
"Failed to render this message.": "이 메시지를 렌더링하지 못했습니다.", "Failed to render this message.": "이 메시지를 렌더링하지 못했습니다.",
@@ -870,9 +895,17 @@
"No chats found": "채팅을 찾을 수 없습니다", "No chats found": "채팅을 찾을 수 없습니다",
"No conversations yet": "아직 대화가 없습니다", "No conversations yet": "아직 대화가 없습니다",
"Open full page": "전체 페이지 열기", "Open full page": "전체 페이지 열기",
"Scroll to bottom": "맨 아래로 스크롤",
"You said:": "내가 한 말:",
"Previous 7 days": "지난 7일", "Previous 7 days": "지난 7일",
"Previous 30 days": "지난 30일", "Previous 30 days": "지난 30일",
"Search chats...": "채팅 검색...", "Search chats...": "채팅 검색...",
"Search chats": "채팅 검색",
"Ask anything... Use @ to mention pages": "무엇이든 물어보세요... 페이지를 언급하려면 @를 사용하세요",
"Ask anything or search your workspace": "무엇이든 물어보거나 워크스페이스를 검색하세요",
"Welcome to {{name}}": "{{name}}에 오신 것을 환영합니다",
"Add files": "파일 추가",
"Mention a page": "페이지 멘션",
"Start a new chat to see it here.": "여기에 표시하려면 새 채팅을 시작하세요.", "Start a new chat to see it here.": "여기에 표시하려면 새 채팅을 시작하세요.",
"Summarize this page": "이 페이지 요약", "Summarize this page": "이 페이지 요약",
"Toggle AI Chat": "AI 채팅 전환", "Toggle AI Chat": "AI 채팅 전환",
@@ -880,5 +913,176 @@
"Try a different search term.": "다른 검색어를 사용해 보세요.", "Try a different search term.": "다른 검색어를 사용해 보세요.",
"Try again": "다시 시도", "Try again": "다시 시도",
"Untitled chat": "제목 없는 채팅", "Untitled chat": "제목 없는 채팅",
"What can I help you with?": "무엇을 도와드릴까요?" "What can I help you with?": "무엇을 도와드릴까요?",
"Are you sure you want to revoke this {{credential}}": "이 {{credential}}을 취소하시겠습니까?",
"Automatically provision users and groups from your identity provider via SCIM.": "SCIM을 통해 ID 공급자에서 사용자와 그룹을 자동으로 프로비저닝합니다.",
"Configure your identity provider with this URL to provision users and groups.": "사용자와 그룹을 프로비저닝할 수 있도록 이 URL로 ID 공급자를 구성하세요.",
"Create {{credential}}": "{{credential}} 만들기",
"{{credential}} created": "{{credential}} 생성됨",
"{{credential}} created successfully": "{{credential}} 생성 완료",
"Created by": "생성한 사람",
"Custom": "사용자 지정",
"Enable SCIM": "SCIM 활성화",
"Enter a descriptive name": "설명적인 이름을 입력하세요",
"I've saved my {{credential}}": "내 {{credential}}를 저장했습니다",
"Important": "중요",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "지금 {{credential}}를 복사해 두세요. 다시는 볼 수 없습니다!",
"Never": "안 함",
"Revoke {{credential}}": "{{credential}} 취소",
"SCIM endpoint URL": "SCIM 엔드포인트 URL",
"SCIM provisioning": "SCIM 프로비저닝",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM이 활성화되어 있는 동안에는 SSO 그룹 동기화보다 SCIM이 우선 적용됩니다.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "SCIM 토큰은 최대 {{max}}개까지 만들 수 있습니다. 새 토큰을 만들려면 기존 토큰을 삭제하세요.",
"SCIM token": "SCIM 토큰",
"SCIM tokens": "SCIM 토큰",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "이 작업은 되돌릴 수 없습니다. ID 공급자가 즉시 동기화를 중지합니다.",
"Toggle SCIM provisioning": "SCIM 프로비저닝 전환",
"Token": "토큰",
"Page menu": "페이지 메뉴",
"Expand": "펼치기",
"Collapse": "접기",
"Comment menu": "댓글 메뉴",
"Group menu": "그룹 메뉴",
"Show hidden breadcrumbs": "숨겨진 이동 경로 표시",
"Breadcrumbs": "이동 경로",
"Page actions": "페이지 작업",
"Pick emoji": "이모지 선택",
"Template menu": "템플릿 메뉴",
"Use": "사용",
"Use template": "템플릿 사용",
"Preview template: {{title}}": "템플릿 미리보기: {{title}}",
"Use a template": "템플릿 사용",
"Search templates...": "템플릿 검색...",
"Search spaces...": "스페이스 검색...",
"No templates found": "템플릿을 찾을 수 없습니다",
"No spaces found": "스페이스를 찾을 수 없습니다",
"Browse all templates": "모든 템플릿 보기",
"This space": "이 스페이스",
"All templates": "모든 템플릿",
"Global": "전역",
"New template": "새 템플릿",
"Edit template": "템플릿 편집",
"Are you sure you want to delete this template?": "이 템플릿을 삭제하시겠습니까?",
"Template scope updated": "템플릿 범위가 업데이트되었습니다",
"Choose which space this template belongs to": "이 템플릿이 속할 스페이스를 선택하세요",
"Scope": "범위",
"Select scope": "범위 선택",
"Title": "제목",
"Saving...": "저장 중...",
"Saved": "저장됨",
"Save failed. Retry": "저장에 실패했습니다. 다시 시도하세요",
"By {{name}}": "작성자 {{name}}",
"Updated {{time}}": "{{time}}에 업데이트됨",
"Choose destination": "대상 선택",
"Search pages and spaces...": "페이지와 스페이스 검색...",
"No results found": "결과를 찾을 수 없습니다",
"You don't have permission to create pages here": "여기에서 페이지를 만들 권한이 없습니다",
"Chat menu": "채팅 메뉴",
"API key menu": "API 키 메뉴",
"Jump to comment selection": "댓글 선택으로 이동",
"Slash commands": "슬래시 명령어",
"Mention suggestions": "멘션 추천",
"Link suggestions": "링크 추천",
"Diagram editor": "다이어그램 편집기",
"Add comment": "댓글 추가",
"Find and replace": "찾기 및 바꾸기",
"Main navigation": "기본 탐색",
"Space navigation": "스페이스 탐색",
"Settings navigation": "설정 탐색",
"AI navigation": "AI 탐색",
"Breadcrumb": "이동 경로",
"Synced block": "동기화된 블록",
"Create a block that stays in sync across pages.": "페이지 간에 동기화된 상태로 유지되는 블록을 만드세요.",
"Editing original": "원본 편집 중",
"Copy synced block": "동기화된 블록 복사",
"Unsync": "동기화 해제",
"Delete synced block": "동기화된 블록 삭제",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "원본",
"THIS PAGE": "이 페이지",
"No pages": "페이지 없음",
"The original synced block no longer exists": "원본 동기화 블록이 더 이상 존재하지 않습니다",
"You don't have access to this synced block": "이 동기화된 블록에 접근할 수 없습니다",
"Failed to load this synced block": "이 동기화된 블록을 불러오지 못했습니다",
"Fixed editor toolbar": "고정된 편집기 도구 모음",
"Show a formatting toolbar above the editor with quick access to common actions.": "일반적인 작업에 빠르게 접근할 수 있도록 편집기 위에 서식 도구 모음을 표시합니다.",
"Toggle fixed editor toolbar": "고정된 편집기 도구 모음 전환",
"Normal text": "일반 텍스트",
"More inline formatting": "추가 인라인 서식",
"Subscript": "아래 첨자",
"Superscript": "위 첨자",
"Inline code": "인라인 코드",
"Insert media": "미디어 삽입",
"Mention": "멘션",
"Emoji": "이모지",
"Columns": "열",
"More inserts": "더 많은 삽입",
"Embeds": "임베드",
"Diagrams": "다이어그램",
"Advanced": "고급",
"Utility": "유틸리티",
"Decrease indent": "들여쓰기 줄이기",
"Increase indent": "들여쓰기 늘리기",
"Clear formatting": "서식 지우기",
"Code block": "코드 블록",
"Experimental": "실험 기능",
"Strikethrough": "취소선",
"Undo": "실행 취소",
"Redo": "다시 실행",
"Backlinks": "백링크",
"Last updated by": "마지막 업데이트한 사람",
"Last updated": "마지막 업데이트",
"Stats": "통계",
"Word count": "단어 수",
"Characters": "문자 수",
"Incoming links": "들어오는 링크",
"Outgoing links": "나가는 링크",
"Incoming links ({{count}})": "들어오는 링크 ({{count}})",
"Outgoing links ({{count}})": "나가는 링크 ({{count}})",
"No pages link here yet.": "아직 여기에 링크된 페이지가 없습니다.",
"This page doesn't link to other pages yet.": "이 페이지는 아직 다른 페이지에 링크되어 있지 않습니다.",
"Verified until {{date}}": "{{date}}까지 검증됨",
"Labels": "라벨",
"Add label": "라벨 추가",
"No labels yet": "아직 라벨이 없습니다",
"Already added": "이미 추가됨",
"Invalid label name": "유효하지 않은 라벨 이름",
"No matches": "일치하는 항목 없음",
"Search or create…": "검색하거나 만들기…",
"Remove label {{name}}": "라벨 {{name}} 제거",
"Failed to add label": "라벨 추가 실패",
"Failed to remove label": "라벨 제거 실패",
"No pages with this label": "이 라벨이 지정된 페이지가 없습니다",
"Pages tagged with this label will appear here.": "이 라벨이 지정된 페이지가 여기에 표시됩니다.",
"No pages match your search.": "검색과 일치하는 페이지가 없습니다.",
"Updated {{date}}": "{{date}}에 업데이트됨",
"Cell actions": "셀 작업",
"Column actions": "열 작업",
"Row actions": "행 작업",
"Filter": "필터",
"Page title": "페이지 제목",
"Page content": "페이지 내용",
"Member actions": "멤버 작업",
"Toggle password visibility": "비밀번호 표시 전환",
"Send comment": "댓글 보내기",
"Token actions": "토큰 작업",
"Template settings": "템플릿 설정",
"Edit diagram": "다이어그램 편집",
"Edit embed": "임베드 편집",
"Edit drawing": "드로잉 편집",
"Delete equation": "수식 삭제",
"Invite actions": "초대 작업",
"Get started": "시작하기",
"* indicates required fields": "* 는 필수 입력 항목을 나타냅니다",
"List of spaces in this workspace": "이 워크스페이스의 스페이스 목록",
"Active sessions": "활성 세션",
"Add {{name}} to favorites": "{{name}} 즐겨찾기에 추가",
"Remove {{name}} from favorites": "{{name}} 즐겨찾기에서 제거",
"Added to favorites": "즐겨찾기에 추가됨",
"Removed from favorites": "즐겨찾기에서 제거됨",
"Added {{name}} to favorites": "{{name}} 즐겨찾기에 추가됨",
"Removed {{name}} from favorites": "{{name}} 즐겨찾기에서 제거됨",
"Page menu for {{name}}": "{{name}}의 페이지 메뉴",
"Create subpage of {{name}}": "{{name}}의 하위 페이지 만들기"
} }
@@ -71,6 +71,7 @@
"Export": "Exporteer", "Export": "Exporteer",
"Failed to create page": "Pagina aanmaken mislukt", "Failed to create page": "Pagina aanmaken mislukt",
"Failed to delete page": "Verwijderen van pagina mislukt", "Failed to delete page": "Verwijderen van pagina mislukt",
"Failed to restore page": "Pagina herstellen mislukt",
"Failed to fetch recent pages": "Kan recente pagina's niet ophalen", "Failed to fetch recent pages": "Kan recente pagina's niet ophalen",
"Failed to import pages": "Pagina's importeren mislukt", "Failed to import pages": "Pagina's importeren mislukt",
"Failed to load page. An error occurred.": "Laden van pagina mislukt. Er is een fout opgetreden.", "Failed to load page. An error occurred.": "Laden van pagina mislukt. Er is een fout opgetreden.",
@@ -276,6 +277,9 @@
"Align left": "Links uitlijnen", "Align left": "Links uitlijnen",
"Align right": "Rechts uitlijnen", "Align right": "Rechts uitlijnen",
"Align center": "Centreren", "Align center": "Centreren",
"Alt text": "Alternatieve tekst",
"Describe this for accessibility.": "Beschrijf dit voor toegankelijkheid.",
"Add a description": "Een beschrijving toevoegen",
"Justify": "Uitvullen", "Justify": "Uitvullen",
"Merge cells": "Cellen samenvoegen", "Merge cells": "Cellen samenvoegen",
"Split cell": "Cel splitsen", "Split cell": "Cel splitsen",
@@ -286,6 +290,19 @@
"Add row above": "Rij hierboven toevoegen", "Add row above": "Rij hierboven toevoegen",
"Add row below": "Rij hieronder toevoegen", "Add row below": "Rij hieronder toevoegen",
"Delete table": "Verwijder tabel", "Delete table": "Verwijder tabel",
"Add column left": "Kolom links toevoegen",
"Add column right": "Kolom rechts toevoegen",
"Clear cell": "Cel wissen",
"Clear cells": "Cellen wissen",
"Toggle header cell": "Kopcel in-/uitschakelen",
"Toggle header column": "Kopkolom in-/uitschakelen",
"Toggle header row": "Koprij in-/uitschakelen",
"Move column left": "Kolom naar links verplaatsen",
"Move column right": "Kolom naar rechts verplaatsen",
"Move row down": "Rij omlaag verplaatsen",
"Move row up": "Rij omhoog verplaatsen",
"Sort A → Z": "Sorteren A → Z",
"Sort Z → A": "Sorteren Z → A",
"Info": "Info", "Info": "Info",
"Note": "Opmerking", "Note": "Opmerking",
"Success": "Geslaagd", "Success": "Geslaagd",
@@ -348,6 +365,8 @@
"Create block quote.": "Maak een block quote.", "Create block quote.": "Maak een block quote.",
"Insert code snippet.": "Codefragment invoegen.", "Insert code snippet.": "Codefragment invoegen.",
"Insert horizontal rule divider": "Horizontale lijn invoegen", "Insert horizontal rule divider": "Horizontale lijn invoegen",
"Page break": "Pagina-einde",
"Insert a page break for printing.": "Voeg een pagina-einde in voor het afdrukken.",
"Upload any image from your device.": "Upload een afbeelding vanaf uw apparaat.", "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 video from your device.": "Upload een video vanaf uw apparaat.",
"Upload any audio from your device.": "Upload een audio vanaf uw apparaat.", "Upload any audio from your device.": "Upload een audio vanaf uw apparaat.",
@@ -392,6 +411,10 @@
"Write...": "Typ...", "Write...": "Typ...",
"Column count": "Aantal kolommen", "Column count": "Aantal kolommen",
"{{count}} Columns": "{{count}} kolommen", "{{count}} Columns": "{{count}} kolommen",
"{{count}} command available_one": "1 opdracht beschikbaar",
"{{count}} command available_other": "{{count}} opdrachten beschikbaar",
"{{count}} result available_one": "1 resultaat beschikbaar",
"{{count}} result available_other": "{{count}} resultaten beschikbaar",
"Equal columns": "Gelijke kolommen", "Equal columns": "Gelijke kolommen",
"Left sidebar": "Linker zijbalk", "Left sidebar": "Linker zijbalk",
"Right sidebar": "Rechter zijbalk", "Right sidebar": "Rechter zijbalk",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} is beschikbaar", "{{latestVersion}} is available": "{{latestVersion}} is beschikbaar",
"Default page edit mode": "Standaard bewerkingsmodus voor pagina", "Default page edit mode": "Standaard bewerkingsmodus voor pagina",
"Choose your preferred page edit mode. Avoid accidental edits.": "Kies uw voorkeurs bewerkmodus voor pagina's. Vermijd per ongeluk bewerken.", "Choose your preferred page edit mode. Avoid accidental edits.": "Kies uw voorkeurs bewerkmodus voor pagina's. Vermijd per ongeluk bewerken.",
"Choose {{format}} file": "Kies {{format}}-bestand",
"Reading": "Lezen", "Reading": "Lezen",
"Delete member": "Lid verwijderen", "Delete member": "Lid verwijderen",
"Member deleted successfully": "Lid succesvol verwijderd", "Member deleted successfully": "Lid succesvol verwijderd",
@@ -565,6 +589,8 @@
"Move to trash": "Verplaatsen naar prullenbak", "Move to trash": "Verplaatsen naar prullenbak",
"Move this page to trash?": "Deze pagina naar de prullenbak verplaatsen?", "Move this page to trash?": "Deze pagina naar de prullenbak verplaatsen?",
"Restore page": "Pagina herstellen", "Restore page": "Pagina herstellen",
"Permanently delete": "Permanent verwijderen",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> heeft deze pagina {{time}} naar de prullenbak verplaatst.",
"Page moved to trash": "Pagina verplaatst naar prullenbak", "Page moved to trash": "Pagina verplaatst naar prullenbak",
"Page restored successfully": "Pagina succesvol hersteld", "Page restored successfully": "Pagina succesvol hersteld",
"Deleted by": "Verwijderd door", "Deleted by": "Verwijderd door",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "Afbeelding overschrijdt de limiet van 10MB.", "Image exceeds 10MB limit.": "Afbeelding overschrijdt de limiet van 10MB.",
"Image removed successfully": "Afbeelding succesvol verwijderd", "Image removed successfully": "Afbeelding succesvol verwijderd",
"API key": "API-sleutel", "API key": "API-sleutel",
"API key created successfully": "API-sleutel succesvol aangemaakt",
"API keys": "API-sleutels", "API keys": "API-sleutels",
"API management": "API-beheer", "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", "Custom expiration date": "Aangepaste vervaldatum",
"Enter a descriptive token name": "Voer een beschrijvende tokennaam in", "Enter a descriptive token name": "Voer een beschrijvende tokennaam in",
"Expiration": "Vervaldatum", "Expiration": "Vervaldatum",
"Expired": "Verlopen", "Expired": "Verlopen",
"Expires": "Verloopt", "Expires": "Verloopt",
"I've saved my API key": "Ik heb mijn API-sleutel opgeslagen",
"Last use": "Laatst gebruikt", "Last use": "Laatst gebruikt",
"No API keys found": "Geen API-sleutels gevonden", "No API keys found": "Geen API-sleutels gevonden",
"No expiration": "Geen vervaldatum", "No expiration": "Geen vervaldatum",
"Revoke API key": "API-sleutel intrekken",
"Revoked successfully": "Succesvol ingetrokken", "Revoked successfully": "Succesvol ingetrokken",
"Select expiration date": "Selecteer vervaldatum", "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.", "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", "Update": "Bijwerken",
"Update {{credential}}": "{{credential}} bijwerken",
"Manage API keys for all users in the workspace": "Beheer API-sleutels voor alle gebruikers in de werkruimte", "Manage API keys for all users in the workspace": "Beheer API-sleutels voor alle gebruikers in de werkruimte",
"Restrict API key creation to admins": "Beperk het aanmaken van API-sleutels tot beheerders.", "Restrict API key creation to admins": "Beperk het aanmaken van API-sleutels tot beheerders.",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Alleen beheerders en eigenaren kunnen nieuwe API-sleutels aanmaken. Bestaande leden-sleutels blijven werken.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Alleen beheerders en eigenaren kunnen nieuwe API-sleutels aanmaken. Bestaande leden-sleutels blijven werken.",
@@ -858,9 +880,12 @@
"AI Chat": "AI-chat", "AI Chat": "AI-chat",
"Analyze for insights": "Analyseren voor inzichten", "Analyze for insights": "Analyseren voor inzichten",
"Ask anything...": "Vraag iets...", "Ask anything...": "Vraag iets...",
"Assistant said:": "Assistent zei:",
"Chat history": "Chatgeschiedenis", "Chat history": "Chatgeschiedenis",
"Chat name": "Chatnaam", "Chat name": "Chatnaam",
"Chat transcript": "Chattranscript",
"Close": "Sluiten", "Close": "Sluiten",
"Copy assistant response": "Reactie van assistent kopiëren",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "Chat laden mislukt. Er is een fout opgetreden.", "Failed to load chat. An error occurred.": "Chat laden mislukt. Er is een fout opgetreden.",
"Failed to render this message.": "Dit bericht kon niet worden weergegeven.", "Failed to render this message.": "Dit bericht kon niet worden weergegeven.",
@@ -870,9 +895,17 @@
"No chats found": "Geen chats gevonden", "No chats found": "Geen chats gevonden",
"No conversations yet": "Nog geen gesprekken", "No conversations yet": "Nog geen gesprekken",
"Open full page": "Volledige pagina openen", "Open full page": "Volledige pagina openen",
"Scroll to bottom": "Naar beneden scrollen",
"You said:": "Jij zei:",
"Previous 7 days": "Afgelopen 7 dagen", "Previous 7 days": "Afgelopen 7 dagen",
"Previous 30 days": "Afgelopen 30 dagen", "Previous 30 days": "Afgelopen 30 dagen",
"Search chats...": "Chats zoeken...", "Search chats...": "Chats zoeken...",
"Search chats": "Chats zoeken",
"Ask anything... Use @ to mention pages": "Vraag iets... Gebruik @ om pagina's te vermelden",
"Ask anything or search your workspace": "Vraag iets of doorzoek je werkruimte",
"Welcome to {{name}}": "Welkom bij {{name}}",
"Add files": "Bestanden toevoegen",
"Mention a page": "Een pagina vermelden",
"Start a new chat to see it here.": "Start een nieuwe chat om die hier te zien.", "Start a new chat to see it here.": "Start een nieuwe chat om die hier te zien.",
"Summarize this page": "Vat deze pagina samen", "Summarize this page": "Vat deze pagina samen",
"Toggle AI Chat": "AI-chat in-/uitschakelen", "Toggle AI Chat": "AI-chat in-/uitschakelen",
@@ -880,5 +913,176 @@
"Try a different search term.": "Probeer een andere zoekterm.", "Try a different search term.": "Probeer een andere zoekterm.",
"Try again": "Probeer opnieuw", "Try again": "Probeer opnieuw",
"Untitled chat": "Chat zonder titel", "Untitled chat": "Chat zonder titel",
"What can I help you with?": "Waar kan ik je mee helpen?" "What can I help you with?": "Waar kan ik je mee helpen?",
"Are you sure you want to revoke this {{credential}}": "Weet u zeker dat u deze {{credential}} wilt intrekken",
"Automatically provision users and groups from your identity provider via SCIM.": "Voorzie gebruikers en groepen automatisch vanuit uw identiteitsprovider via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configureer uw identiteitsprovider met deze URL om gebruikers en groepen te provisioneren.",
"Create {{credential}}": "{{credential}} maken",
"{{credential}} created": "{{credential}} aangemaakt",
"{{credential}} created successfully": "{{credential}} succesvol aangemaakt",
"Created by": "Aangemaakt door",
"Custom": "Aangepast",
"Enable SCIM": "SCIM inschakelen",
"Enter a descriptive name": "Voer een beschrijvende naam in",
"I've saved my {{credential}}": "Ik heb mijn {{credential}} opgeslagen",
"Important": "Belangrijk",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Zorg ervoor dat u uw {{credential}} nu kopieert. U kunt deze niet meer bekijken!",
"Never": "Nooit",
"Revoke {{credential}}": "{{credential}} intrekken",
"SCIM endpoint URL": "SCIM-eindpunt-URL",
"SCIM provisioning": "SCIM-provisioning",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM heeft voorrang op SSO-groepssynchronisatie zolang het is ingeschakeld.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "U heeft het maximum van {{max}} SCIM-tokens bereikt. Verwijder een bestaand token om een nieuw token aan te maken.",
"SCIM token": "SCIM-token",
"SCIM tokens": "SCIM-tokens",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Deze actie kan niet ongedaan worden gemaakt. Uw identiteitsprovider stopt onmiddellijk met synchroniseren.",
"Toggle SCIM provisioning": "SCIM-provisioning in-/uitschakelen",
"Token": "Token",
"Page menu": "Paginamenu",
"Expand": "Uitvouwen",
"Collapse": "Samenvouwen",
"Comment menu": "Reactiemenu",
"Group menu": "Groepsmenu",
"Show hidden breadcrumbs": "Verborgen broodkruimels weergeven",
"Breadcrumbs": "Broodkruimels",
"Page actions": "Pagina-acties",
"Pick emoji": "Emoji kiezen",
"Template menu": "Sjabloonmenu",
"Use": "Gebruiken",
"Use template": "Sjabloon gebruiken",
"Preview template: {{title}}": "Voorbeeld van sjabloon: {{title}}",
"Use a template": "Een sjabloon gebruiken",
"Search templates...": "Sjablonen zoeken...",
"Search spaces...": "Werkruimtes zoeken...",
"No templates found": "Geen sjablonen gevonden",
"No spaces found": "Geen werkruimtes gevonden",
"Browse all templates": "Alle sjablonen bekijken",
"This space": "Deze werkruimte",
"All templates": "Alle sjablonen",
"Global": "Globaal",
"New template": "Nieuw sjabloon",
"Edit template": "Sjabloon bewerken",
"Are you sure you want to delete this template?": "Weet je zeker dat je dit sjabloon wilt verwijderen?",
"Template scope updated": "Bereik van sjabloon bijgewerkt",
"Choose which space this template belongs to": "Kies bij welke werkruimte dit sjabloon hoort",
"Scope": "Bereik",
"Select scope": "Bereik selecteren",
"Title": "Titel",
"Saving...": "Opslaan...",
"Saved": "Opgeslagen",
"Save failed. Retry": "Opslaan mislukt. Opnieuw proberen",
"By {{name}}": "Door {{name}}",
"Updated {{time}}": "Bijgewerkt {{time}}",
"Choose destination": "Bestemming kiezen",
"Search pages and spaces...": "Pagina's en werkruimtes zoeken...",
"No results found": "Geen resultaten gevonden",
"You don't have permission to create pages here": "Je hebt geen toestemming om hier pagina's te maken",
"Chat menu": "Chatmenu",
"API key menu": "API-sleutelmenu",
"Jump to comment selection": "Naar reactieselectie springen",
"Slash commands": "Slash-opdrachten",
"Mention suggestions": "Vermeldingssuggesties",
"Link suggestions": "Linksuggesties",
"Diagram editor": "Diagrameditor",
"Add comment": "Reactie toevoegen",
"Find and replace": "Zoeken en vervangen",
"Main navigation": "Hoofdnavigatie",
"Space navigation": "Ruimtenavigatie",
"Settings navigation": "Instellingennavigatie",
"AI navigation": "AI-navigatie",
"Breadcrumb": "Broodkruimel",
"Synced block": "Gesynchroniseerd blok",
"Create a block that stays in sync across pages.": "Maak een blok dat gesynchroniseerd blijft op meerdere pagina's.",
"Editing original": "Origineel bewerken",
"Copy synced block": "Gesynchroniseerd blok kopiëren",
"Unsync": "Synchronisatie opheffen",
"Delete synced block": "Gesynchroniseerd blok verwijderen",
"Synced to {{count}} other page_one": "Gesynchroniseerd met {{count}} andere pagina",
"Synced to {{count}} other page_other": "Gesynchroniseerd met {{count}} andere pagina's",
"ORIGINAL": "ORIGINEEL",
"THIS PAGE": "DEZE PAGINA",
"No pages": "Geen pagina's",
"The original synced block no longer exists": "Het oorspronkelijke gesynchroniseerde blok bestaat niet meer",
"You don't have access to this synced block": "Je hebt geen toegang tot dit gesynchroniseerde blok",
"Failed to load this synced block": "Dit gesynchroniseerde blok kon niet worden geladen",
"Fixed editor toolbar": "Vaste editorwerkbalk",
"Show a formatting toolbar above the editor with quick access to common actions.": "Toon een opmaakwerkbalk boven de editor met snelle toegang tot veelgebruikte acties.",
"Toggle fixed editor toolbar": "Vaste editorwerkbalk in-/uitschakelen",
"Normal text": "Normale tekst",
"More inline formatting": "Meer inline-opmaak",
"Subscript": "Subscript",
"Superscript": "Superscript",
"Inline code": "Inline-code",
"Insert media": "Media invoegen",
"Mention": "Vermelding",
"Emoji": "Emoji",
"Columns": "Kolommen",
"More inserts": "Meer invoegingen",
"Embeds": "Insluitingen",
"Diagrams": "Diagrammen",
"Advanced": "Geavanceerd",
"Utility": "Hulpprogramma's",
"Decrease indent": "Inspringing verkleinen",
"Increase indent": "Inspringing vergroten",
"Clear formatting": "Opmaak wissen",
"Code block": "Codeblok",
"Experimental": "Experimenteel",
"Strikethrough": "Doorhalen",
"Undo": "Ongedaan maken",
"Redo": "Opnieuw",
"Backlinks": "Terugkoppelingen",
"Last updated by": "Laatst bijgewerkt door",
"Last updated": "Laatst bijgewerkt",
"Stats": "Statistieken",
"Word count": "Aantal woorden",
"Characters": "Tekens",
"Incoming links": "Inkomende links",
"Outgoing links": "Uitgaande links",
"Incoming links ({{count}})": "Inkomende links ({{count}})",
"Outgoing links ({{count}})": "Uitgaande links ({{count}})",
"No pages link here yet.": "Er linken nog geen pagina's hiernaartoe.",
"This page doesn't link to other pages yet.": "Deze pagina linkt nog niet naar andere pagina's.",
"Verified until {{date}}": "Geverifieerd tot {{date}}",
"Labels": "Labels",
"Add label": "Label toevoegen",
"No labels yet": "Nog geen labels",
"Already added": "Al toegevoegd",
"Invalid label name": "Ongeldige labelnaam",
"No matches": "Geen overeenkomsten",
"Search or create…": "Zoeken of maken…",
"Remove label {{name}}": "Label {{name}} verwijderen",
"Failed to add label": "Label toevoegen mislukt",
"Failed to remove label": "Label verwijderen mislukt",
"No pages with this label": "Geen pagina's met dit label",
"Pages tagged with this label will appear here.": "Pagina's met dit label worden hier weergegeven.",
"No pages match your search.": "Geen pagina's komen overeen met je zoekopdracht.",
"Updated {{date}}": "Bijgewerkt op {{date}}",
"Cell actions": "Celacties",
"Column actions": "Kolomacties",
"Row actions": "Rijacties",
"Filter": "Filter",
"Page title": "Paginatitel",
"Page content": "Pagina-inhoud",
"Member actions": "Lidacties",
"Toggle password visibility": "Wachtwoordzichtbaarheid in-/uitschakelen",
"Send comment": "Reactie verzenden",
"Token actions": "Tokenacties",
"Template settings": "Sjablooninstellingen",
"Edit diagram": "Diagram bewerken",
"Edit embed": "Insluiting bewerken",
"Edit drawing": "Tekening bewerken",
"Delete equation": "Vergelijking verwijderen",
"Invite actions": "Uitnodigingsacties",
"Get started": "Aan de slag",
"* indicates required fields": "* geeft verplichte velden aan",
"List of spaces in this workspace": "Lijst met werkruimtes in deze workspace",
"Active sessions": "Actieve sessies",
"Add {{name}} to favorites": "{{name}} aan favorieten toevoegen",
"Remove {{name}} from favorites": "{{name}} uit favorieten verwijderen",
"Added to favorites": "Toegevoegd aan favorieten",
"Removed from favorites": "Verwijderd uit favorieten",
"Added {{name}} to favorites": "{{name}} toegevoegd aan favorieten",
"Removed {{name}} from favorites": "{{name}} verwijderd uit favorieten",
"Page menu for {{name}}": "Paginamenu voor {{name}}",
"Create subpage of {{name}}": "Subpagina van {{name}} maken"
} }
@@ -71,6 +71,7 @@
"Export": "Exportar", "Export": "Exportar",
"Failed to create page": "Falha ao criar página", "Failed to create page": "Falha ao criar página",
"Failed to delete page": "Falha ao excluir página", "Failed to delete page": "Falha ao excluir página",
"Failed to restore page": "Falha ao restaurar página",
"Failed to fetch recent pages": "Falha ao buscar páginas recentes", "Failed to fetch recent pages": "Falha ao buscar páginas recentes",
"Failed to import pages": "Falha ao importar páginas", "Failed to import pages": "Falha ao importar páginas",
"Failed to load page. An error occurred.": "Falha ao carregar página. Ocorreu um erro.", "Failed to load page. An error occurred.": "Falha ao carregar página. Ocorreu um erro.",
@@ -276,6 +277,9 @@
"Align left": "Alinhar à esquerda", "Align left": "Alinhar à esquerda",
"Align right": "Alinhar à direita", "Align right": "Alinhar à direita",
"Align center": "Alinhar ao centro", "Align center": "Alinhar ao centro",
"Alt text": "Texto alternativo",
"Describe this for accessibility.": "Descreva isto para acessibilidade.",
"Add a description": "Adicionar uma descrição",
"Justify": "Justificar", "Justify": "Justificar",
"Merge cells": "Mesclar células", "Merge cells": "Mesclar células",
"Split cell": "Dividir célula", "Split cell": "Dividir célula",
@@ -286,6 +290,19 @@
"Add row above": "Adicionar linha acima", "Add row above": "Adicionar linha acima",
"Add row below": "Adicionar linha abaixo", "Add row below": "Adicionar linha abaixo",
"Delete table": "Excluir tabela", "Delete table": "Excluir tabela",
"Add column left": "Adicionar coluna à esquerda",
"Add column right": "Adicionar coluna à direita",
"Clear cell": "Limpar célula",
"Clear cells": "Limpar células",
"Toggle header cell": "Alternar célula de cabeçalho",
"Toggle header column": "Alternar coluna de cabeçalho",
"Toggle header row": "Alternar linha de cabeçalho",
"Move column left": "Mover coluna para a esquerda",
"Move column right": "Mover coluna para a direita",
"Move row down": "Mover linha para baixo",
"Move row up": "Mover linha para cima",
"Sort A → Z": "Ordenar A → Z",
"Sort Z → A": "Ordenar Z → A",
"Info": "Informação", "Info": "Informação",
"Note": "Observação", "Note": "Observação",
"Success": "Sucesso", "Success": "Sucesso",
@@ -348,6 +365,8 @@
"Create block quote.": "Crie uma citação em bloco.", "Create block quote.": "Crie uma citação em bloco.",
"Insert code snippet.": "Insira um trecho de código.", "Insert code snippet.": "Insira um trecho de código.",
"Insert horizontal rule divider": "Insira um divisor horizontal", "Insert horizontal rule divider": "Insira um divisor horizontal",
"Page break": "Quebra de página",
"Insert a page break for printing.": "Insira uma quebra de página para impressão.",
"Upload any image from your device.": "Envie qualquer imagem do seu dispositivo.", "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 video from your device.": "Envie qualquer vídeo do seu dispositivo.",
"Upload any audio from your device.": "Envie qualquer áudio do seu dispositivo.", "Upload any audio from your device.": "Envie qualquer áudio do seu dispositivo.",
@@ -392,6 +411,10 @@
"Write...": "Escreva...", "Write...": "Escreva...",
"Column count": "Número de colunas", "Column count": "Número de colunas",
"{{count}} Columns": "{{count}} colunas", "{{count}} Columns": "{{count}} colunas",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Colunas iguais", "Equal columns": "Colunas iguais",
"Left sidebar": "Barra lateral esquerda", "Left sidebar": "Barra lateral esquerda",
"Right sidebar": "Barra lateral direita", "Right sidebar": "Barra lateral direita",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} está disponível", "{{latestVersion}} is available": "{{latestVersion}} está disponível",
"Default page edit mode": "Modo padrão de edição da página", "Default page edit mode": "Modo padrão de edição da página",
"Choose your preferred page edit mode. Avoid accidental edits.": "Escolha o modo de edição de página preferido. Evite edições acidentais.", "Choose your preferred page edit mode. Avoid accidental edits.": "Escolha o modo de edição de página preferido. Evite edições acidentais.",
"Choose {{format}} file": "Escolher arquivo {{format}}",
"Reading": "Leitura", "Reading": "Leitura",
"Delete member": "Excluir membro", "Delete member": "Excluir membro",
"Member deleted successfully": "Membro excluído com sucesso", "Member deleted successfully": "Membro excluído com sucesso",
@@ -565,6 +589,8 @@
"Move to trash": "Mover para a lixeira", "Move to trash": "Mover para a lixeira",
"Move this page to trash?": "Mover esta página para a lixeira?", "Move this page to trash?": "Mover esta página para a lixeira?",
"Restore page": "Restaurar página", "Restore page": "Restaurar página",
"Permanently delete": "Excluir permanentemente",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> moveu esta página para a Lixeira {{time}}.",
"Page moved to trash": "Página movida para a lixeira", "Page moved to trash": "Página movida para a lixeira",
"Page restored successfully": "Página restaurada com sucesso", "Page restored successfully": "Página restaurada com sucesso",
"Deleted by": "Excluído por", "Deleted by": "Excluído por",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "A imagem excede o limite de 10MB.", "Image exceeds 10MB limit.": "A imagem excede o limite de 10MB.",
"Image removed successfully": "Imagem removida com sucesso", "Image removed successfully": "Imagem removida com sucesso",
"API key": "Chave API", "API key": "Chave API",
"API key created successfully": "Chave API criada com sucesso",
"API keys": "Chaves API", "API keys": "Chaves API",
"API management": "Gestão de 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", "Custom expiration date": "Data de expiração personalizada",
"Enter a descriptive token name": "Insira um nome descritivo para o token", "Enter a descriptive token name": "Insira um nome descritivo para o token",
"Expiration": "Expiração", "Expiration": "Expiração",
"Expired": "Expirado", "Expired": "Expirado",
"Expires": "Expira", "Expires": "Expira",
"I've saved my API key": "Salvei minha chave API",
"Last use": "Último uso", "Last use": "Último uso",
"No API keys found": "Nenhuma chave API encontrada", "No API keys found": "Nenhuma chave API encontrada",
"No expiration": "Sem expiração", "No expiration": "Sem expiração",
"Revoke API key": "Revogar chave API",
"Revoked successfully": "Revogada com sucesso", "Revoked successfully": "Revogada com sucesso",
"Select expiration date": "Selecionar data de expiração", "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.", "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", "Update": "Atualizar",
"Update {{credential}}": "Atualizar {{credential}}",
"Manage API keys for all users in the workspace": "Gerenciar chaves API para todos os usuários no espaço de trabalho", "Manage API keys for all users in the workspace": "Gerenciar chaves API para todos os usuários no espaço de trabalho",
"Restrict API key creation to admins": "Restringir a criação de chave de API aos administradores", "Restrict API key creation to admins": "Restringir a criação de chave de API aos administradores",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Somente administradores e proprietários podem criar novas chaves de API. As chaves de membros já existentes continuarão funcionando.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Somente administradores e proprietários podem criar novas chaves de API. As chaves de membros já existentes continuarão funcionando.",
@@ -858,9 +880,12 @@
"AI Chat": "Chat com IA", "AI Chat": "Chat com IA",
"Analyze for insights": "Analisar para obter insights", "Analyze for insights": "Analisar para obter insights",
"Ask anything...": "Pergunte qualquer coisa...", "Ask anything...": "Pergunte qualquer coisa...",
"Assistant said:": "O assistente disse:",
"Chat history": "Histórico de chats", "Chat history": "Histórico de chats",
"Chat name": "Nome do chat", "Chat name": "Nome do chat",
"Chat transcript": "Transcrição do chat",
"Close": "Fechar", "Close": "Fechar",
"Copy assistant response": "Copiar resposta do assistente",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "Falha ao carregar o chat. Ocorreu um erro.", "Failed to load chat. An error occurred.": "Falha ao carregar o chat. Ocorreu um erro.",
"Failed to render this message.": "Falha ao renderizar esta mensagem.", "Failed to render this message.": "Falha ao renderizar esta mensagem.",
@@ -870,9 +895,17 @@
"No chats found": "Nenhum chat encontrado", "No chats found": "Nenhum chat encontrado",
"No conversations yet": "Ainda não há conversas", "No conversations yet": "Ainda não há conversas",
"Open full page": "Abrir página inteira", "Open full page": "Abrir página inteira",
"Scroll to bottom": "Rolar até o fim",
"You said:": "Você disse:",
"Previous 7 days": "Últimos 7 dias", "Previous 7 days": "Últimos 7 dias",
"Previous 30 days": "Últimos 30 dias", "Previous 30 days": "Últimos 30 dias",
"Search chats...": "Pesquisar chats...", "Search chats...": "Pesquisar chats...",
"Search chats": "Pesquisar chats",
"Ask anything... Use @ to mention pages": "Pergunte qualquer coisa... Use @ para mencionar páginas",
"Ask anything or search your workspace": "Pergunte qualquer coisa ou pesquise no seu workspace",
"Welcome to {{name}}": "Boas-vindas a {{name}}",
"Add files": "Adicionar arquivos",
"Mention a page": "Mencionar uma página",
"Start a new chat to see it here.": "Inicie um novo chat para vê-lo aqui.", "Start a new chat to see it here.": "Inicie um novo chat para vê-lo aqui.",
"Summarize this page": "Resumir esta página", "Summarize this page": "Resumir esta página",
"Toggle AI Chat": "Alternar chat com IA", "Toggle AI Chat": "Alternar chat com IA",
@@ -880,5 +913,176 @@
"Try a different search term.": "Tente um termo de pesquisa diferente.", "Try a different search term.": "Tente um termo de pesquisa diferente.",
"Try again": "Tentar novamente", "Try again": "Tentar novamente",
"Untitled chat": "Chat sem título", "Untitled chat": "Chat sem título",
"What can I help you with?": "Com o que posso ajudar você?" "What can I help you with?": "Com o que posso ajudar você?",
"Are you sure you want to revoke this {{credential}}": "Tem certeza de que deseja revogar esta {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Provisione automaticamente usuários e grupos do seu provedor de identidade via SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Configure seu provedor de identidade com esta URL para provisionar usuários e grupos.",
"Create {{credential}}": "Criar {{credential}}",
"{{credential}} created": "{{credential}} criada",
"{{credential}} created successfully": "{{credential}} criada com sucesso",
"Created by": "Criado por",
"Custom": "Personalizado",
"Enable SCIM": "Ativar SCIM",
"Enter a descriptive name": "Insira um nome descritivo",
"I've saved my {{credential}}": "Salvei minha {{credential}}",
"Important": "Importante",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Copie sua {{credential}} agora. Você não poderá vê-la novamente!",
"Never": "Nunca",
"Revoke {{credential}}": "Revogar {{credential}}",
"SCIM endpoint URL": "URL do endpoint SCIM",
"SCIM provisioning": "Provisionamento SCIM",
"SCIM takes precedence over SSO group sync while enabled.": "O SCIM tem precedência sobre a sincronização de grupos por SSO enquanto estiver ativado.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Você atingiu o máximo de {{max}} tokens SCIM. Exclua um token existente para criar um novo.",
"SCIM token": "Token SCIM",
"SCIM tokens": "Tokens SCIM",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Esta ação não pode ser desfeita. Seu provedor de identidade deixará de sincronizar imediatamente.",
"Toggle SCIM provisioning": "Alternar provisionamento SCIM",
"Token": "Token",
"Page menu": "Menu da página",
"Expand": "Expandir",
"Collapse": "Recolher",
"Comment menu": "Menu de comentários",
"Group menu": "Menu do grupo",
"Show hidden breadcrumbs": "Mostrar breadcrumbs ocultos",
"Breadcrumbs": "Trilhas de navegação",
"Page actions": "Ações da página",
"Pick emoji": "Escolher emoji",
"Template menu": "Menu do modelo",
"Use": "Usar",
"Use template": "Usar modelo",
"Preview template: {{title}}": "Visualizar modelo: {{title}}",
"Use a template": "Usar um modelo",
"Search templates...": "Pesquisar modelos...",
"Search spaces...": "Pesquisar espaços...",
"No templates found": "Nenhum modelo encontrado",
"No spaces found": "Nenhum espaço encontrado",
"Browse all templates": "Ver todos os modelos",
"This space": "Este espaço",
"All templates": "Todos os modelos",
"Global": "Global",
"New template": "Novo modelo",
"Edit template": "Editar modelo",
"Are you sure you want to delete this template?": "Tem certeza de que deseja excluir este modelo?",
"Template scope updated": "Escopo do modelo atualizado",
"Choose which space this template belongs to": "Escolha a qual espaço este modelo pertence",
"Scope": "Escopo",
"Select scope": "Selecionar escopo",
"Title": "Título",
"Saving...": "Salvando...",
"Saved": "Salvo",
"Save failed. Retry": "Falha ao salvar. Tentar novamente",
"By {{name}}": "Por {{name}}",
"Updated {{time}}": "Atualizado {{time}}",
"Choose destination": "Escolher destino",
"Search pages and spaces...": "Pesquisar páginas e espaços...",
"No results found": "Nenhum resultado encontrado",
"You don't have permission to create pages here": "Você não tem permissão para criar páginas aqui",
"Chat menu": "Menu do chat",
"API key menu": "Menu da chave de API",
"Jump to comment selection": "Ir para a seleção de comentários",
"Slash commands": "Comandos de barra",
"Mention suggestions": "Sugestões de menção",
"Link suggestions": "Sugestões de links",
"Diagram editor": "Editor de diagramas",
"Add comment": "Adicionar comentário",
"Find and replace": "Localizar e substituir",
"Main navigation": "Navegação principal",
"Space navigation": "Navegação do espaço",
"Settings navigation": "Navegação de configurações",
"AI navigation": "Navegação de IA",
"Breadcrumb": "Trilha de navegação",
"Synced block": "Bloco sincronizado",
"Create a block that stays in sync across pages.": "Crie um bloco que permaneça sincronizado entre páginas.",
"Editing original": "Editando original",
"Copy synced block": "Copiar bloco sincronizado",
"Unsync": "Desfazer sincronização",
"Delete synced block": "Excluir bloco sincronizado",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "ORIGINAL",
"THIS PAGE": "ESTA PÁGINA",
"No pages": "Nenhuma página",
"The original synced block no longer exists": "O bloco sincronizado original não existe mais",
"You don't have access to this synced block": "Você não tem acesso a este bloco sincronizado",
"Failed to load this synced block": "Falha ao carregar este bloco sincronizado",
"Fixed editor toolbar": "Barra de ferramentas fixa do editor",
"Show a formatting toolbar above the editor with quick access to common actions.": "Mostre uma barra de ferramentas de formatação acima do editor com acesso rápido a ações comuns.",
"Toggle fixed editor toolbar": "Alternar barra de ferramentas fixa do editor",
"Normal text": "Texto normal",
"More inline formatting": "Mais formatação em linha",
"Subscript": "Subscrito",
"Superscript": "Sobrescrito",
"Inline code": "Código em linha",
"Insert media": "Inserir mídia",
"Mention": "Menção",
"Emoji": "Emoji",
"Columns": "Colunas",
"More inserts": "Mais inserções",
"Embeds": "Incorporações",
"Diagrams": "Diagramas",
"Advanced": "Avançado",
"Utility": "Utilitário",
"Decrease indent": "Diminuir recuo",
"Increase indent": "Aumentar recuo",
"Clear formatting": "Limpar formatação",
"Code block": "Bloco de código",
"Experimental": "Experimental",
"Strikethrough": "Tachado",
"Undo": "Desfazer",
"Redo": "Refazer",
"Backlinks": "Links de retorno",
"Last updated by": "Última atualização por",
"Last updated": "Última atualização",
"Stats": "Estatísticas",
"Word count": "Contagem de palavras",
"Characters": "Caracteres",
"Incoming links": "Links recebidos",
"Outgoing links": "Links de saída",
"Incoming links ({{count}})": "Links recebidos ({{count}})",
"Outgoing links ({{count}})": "Links de saída ({{count}})",
"No pages link here yet.": "Nenhuma página tem link para cá ainda.",
"This page doesn't link to other pages yet.": "Esta página ainda não tem links para outras páginas.",
"Verified until {{date}}": "Verificado até {{date}}",
"Labels": "Rótulos",
"Add label": "Adicionar rótulo",
"No labels yet": "Ainda não há rótulos",
"Already added": "Já adicionado",
"Invalid label name": "Nome de rótulo inválido",
"No matches": "Sem correspondências",
"Search or create…": "Pesquisar ou criar…",
"Remove label {{name}}": "Remover rótulo {{name}}",
"Failed to add label": "Falha ao adicionar rótulo",
"Failed to remove label": "Falha ao remover rótulo",
"No pages with this label": "Nenhuma página com este rótulo",
"Pages tagged with this label will appear here.": "As páginas marcadas com este rótulo aparecerão aqui.",
"No pages match your search.": "Nenhuma página corresponde à sua pesquisa.",
"Updated {{date}}": "Atualizado em {{date}}",
"Cell actions": "Ações da célula",
"Column actions": "Ações da coluna",
"Row actions": "Ações da linha",
"Filter": "Filtrar",
"Page title": "Título da página",
"Page content": "Conteúdo da página",
"Member actions": "Ações do membro",
"Toggle password visibility": "Alternar visibilidade da senha",
"Send comment": "Enviar comentário",
"Token actions": "Ações do token",
"Template settings": "Configurações do modelo",
"Edit diagram": "Editar diagrama",
"Edit embed": "Editar incorporação",
"Edit drawing": "Editar desenho",
"Delete equation": "Excluir equação",
"Invite actions": "Ações do convite",
"Get started": "Começar",
"* indicates required fields": "* indica campos obrigatórios",
"List of spaces in this workspace": "Lista de espaços neste workspace",
"Active sessions": "Sessões ativas",
"Add {{name}} to favorites": "Adicionar {{name}} aos favoritos",
"Remove {{name}} from favorites": "Remover {{name}} dos favoritos",
"Added to favorites": "Adicionado aos favoritos",
"Removed from favorites": "Removido dos favoritos",
"Added {{name}} to favorites": "{{name}} adicionado aos favoritos",
"Removed {{name}} from favorites": "{{name}} removido dos favoritos",
"Page menu for {{name}}": "Menu da página de {{name}}",
"Create subpage of {{name}}": "Criar subpágina de {{name}}"
} }
@@ -71,6 +71,7 @@
"Export": "Экспорт", "Export": "Экспорт",
"Failed to create page": "Не удалось создать страницу", "Failed to create page": "Не удалось создать страницу",
"Failed to delete page": "Не удалось удалить страницу", "Failed to delete page": "Не удалось удалить страницу",
"Failed to restore page": "Не удалось восстановить страницу",
"Failed to fetch recent pages": "Не удалось получить недавние страницы", "Failed to fetch recent pages": "Не удалось получить недавние страницы",
"Failed to import pages": "Не удалось импортировать страницы", "Failed to import pages": "Не удалось импортировать страницы",
"Failed to load page. An error occurred.": "Не удалось загрузить страницу. Произошла ошибка.", "Failed to load page. An error occurred.": "Не удалось загрузить страницу. Произошла ошибка.",
@@ -276,6 +277,9 @@
"Align left": "По левому краю", "Align left": "По левому краю",
"Align right": "По правому краю", "Align right": "По правому краю",
"Align center": "По центру", "Align center": "По центру",
"Alt text": "Альтернативный текст",
"Describe this for accessibility.": "Опишите это для специальных возможностей.",
"Add a description": "Добавить описание",
"Justify": "По ширине", "Justify": "По ширине",
"Merge cells": "Объединить ячейки", "Merge cells": "Объединить ячейки",
"Split cell": "Разделить ячейку", "Split cell": "Разделить ячейку",
@@ -286,6 +290,19 @@
"Add row above": "Добавить строку выше", "Add row above": "Добавить строку выше",
"Add row below": "Добавить строку ниже", "Add row below": "Добавить строку ниже",
"Delete table": "Удалить таблицу", "Delete table": "Удалить таблицу",
"Add column left": "Добавить столбец слева",
"Add column right": "Добавить столбец справа",
"Clear cell": "Очистить ячейку",
"Clear cells": "Очистить ячейки",
"Toggle header cell": "Переключить ячейку заголовка",
"Toggle header column": "Переключить столбец заголовка",
"Toggle header row": "Переключить строку заголовка",
"Move column left": "Переместить столбец влево",
"Move column right": "Переместить столбец вправо",
"Move row down": "Переместить строку вниз",
"Move row up": "Переместить строку вверх",
"Sort A → Z": "Сортировать A → Я",
"Sort Z → A": "Сортировать Я → A",
"Info": "Информация", "Info": "Информация",
"Note": "Примечание", "Note": "Примечание",
"Success": "Успешно", "Success": "Успешно",
@@ -348,6 +365,8 @@
"Create block quote.": "Создать блок цитирования.", "Create block quote.": "Создать блок цитирования.",
"Insert code snippet.": "Вставить фрагмент кода.", "Insert code snippet.": "Вставить фрагмент кода.",
"Insert horizontal rule divider": "Вставить горизонтальный разделитель", "Insert horizontal rule divider": "Вставить горизонтальный разделитель",
"Page break": "Разрыв страницы",
"Insert a page break for printing.": "Вставить разрыв страницы для печати.",
"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 audio from your device.": "Загрузите любой аудиофайл с вашего устройства.", "Upload any audio from your device.": "Загрузите любой аудиофайл с вашего устройства.",
@@ -392,6 +411,10 @@
"Write...": "Напишите...", "Write...": "Напишите...",
"Column count": "Количество столбцов", "Column count": "Количество столбцов",
"{{count}} Columns": "{count, plural, one{# столбец} few{# столбца} many{# столбцов} other{# столбца}}", "{{count}} Columns": "{count, plural, one{# столбец} few{# столбца} many{# столбцов} other{# столбца}}",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Равные столбцы", "Equal columns": "Равные столбцы",
"Left sidebar": "Левая боковая панель", "Left sidebar": "Левая боковая панель",
"Right sidebar": "Правая боковая панель", "Right sidebar": "Правая боковая панель",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "Доступна версия {{latestVersion}}", "{{latestVersion}} is available": "Доступна версия {{latestVersion}}",
"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.": "Выберите предпочитаемый режим редактирования страницы. Избегайте случайных изменений.",
"Choose {{format}} file": "Выберите файл {{format}}",
"Reading": "Чтение", "Reading": "Чтение",
"Delete member": "Удалить участника", "Delete member": "Удалить участника",
"Member deleted successfully": "Участник успешно удалён", "Member deleted successfully": "Участник успешно удалён",
@@ -565,6 +589,8 @@
"Move to trash": "Переместить в корзину", "Move to trash": "Переместить в корзину",
"Move this page to trash?": "Переместить эту страницу в корзину?", "Move this page to trash?": "Переместить эту страницу в корзину?",
"Restore page": "Восстановить страницу", "Restore page": "Восстановить страницу",
"Permanently delete": "Удалить навсегда",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> переместил(а) эту страницу в корзину {{time}}.",
"Page moved to trash": "Страница перемещена в корзину", "Page moved to trash": "Страница перемещена в корзину",
"Page restored successfully": "Страница успешно восстановлена", "Page restored successfully": "Страница успешно восстановлена",
"Deleted by": "Удалено пользователем", "Deleted by": "Удалено пользователем",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "Изображение превышает предел 10MB.", "Image exceeds 10MB limit.": "Изображение превышает предел 10MB.",
"Image removed successfully": "Изображение успешно удалено", "Image removed successfully": "Изображение успешно удалено",
"API key": "API ключ", "API key": "API ключ",
"API key created successfully": "API ключ успешно создан",
"API keys": "API ключи", "API keys": "API ключи",
"API management": "Управление API", "API management": "Управление API",
"Are you sure you want to revoke this API key": "Вы уверены, что хотите отозвать этот API ключ",
"Create API Key": "Создать API ключ",
"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": "Я сохранил мой API ключ",
"Last use": "Последнее использование", "Last use": "Последнее использование",
"No API keys found": "API ключи не найдены", "No API keys found": "API ключи не найдены",
"No expiration": "Не истекает", "No expiration": "Не истекает",
"Revoke API key": "Отозвать API ключ",
"Revoked successfully": "Отозван успешно", "Revoked successfully": "Отозван успешно",
"Select expiration date": "Выберете срок действия", "Select expiration date": "Выберете срок действия",
"This action cannot be undone. Any applications using this API key will stop working.": "Это действие необратимо. Любые приложения, использующие этот API ключ, перестанут работать.", "This action cannot be undone. Any applications using this API key will stop working.": "Это действие необратимо. Любые приложения, использующие этот API ключ, перестанут работать.",
"Update API key": "Обновить API ключ", "Update": "Обновить",
"Update {{credential}}": "Обновить {{credential}}",
"Manage API keys for all users in the workspace": "Управлять API ключами для всех пользователей в рабочей области", "Manage API keys for all users in the workspace": "Управлять API ключами для всех пользователей в рабочей области",
"Restrict API key creation to admins": "Ограничить создание API-ключей только администраторами.", "Restrict API key creation to admins": "Ограничить создание API-ключей только администраторами.",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Только администраторы и владельцы могут создавать новые API-ключи. Существующие ключи участников продолжат работать.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Только администраторы и владельцы могут создавать новые API-ключи. Существующие ключи участников продолжат работать.",
@@ -858,9 +880,12 @@
"AI Chat": "Чат с ИИ", "AI Chat": "Чат с ИИ",
"Analyze for insights": "Проанализировать и получить выводы", "Analyze for insights": "Проанализировать и получить выводы",
"Ask anything...": "Спросите что угодно...", "Ask anything...": "Спросите что угодно...",
"Assistant said:": "Ассистент ответил:",
"Chat history": "История чатов", "Chat history": "История чатов",
"Chat name": "Название чата", "Chat name": "Название чата",
"Chat transcript": "Расшифровка чата",
"Close": "Закрыть", "Close": "Закрыть",
"Copy assistant response": "Скопировать ответ ассистента",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "Не удалось загрузить чат. Произошла ошибка.", "Failed to load chat. An error occurred.": "Не удалось загрузить чат. Произошла ошибка.",
"Failed to render this message.": "Не удалось отобразить это сообщение.", "Failed to render this message.": "Не удалось отобразить это сообщение.",
@@ -870,9 +895,17 @@
"No chats found": "Чаты не найдены", "No chats found": "Чаты не найдены",
"No conversations yet": "Пока нет разговоров", "No conversations yet": "Пока нет разговоров",
"Open full page": "Открыть полную страницу", "Open full page": "Открыть полную страницу",
"Scroll to bottom": "Прокрутить вниз",
"You said:": "Вы сказали:",
"Previous 7 days": "Предыдущие 7 дней", "Previous 7 days": "Предыдущие 7 дней",
"Previous 30 days": "Предыдущие 30 дней", "Previous 30 days": "Предыдущие 30 дней",
"Search chats...": "Поиск чатов...", "Search chats...": "Поиск чатов...",
"Search chats": "Поиск чатов",
"Ask anything... Use @ to mention pages": "Спросите что угодно... Используйте @, чтобы упомянуть страницы",
"Ask anything or search your workspace": "Спросите что угодно или выполните поиск по рабочему пространству",
"Welcome to {{name}}": "Добро пожаловать в {{name}}",
"Add files": "Добавить файлы",
"Mention a page": "Упомянуть страницу",
"Start a new chat to see it here.": "Начните новый чат, чтобы увидеть его здесь.", "Start a new chat to see it here.": "Начните новый чат, чтобы увидеть его здесь.",
"Summarize this page": "Суммировать эту страницу", "Summarize this page": "Суммировать эту страницу",
"Toggle AI Chat": "Переключить чат с ИИ", "Toggle AI Chat": "Переключить чат с ИИ",
@@ -880,5 +913,176 @@
"Try a different search term.": "Попробуйте другой поисковый запрос.", "Try a different search term.": "Попробуйте другой поисковый запрос.",
"Try again": "Попробовать снова", "Try again": "Попробовать снова",
"Untitled chat": "Чат без названия", "Untitled chat": "Чат без названия",
"What can I help you with?": "Чем я могу вам помочь?" "What can I help you with?": "Чем я могу вам помочь?",
"Are you sure you want to revoke this {{credential}}": "Вы уверены, что хотите отозвать этот {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Автоматически предоставляйте доступ пользователям и группам из вашего провайдера удостоверений через SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Настройте ваш провайдер удостоверений с этим URL для предоставления доступа пользователям и группам.",
"Create {{credential}}": "Создать {{credential}}",
"{{credential}} created": "{{credential}} создан",
"{{credential}} created successfully": "{{credential}} успешно создан",
"Created by": "Создан",
"Custom": "Пользовательский",
"Enable SCIM": "Включить SCIM",
"Enter a descriptive name": "Введите понятное имя",
"I've saved my {{credential}}": "Я сохранил свой {{credential}}",
"Important": "Важно",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Обязательно скопируйте ваш {{credential}} сейчас. Позже вы не сможете увидеть его снова!",
"Never": "Никогда",
"Revoke {{credential}}": "Отозвать {{credential}}",
"SCIM endpoint URL": "URL конечной точки SCIM",
"SCIM provisioning": "SCIM-подготовка учетных записей",
"SCIM takes precedence over SSO group sync while enabled.": "Пока SCIM включен, он имеет приоритет над синхронизацией групп через SSO.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Вы достигли максимального количества токенов SCIM: {{max}}. Удалите существующий токен, чтобы создать новый.",
"SCIM token": "Токен SCIM",
"SCIM tokens": "Токены SCIM",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Это действие нельзя отменить. Ваш провайдер удостоверений немедленно прекратит синхронизацию.",
"Toggle SCIM provisioning": "Переключить подготовку учетных записей SCIM",
"Token": "Токен",
"Page menu": "Меню страницы",
"Expand": "Развернуть",
"Collapse": "Свернуть",
"Comment menu": "Меню комментария",
"Group menu": "Меню группы",
"Show hidden breadcrumbs": "Показать скрытые хлебные крошки",
"Breadcrumbs": "Хлебные крошки",
"Page actions": "Действия со страницей",
"Pick emoji": "Выбрать эмодзи",
"Template menu": "Меню шаблона",
"Use": "Использовать",
"Use template": "Использовать шаблон",
"Preview template: {{title}}": "Предпросмотр шаблона: {{title}}",
"Use a template": "Использовать шаблон",
"Search templates...": "Поиск шаблонов...",
"Search spaces...": "Поиск пространств...",
"No templates found": "Шаблоны не найдены",
"No spaces found": "Пространства не найдены",
"Browse all templates": "Просмотреть все шаблоны",
"This space": "Это пространство",
"All templates": "Все шаблоны",
"Global": "Глобально",
"New template": "Новый шаблон",
"Edit template": "Редактировать шаблон",
"Are you sure you want to delete this template?": "Вы уверены, что хотите удалить этот шаблон?",
"Template scope updated": "Область действия шаблона обновлена",
"Choose which space this template belongs to": "Выберите, к какому пространству относится этот шаблон",
"Scope": "Область действия",
"Select scope": "Выберите область действия",
"Title": "Заголовок",
"Saving...": "Сохранение...",
"Saved": "Сохранено",
"Save failed. Retry": "Не удалось сохранить. Повторите попытку",
"By {{name}}": "От {{name}}",
"Updated {{time}}": "Обновлено {{time}}",
"Choose destination": "Выберите место назначения",
"Search pages and spaces...": "Поиск страниц и пространств...",
"No results found": "Результаты не найдены",
"You don't have permission to create pages here": "У вас нет прав на создание страниц здесь",
"Chat menu": "Меню чата",
"API key menu": "Меню API-ключа",
"Jump to comment selection": "Перейти к выбору комментария",
"Slash commands": "Команды со слешем",
"Mention suggestions": "Подсказки упоминаний",
"Link suggestions": "Подсказки ссылок",
"Diagram editor": "Редактор диаграмм",
"Add comment": "Добавить комментарий",
"Find and replace": "Найти и заменить",
"Main navigation": "Основная навигация",
"Space navigation": "Навигация по пространству",
"Settings navigation": "Навигация по настройкам",
"AI navigation": "Навигация ИИ",
"Breadcrumb": "Хлебная крошка",
"Synced block": "Синхронизированный блок",
"Create a block that stays in sync across pages.": "Создайте блок, который будет синхронизироваться между страницами.",
"Editing original": "Редактирование оригинала",
"Copy synced block": "Скопировать синхронизированный блок",
"Unsync": "Не синхронизировать",
"Delete synced block": "Удалить синхронизированный блок",
"Synced to {{count}} other page_one": "Синхронизировано с {{count}} с другой страницей",
"Synced to {{count}} other page_other": "Синхронизировано с {{count}} с другими страницами",
"ORIGINAL": "ОРИГИНАЛ",
"THIS PAGE": "ЭТА СТРАНИЦА",
"No pages": "Нет страниц",
"The original synced block no longer exists": "Исходный синхронизированный блок больше не существует",
"You don't have access to this synced block": "У вас нет доступа к этому синхронизированному блоку",
"Failed to load this synced block": "Не удалось загрузить этот синхронизированный блок",
"Fixed editor toolbar": "Закреплённая панель инструментов редактора",
"Show a formatting toolbar above the editor with quick access to common actions.": "Показывать панель форматирования над редактором для быстрого доступа к часто используемым действиям.",
"Toggle fixed editor toolbar": "Переключить закреплённую панель инструментов редактора",
"Normal text": "Обычный текст",
"More inline formatting": "Больше вариантов встроенного форматирования",
"Subscript": "Подстрочный",
"Superscript": "Надстрочный",
"Inline code": "Встроенный код",
"Insert media": "Вставить медиа",
"Mention": "Упоминание",
"Emoji": "Эмодзи",
"Columns": "Столбцы",
"More inserts": "Больше вариантов вставки",
"Embeds": "Встраивания",
"Diagrams": "Диаграммы",
"Advanced": "Дополнительно",
"Utility": "Служебное",
"Decrease indent": "Уменьшить отступ",
"Increase indent": "Увеличить отступ",
"Clear formatting": "Очистить форматирование",
"Code block": "Блок кода",
"Experimental": "Экспериментальное",
"Strikethrough": "Зачеркивание",
"Undo": "Отменить",
"Redo": "Повторить",
"Backlinks": "Обратные ссылки",
"Last updated by": "Последний изменивший",
"Last updated": "Последнее обновление",
"Stats": "Статистика",
"Word count": "Количество слов",
"Characters": "Символы",
"Incoming links": "Входящие ссылки",
"Outgoing links": "Исходящие ссылки",
"Incoming links ({{count}})": "Входящие ссылки ({{count}})",
"Outgoing links ({{count}})": "Исходящие ссылки ({{count}})",
"No pages link here yet.": "На эту страницу пока что нет ссылок.",
"This page doesn't link to other pages yet.": "Эта страница пока не содержит ссылок на другие страницы.",
"Verified until {{date}}": "Подтверждено до: {{date}}",
"Labels": "Метки",
"Add label": "Добавить метку",
"No labels yet": "Меток пока нет",
"Already added": "Уже добавлено",
"Invalid label name": "Недопустимое имя метки",
"No matches": "Совпадений нет",
"Search or create…": "Найти или создать…",
"Remove label {{name}}": "Удалить метку {{name}}",
"Failed to add label": "Не удалось добавить метку",
"Failed to remove label": "Не удалось удалить метку",
"No pages with this label": "Нет страниц с этой меткой",
"Pages tagged with this label will appear here.": "Здесь будут отображаться страницы с этой меткой.",
"No pages match your search.": "Нет страниц, соответствующих вашему запросу.",
"Updated {{date}}": "Обновлено {{date}}",
"Cell actions": "Действия с ячейкой",
"Column actions": "Действия со столбцом",
"Row actions": "Действия со строкой",
"Filter": "Фильтр",
"Page title": "Заголовок страницы",
"Page content": "Содержимое страницы",
"Member actions": "Действия с участником",
"Toggle password visibility": "Переключить видимость пароля",
"Send comment": "Отправить комментарий",
"Token actions": "Действия с токеном",
"Template settings": "Настройки шаблона",
"Edit diagram": "Редактировать диаграмму",
"Edit embed": "Редактировать встраивание",
"Edit drawing": "Редактировать рисунок",
"Delete equation": "Удалить уравнение",
"Invite actions": "Действия с приглашением",
"Get started": "Начать",
"* indicates required fields": "* обозначает обязательные поля",
"List of spaces in this workspace": "Список пространств в этом рабочем пространстве",
"Active sessions": "Активные сеансы",
"Add {{name}} to favorites": "Добавить {{name}} в избранное",
"Remove {{name}} from favorites": "Удалить {{name}} из избранного",
"Added to favorites": "Добавлено в избранное",
"Removed from favorites": "Удалено из избранного",
"Added {{name}} to favorites": "{{name}} добавлено в избранное",
"Removed {{name}} from favorites": "{{name}} удалено из избранного",
"Page menu for {{name}}": "Меню страницы для {{name}}",
"Create subpage of {{name}}": "Создать подстраницу для {{name}}"
} }
@@ -71,6 +71,7 @@
"Export": "Експорт", "Export": "Експорт",
"Failed to create page": "Не вдалося створити сторінку", "Failed to create page": "Не вдалося створити сторінку",
"Failed to delete page": "Не вдалося видалити сторінку", "Failed to delete page": "Не вдалося видалити сторінку",
"Failed to restore page": "Не вдалося відновити сторінку",
"Failed to fetch recent pages": "Не вдалося отримати нещодавні сторінки", "Failed to fetch recent pages": "Не вдалося отримати нещодавні сторінки",
"Failed to import pages": "Не вдалося імпортувати сторінки", "Failed to import pages": "Не вдалося імпортувати сторінки",
"Failed to load page. An error occurred.": "Не вдалося завантажити сторінку. Сталася помилка.", "Failed to load page. An error occurred.": "Не вдалося завантажити сторінку. Сталася помилка.",
@@ -276,6 +277,9 @@
"Align left": "По лівому краю", "Align left": "По лівому краю",
"Align right": "По правому краю", "Align right": "По правому краю",
"Align center": "По центру", "Align center": "По центру",
"Alt text": "Альтернативний текст",
"Describe this for accessibility.": "Опишіть це для доступності.",
"Add a description": "Додати опис",
"Justify": "По ширині", "Justify": "По ширині",
"Merge cells": "Об'єднати комірки", "Merge cells": "Об'єднати комірки",
"Split cell": "Розділити комірку", "Split cell": "Розділити комірку",
@@ -286,6 +290,19 @@
"Add row above": "Додати рядок вище", "Add row above": "Додати рядок вище",
"Add row below": "Додати рядок нижче", "Add row below": "Додати рядок нижче",
"Delete table": "Видалити таблицю", "Delete table": "Видалити таблицю",
"Add column left": "Додати стовпець ліворуч",
"Add column right": "Додати стовпець праворуч",
"Clear cell": "Очистити комірку",
"Clear cells": "Очистити комірки",
"Toggle header cell": "Перемкнути комірку заголовка",
"Toggle header column": "Перемкнути стовпець заголовка",
"Toggle header row": "Перемкнути рядок заголовка",
"Move column left": "Перемістити стовпець ліворуч",
"Move column right": "Перемістити стовпець праворуч",
"Move row down": "Перемістити рядок вниз",
"Move row up": "Перемістити рядок вгору",
"Sort A → Z": "Сортувати A → Z",
"Sort Z → A": "Сортувати Z → A",
"Info": "Інформація", "Info": "Інформація",
"Note": "Примітка", "Note": "Примітка",
"Success": "Успішно", "Success": "Успішно",
@@ -348,6 +365,8 @@
"Create block quote.": "Створити блок цитування.", "Create block quote.": "Створити блок цитування.",
"Insert code snippet.": "Вставити фрагмент коду.", "Insert code snippet.": "Вставити фрагмент коду.",
"Insert horizontal rule divider": "Вставити горизонтальний роздільник", "Insert horizontal rule divider": "Вставити горизонтальний роздільник",
"Page break": "Розрив сторінки",
"Insert a page break for printing.": "Вставте розрив сторінки для друку.",
"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 audio from your device.": "Завантажте будь-який аудіофайл зі свого пристрою.", "Upload any audio from your device.": "Завантажте будь-який аудіофайл зі свого пристрою.",
@@ -392,6 +411,10 @@
"Write...": "Напишіть...", "Write...": "Напишіть...",
"Column count": "Кількість колонок", "Column count": "Кількість колонок",
"{{count}} Columns": "{count, plural, one{# колонка} few{# колонки} many{# колонок} other{# колонки}}", "{{count}} Columns": "{count, plural, one{# колонка} few{# колонки} many{# колонок} other{# колонки}}",
"{{count}} command available_one": "1 command available",
"{{count}} command available_other": "{{count}} commands available",
"{{count}} result available_one": "1 result available",
"{{count}} result available_other": "{{count}} results available",
"Equal columns": "Рівні колонки", "Equal columns": "Рівні колонки",
"Left sidebar": "Ліва бічна панель", "Left sidebar": "Ліва бічна панель",
"Right sidebar": "Права бічна панель", "Right sidebar": "Права бічна панель",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "Доступна версія {{latestVersion}}", "{{latestVersion}} is available": "Доступна версія {{latestVersion}}",
"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.": "Виберіть бажаний режим редагування сторінки. Уникайте випадкових редагувань.",
"Choose {{format}} file": "Виберіть файл {{format}}",
"Reading": "Читання", "Reading": "Читання",
"Delete member": "Видалити учасника", "Delete member": "Видалити учасника",
"Member deleted successfully": "Учасника успішно видалено", "Member deleted successfully": "Учасника успішно видалено",
@@ -565,6 +589,8 @@
"Move to trash": "Перемістити в кошик", "Move to trash": "Перемістити в кошик",
"Move this page to trash?": "Перемістити цю сторінку до кошика?", "Move this page to trash?": "Перемістити цю сторінку до кошика?",
"Restore page": "Відновити сторінку", "Restore page": "Відновити сторінку",
"Permanently delete": "Видалити назавжди",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> перемістив цю сторінку до кошика {{time}}.",
"Page moved to trash": "Сторінку переміщено в кошик", "Page moved to trash": "Сторінку переміщено в кошик",
"Page restored successfully": "Сторінку успішно відновлено", "Page restored successfully": "Сторінку успішно відновлено",
"Deleted by": "Видалив", "Deleted by": "Видалив",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "Зображення має займати менше, ніж 10 МБ.", "Image exceeds 10MB limit.": "Зображення має займати менше, ніж 10 МБ.",
"Image removed successfully": "Зображення видалено", "Image removed successfully": "Зображення видалено",
"API key": "Ключ API", "API key": "Ключ API",
"API key created successfully": "Ключ API успішно створено",
"API keys": "Ключі API", "API keys": "Ключі API",
"API management": "Управління API", "API management": "Управління API",
"Are you sure you want to revoke this API key": "Ви впевнені, що хочете відкликати цей ключ API",
"Create API Key": "Створити ключ API",
"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": "Я зберіг свій ключ API",
"Last use": "Останнє використання", "Last use": "Останнє використання",
"No API keys found": "Ключі API не знайдено", "No API keys found": "Ключі API не знайдено",
"No expiration": "Без терміну дії", "No expiration": "Без терміну дії",
"Revoke API key": "Відкликати ключ API",
"Revoked successfully": "Успішно відкликано", "Revoked successfully": "Успішно відкликано",
"Select expiration date": "Виберіть дату закінчення", "Select expiration date": "Виберіть дату закінчення",
"This action cannot be undone. Any applications using this API key will stop working.": "Цю дію не можна скасувати. Будь-які додатки, що використовують цей ключ API, перестануть працювати.", "This action cannot be undone. Any applications using this API key will stop working.": "Цю дію не можна скасувати. Будь-які додатки, що використовують цей ключ API, перестануть працювати.",
"Update API key": "Оновити ключ API", "Update": "Оновити",
"Update {{credential}}": "Оновити {{credential}}",
"Manage API keys for all users in the workspace": "Керувати ключами API для всіх користувачів у робочій області", "Manage API keys for all users in the workspace": "Керувати ключами API для всіх користувачів у робочій області",
"Restrict API key creation to admins": "Обмежити створення API-ключів лише для адміністраторів", "Restrict API key creation to admins": "Обмежити створення API-ключів лише для адміністраторів",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "Тільки адміністратори та власники можуть створювати нові API-ключі. Існуючі ключі учасників і надалі працюватимуть.", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "Тільки адміністратори та власники можуть створювати нові API-ключі. Існуючі ключі учасників і надалі працюватимуть.",
@@ -858,9 +880,12 @@
"AI Chat": "AI-чат", "AI Chat": "AI-чат",
"Analyze for insights": "Проаналізувати для отримання висновків", "Analyze for insights": "Проаналізувати для отримання висновків",
"Ask anything...": "Запитайте що завгодно...", "Ask anything...": "Запитайте що завгодно...",
"Assistant said:": "Помічник сказав:",
"Chat history": "Історія чатів", "Chat history": "Історія чатів",
"Chat name": "Назва чату", "Chat name": "Назва чату",
"Chat transcript": "Стенограма чату",
"Close": "Закрити", "Close": "Закрити",
"Copy assistant response": "Копіювати відповідь помічника",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "Не вдалося завантажити чат. Сталася помилка.", "Failed to load chat. An error occurred.": "Не вдалося завантажити чат. Сталася помилка.",
"Failed to render this message.": "Не вдалося відобразити це повідомлення.", "Failed to render this message.": "Не вдалося відобразити це повідомлення.",
@@ -870,9 +895,17 @@
"No chats found": "Чатів не знайдено", "No chats found": "Чатів не знайдено",
"No conversations yet": "Розмов поки немає", "No conversations yet": "Розмов поки немає",
"Open full page": "Відкрити повну сторінку", "Open full page": "Відкрити повну сторінку",
"Scroll to bottom": "Прокрутити вниз",
"You said:": "Ви сказали:",
"Previous 7 days": "Попередні 7 днів", "Previous 7 days": "Попередні 7 днів",
"Previous 30 days": "Попередні 30 днів", "Previous 30 days": "Попередні 30 днів",
"Search chats...": "Шукати чати...", "Search chats...": "Шукати чати...",
"Search chats": "Шукати чати",
"Ask anything... Use @ to mention pages": "Запитайте будь-що... Використовуйте @, щоб згадувати сторінки",
"Ask anything or search your workspace": "Запитайте будь-що або шукайте у своєму робочому просторі",
"Welcome to {{name}}": "Ласкаво просимо до {{name}}",
"Add files": "Додати файли",
"Mention a page": "Згадати сторінку",
"Start a new chat to see it here.": "Почніть новий чат, щоб побачити його тут.", "Start a new chat to see it here.": "Почніть новий чат, щоб побачити його тут.",
"Summarize this page": "Підсумувати цю сторінку", "Summarize this page": "Підсумувати цю сторінку",
"Toggle AI Chat": "Перемкнути AI-чат", "Toggle AI Chat": "Перемкнути AI-чат",
@@ -880,5 +913,176 @@
"Try a different search term.": "Спробуйте інший пошуковий запит.", "Try a different search term.": "Спробуйте інший пошуковий запит.",
"Try again": "Спробувати ще раз", "Try again": "Спробувати ще раз",
"Untitled chat": "Чат без назви", "Untitled chat": "Чат без назви",
"What can I help you with?": "Чим я можу вам допомогти?" "What can I help you with?": "Чим я можу вам допомогти?",
"Are you sure you want to revoke this {{credential}}": "Ви впевнені, що хочете відкликати цей {{credential}}",
"Automatically provision users and groups from your identity provider via SCIM.": "Автоматично надавайте користувачів і групи від вашого постачальника ідентифікації через SCIM.",
"Configure your identity provider with this URL to provision users and groups.": "Налаштуйте свого постачальника ідентифікації за допомогою цієї URL-адреси для надання користувачів і груп.",
"Create {{credential}}": "Створити {{credential}}",
"{{credential}} created": "{{credential}} створено",
"{{credential}} created successfully": "{{credential}} успішно створено",
"Created by": "Створено",
"Custom": "Користувацький",
"Enable SCIM": "Увімкнути SCIM",
"Enter a descriptive name": "Введіть описову назву",
"I've saved my {{credential}}": "Я зберіг(ла) свій {{credential}}",
"Important": "Важливо",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "Обов’язково скопіюйте свій {{credential}} зараз. Ви більше не зможете побачити його знову!",
"Never": "Ніколи",
"Revoke {{credential}}": "Відкликати {{credential}}",
"SCIM endpoint URL": "URL-адреса кінцевої точки SCIM",
"SCIM provisioning": "Надання SCIM",
"SCIM takes precedence over SSO group sync while enabled.": "SCIM має пріоритет над синхронізацією груп SSO, коли його ввімкнено.",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "Ви досягли максимальної кількості токенів SCIM: {{max}}. Видаліть наявний токен, щоб створити новий.",
"SCIM token": "Токен SCIM",
"SCIM tokens": "Токени SCIM",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "Цю дію не можна скасувати. Ваш постачальник ідентифікації негайно припинить синхронізацію.",
"Toggle SCIM provisioning": "Перемкнути надання SCIM",
"Token": "Токен",
"Page menu": "Меню сторінки",
"Expand": "Розгорнути",
"Collapse": "Згорнути",
"Comment menu": "Меню коментаря",
"Group menu": "Меню групи",
"Show hidden breadcrumbs": "Показати приховані \"хлібні крихти\"",
"Breadcrumbs": "\"Хлібні крихти\"",
"Page actions": "Дії сторінки",
"Pick emoji": "Вибрати емодзі",
"Template menu": "Меню шаблону",
"Use": "Використати",
"Use template": "Використати шаблон",
"Preview template: {{title}}": "Попередній перегляд шаблону: {{title}}",
"Use a template": "Використати шаблон",
"Search templates...": "Шукати шаблони...",
"Search spaces...": "Шукати простори...",
"No templates found": "Шаблони не знайдено",
"No spaces found": "Простори не знайдено",
"Browse all templates": "Переглянути всі шаблони",
"This space": "Цей простір",
"All templates": "Усі шаблони",
"Global": "Глобальний",
"New template": "Новий шаблон",
"Edit template": "Редагувати шаблон",
"Are you sure you want to delete this template?": "Ви впевнені, що хочете видалити цей шаблон?",
"Template scope updated": "Область дії шаблону оновлено",
"Choose which space this template belongs to": "Виберіть, до якого простору належить цей шаблон",
"Scope": "Область дії",
"Select scope": "Вибрати область дії",
"Title": "Назва",
"Saving...": "Збереження...",
"Saved": "Збережено",
"Save failed. Retry": "Не вдалося зберегти. Повторіть спробу",
"By {{name}}": "Від {{name}}",
"Updated {{time}}": "Оновлено {{time}}",
"Choose destination": "Вибрати місце призначення",
"Search pages and spaces...": "Шукати сторінки та простори...",
"No results found": "Результати не знайдено",
"You don't have permission to create pages here": "У вас немає дозволу на створення сторінок тут",
"Chat menu": "Меню чату",
"API key menu": "Меню ключа API",
"Jump to comment selection": "Перейти до вибору коментаря",
"Slash commands": "Слеш-команди",
"Mention suggestions": "Підказки згадок",
"Link suggestions": "Підказки посилань",
"Diagram editor": "Редактор діаграм",
"Add comment": "Додати коментар",
"Find and replace": "Знайти й замінити",
"Main navigation": "Основна навігація",
"Space navigation": "Навігація простору",
"Settings navigation": "Навігація налаштувань",
"AI navigation": "Навігація AI",
"Breadcrumb": "Хлібна крихта",
"Synced block": "Синхронізований блок",
"Create a block that stays in sync across pages.": "Створіть блок, який синхронізується між сторінками.",
"Editing original": "Редагування оригіналу",
"Copy synced block": "Скопіювати синхронізований блок",
"Unsync": "Скасувати синхронізацію",
"Delete synced block": "Видалити синхронізований блок",
"Synced to {{count}} other page_one": "Synced to {{count}} other page",
"Synced to {{count}} other page_other": "Synced to {{count}} other pages",
"ORIGINAL": "ОРИГІНАЛ",
"THIS PAGE": "ЦЯ СТОРІНКА",
"No pages": "Немає сторінок",
"The original synced block no longer exists": "Оригінальний синхронізований блок більше не існує",
"You don't have access to this synced block": "У вас немає доступу до цього синхронізованого блоку",
"Failed to load this synced block": "Не вдалося завантажити цей синхронізований блок",
"Fixed editor toolbar": "Закріплена панель інструментів редактора",
"Show a formatting toolbar above the editor with quick access to common actions.": "Показувати панель форматування над редактором для швидкого доступу до поширених дій.",
"Toggle fixed editor toolbar": "Перемкнути закріплену панель інструментів редактора",
"Normal text": "Звичайний текст",
"More inline formatting": "Більше вбудованого форматування",
"Subscript": "Нижній індекс",
"Superscript": "Верхній індекс",
"Inline code": "Вбудований код",
"Insert media": "Вставити медіа",
"Mention": "Згадка",
"Emoji": "Емодзі",
"Columns": "Стовпці",
"More inserts": "Більше вставок",
"Embeds": "Вбудовування",
"Diagrams": "Діаграми",
"Advanced": "Додатково",
"Utility": "Службові",
"Decrease indent": "Зменшити відступ",
"Increase indent": "Збільшити відступ",
"Clear formatting": "Очистити форматування",
"Code block": "Блок коду",
"Experimental": "Експериментальне",
"Strikethrough": "Закреслення",
"Undo": "Скасувати",
"Redo": "Повторити",
"Backlinks": "Зворотні посилання",
"Last updated by": "Востаннє оновив",
"Last updated": "Останнє оновлення",
"Stats": "Статистика",
"Word count": "Кількість слів",
"Characters": "Символи",
"Incoming links": "Вхідні посилання",
"Outgoing links": "Вихідні посилання",
"Incoming links ({{count}})": "Вхідні посилання ({{count}})",
"Outgoing links ({{count}})": "Вихідні посилання ({{count}})",
"No pages link here yet.": "Поки що жодна сторінка не посилається сюди.",
"This page doesn't link to other pages yet.": "Ця сторінка ще не містить посилань на інші сторінки.",
"Verified until {{date}}": "Перевірено до {{date}}",
"Labels": "Мітки",
"Add label": "Додати мітку",
"No labels yet": "Міток поки немає",
"Already added": "Уже додано",
"Invalid label name": "Некоректна назва мітки",
"No matches": "Немає збігів",
"Search or create…": "Шукати або створити…",
"Remove label {{name}}": "Видалити мітку {{name}}",
"Failed to add label": "Не вдалося додати мітку",
"Failed to remove label": "Не вдалося видалити мітку",
"No pages with this label": "Немає сторінок із цією міткою",
"Pages tagged with this label will appear here.": "Тут з’являться сторінки, позначені цією міткою.",
"No pages match your search.": "Немає сторінок, що відповідають вашому запиту.",
"Updated {{date}}": "Оновлено {{date}}",
"Cell actions": "Дії з коміркою",
"Column actions": "Дії зі стовпцем",
"Row actions": "Дії з рядком",
"Filter": "Фільтр",
"Page title": "Назва сторінки",
"Page content": "Вміст сторінки",
"Member actions": "Дії з учасником",
"Toggle password visibility": "Перемкнути видимість пароля",
"Send comment": "Надіслати коментар",
"Token actions": "Дії з токеном",
"Template settings": "Налаштування шаблону",
"Edit diagram": "Редагувати діаграму",
"Edit embed": "Редагувати вбудований елемент",
"Edit drawing": "Редагувати рисунок",
"Delete equation": "Видалити рівняння",
"Invite actions": "Дії із запрошенням",
"Get started": "Почати",
"* indicates required fields": "* позначає обов’язкові поля",
"List of spaces in this workspace": "Список просторів у цьому робочому просторі",
"Active sessions": "Активні сеанси",
"Add {{name}} to favorites": "Додати {{name}} до обраного",
"Remove {{name}} from favorites": "Видалити {{name}} з обраного",
"Added to favorites": "Додано до обраного",
"Removed from favorites": "Видалено з обраного",
"Added {{name}} to favorites": "{{name}} додано до обраного",
"Removed {{name}} from favorites": "{{name}} видалено з обраного",
"Page menu for {{name}}": "Меню сторінки для {{name}}",
"Create subpage of {{name}}": "Створити підсторінку для {{name}}"
} }
@@ -71,6 +71,7 @@
"Export": "导出", "Export": "导出",
"Failed to create page": "创建页面失败", "Failed to create page": "创建页面失败",
"Failed to delete page": "删除页面失败", "Failed to delete page": "删除页面失败",
"Failed to restore page": "恢复页面失败",
"Failed to fetch recent pages": "获取最近页面失败", "Failed to fetch recent pages": "获取最近页面失败",
"Failed to import pages": "导入页面失败", "Failed to import pages": "导入页面失败",
"Failed to load page. An error occurred.": "页面加载失败。发生了一个错误。", "Failed to load page. An error occurred.": "页面加载失败。发生了一个错误。",
@@ -276,6 +277,9 @@
"Align left": "靠左对齐", "Align left": "靠左对齐",
"Align right": "靠右对齐", "Align right": "靠右对齐",
"Align center": "居中对齐", "Align center": "居中对齐",
"Alt text": "替代文本",
"Describe this for accessibility.": "为无障碍访问添加描述。",
"Add a description": "添加描述",
"Justify": "两端对齐", "Justify": "两端对齐",
"Merge cells": "合并单元格", "Merge cells": "合并单元格",
"Split cell": "分割单元格", "Split cell": "分割单元格",
@@ -286,6 +290,19 @@
"Add row above": "在上方添加行", "Add row above": "在上方添加行",
"Add row below": "在下方插入行", "Add row below": "在下方插入行",
"Delete table": "删除表格", "Delete table": "删除表格",
"Add column left": "在左侧添加列",
"Add column right": "在右侧添加列",
"Clear cell": "清空单元格",
"Clear cells": "清空单元格",
"Toggle header cell": "切换标题单元格",
"Toggle header column": "切换标题列",
"Toggle header row": "切换标题行",
"Move column left": "左移列",
"Move column right": "右移列",
"Move row down": "下移行",
"Move row up": "上移行",
"Sort A → Z": "按 A → Z 排序",
"Sort Z → A": "按 Z → A 排序",
"Info": "信息", "Info": "信息",
"Note": "注意", "Note": "注意",
"Success": "成功", "Success": "成功",
@@ -348,6 +365,8 @@
"Create block quote.": "创建引用块", "Create block quote.": "创建引用块",
"Insert code snippet.": "插入代码片段", "Insert code snippet.": "插入代码片段",
"Insert horizontal rule divider": "插入水平分割线", "Insert horizontal rule divider": "插入水平分割线",
"Page break": "分页符",
"Insert a page break for printing.": "插入一个用于打印的分页符。",
"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 audio from your device.": "从您的设备上传任意音频文件。", "Upload any audio from your device.": "从您的设备上传任意音频文件。",
@@ -392,6 +411,10 @@
"Write...": "写点内容...", "Write...": "写点内容...",
"Column count": "列数", "Column count": "列数",
"{{count}} Columns": "{{count}} 列", "{{count}} Columns": "{{count}} 列",
"{{count}} command available_one": "有 1 个可用命令",
"{{count}} command available_other": "有 {{count}} 个可用命令",
"{{count}} result available_one": "有 1 个可用结果",
"{{count}} result available_other": "有 {{count}} 个可用结果",
"Equal columns": "等宽列", "Equal columns": "等宽列",
"Left sidebar": "左侧边栏", "Left sidebar": "左侧边栏",
"Right sidebar": "右侧边栏", "Right sidebar": "右侧边栏",
@@ -416,6 +439,7 @@
"{{latestVersion}} is available": "{{latestVersion}} 可用", "{{latestVersion}} is available": "{{latestVersion}} 可用",
"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.": "选择您偏好的页面编辑模式。避免意外编辑。",
"Choose {{format}} file": "选择 {{format}} 文件",
"Reading": "阅读", "Reading": "阅读",
"Delete member": "删除成员", "Delete member": "删除成员",
"Member deleted successfully": "成员删除成功", "Member deleted successfully": "成员删除成功",
@@ -565,6 +589,8 @@
"Move to trash": "移至回收站", "Move to trash": "移至回收站",
"Move this page to trash?": "将此页面移至垃圾箱?", "Move this page to trash?": "将此页面移至垃圾箱?",
"Restore page": "恢复页面", "Restore page": "恢复页面",
"Permanently delete": "永久删除",
"<b>{{name}}</b> moved this page to Trash {{time}}.": "<b>{{name}}</b> 于 {{time}} 将此页面移至回收站。",
"Page moved to trash": "页面已移至回收站", "Page moved to trash": "页面已移至回收站",
"Page restored successfully": "页面恢复成功", "Page restored successfully": "页面恢复成功",
"Deleted by": "删除者", "Deleted by": "删除者",
@@ -608,25 +634,21 @@
"Image exceeds 10MB limit.": "图片超过10MB限制。", "Image exceeds 10MB limit.": "图片超过10MB限制。",
"Image removed successfully": "图片删除成功", "Image removed successfully": "图片删除成功",
"API key": "API密钥", "API key": "API密钥",
"API key created successfully": "API密钥创建成功",
"API keys": "API密钥", "API keys": "API密钥",
"API management": "API管理", "API management": "API管理",
"Are you sure you want to revoke this API key": "确定要撤销此API密钥吗",
"Create API Key": "创建API密钥",
"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": "我已保存我的API密钥",
"Last use": "上次使用", "Last use": "上次使用",
"No API keys found": "找不到API密钥", "No API keys found": "找不到API密钥",
"No expiration": "无到期", "No expiration": "无到期",
"Revoke API key": "撤销API密钥",
"Revoked successfully": "撤销成功", "Revoked successfully": "撤销成功",
"Select expiration date": "选择到期日期", "Select expiration date": "选择到期日期",
"This action cannot be undone. Any applications using this API key will stop working.": "此操作无法撤销。使用此API密钥的任何应用程序将停止工作。", "This action cannot be undone. Any applications using this API key will stop working.": "此操作无法撤销。使用此API密钥的任何应用程序将停止工作。",
"Update API key": "更新API密钥", "Update": "更新",
"Update {{credential}}": "更新{{credential}}",
"Manage API keys for all users in the workspace": "管理工作空间中所有用户的API密钥", "Manage API keys for all users in the workspace": "管理工作空间中所有用户的API密钥",
"Restrict API key creation to admins": "仅限管理员创建 API 密钥。", "Restrict API key creation to admins": "仅限管理员创建 API 密钥。",
"Only admins and owners can create new API keys. Existing member keys will continue to work.": "只有管理员和所有者可以创建新的 API 密钥。现有成员密钥将继续有效。", "Only admins and owners can create new API keys. Existing member keys will continue to work.": "只有管理员和所有者可以创建新的 API 密钥。现有成员密钥将继续有效。",
@@ -858,9 +880,12 @@
"AI Chat": "AI 聊天", "AI Chat": "AI 聊天",
"Analyze for insights": "分析并获取洞察", "Analyze for insights": "分析并获取洞察",
"Ask anything...": "随便问点什么...", "Ask anything...": "随便问点什么...",
"Assistant said:": "助手说:",
"Chat history": "聊天记录", "Chat history": "聊天记录",
"Chat name": "聊天名称", "Chat name": "聊天名称",
"Chat transcript": "聊天记录",
"Close": "关闭", "Close": "关闭",
"Copy assistant response": "复制助手回复",
"Docmost AI": "Docmost AI", "Docmost AI": "Docmost AI",
"Failed to load chat. An error occurred.": "加载聊天失败。发生错误。", "Failed to load chat. An error occurred.": "加载聊天失败。发生错误。",
"Failed to render this message.": "渲染此消息失败。", "Failed to render this message.": "渲染此消息失败。",
@@ -870,9 +895,17 @@
"No chats found": "未找到聊天", "No chats found": "未找到聊天",
"No conversations yet": "暂无对话", "No conversations yet": "暂无对话",
"Open full page": "打开完整页面", "Open full page": "打开完整页面",
"Scroll to bottom": "滚动到底部",
"You said:": "你说:",
"Previous 7 days": "前 7 天", "Previous 7 days": "前 7 天",
"Previous 30 days": "前 30 天", "Previous 30 days": "前 30 天",
"Search chats...": "搜索聊天...", "Search chats...": "搜索聊天...",
"Search chats": "搜索聊天",
"Ask anything... Use @ to mention pages": "询问任何内容……使用 @ 提及页面",
"Ask anything or search your workspace": "询问任何问题或搜索你的工作区",
"Welcome to {{name}}": "欢迎使用 {{name}}",
"Add files": "添加文件",
"Mention a page": "提及页面",
"Start a new chat to see it here.": "开始新的聊天后会显示在这里。", "Start a new chat to see it here.": "开始新的聊天后会显示在这里。",
"Summarize this page": "总结此页面", "Summarize this page": "总结此页面",
"Toggle AI Chat": "切换 AI 聊天", "Toggle AI Chat": "切换 AI 聊天",
@@ -880,5 +913,176 @@
"Try a different search term.": "请尝试其他搜索词。", "Try a different search term.": "请尝试其他搜索词。",
"Try again": "重试", "Try again": "重试",
"Untitled chat": "未命名聊天", "Untitled chat": "未命名聊天",
"What can I help you with?": "我能帮您做什么?" "What can I help you with?": "我能帮您做什么?",
"Are you sure you want to revoke this {{credential}}": "确定要撤销此{{credential}}吗",
"Automatically provision users and groups from your identity provider via SCIM.": "通过 SCIM 从您的身份提供商自动预配用户和群组。",
"Configure your identity provider with this URL to provision users and groups.": "使用此 URL 配置您的身份提供商以预配用户和群组。",
"Create {{credential}}": "创建{{credential}}",
"{{credential}} created": "已创建{{credential}}",
"{{credential}} created successfully": "已成功创建{{credential}}",
"Created by": "创建者",
"Custom": "自定义",
"Enable SCIM": "启用 SCIM",
"Enter a descriptive name": "输入描述性名称",
"I've saved my {{credential}}": "我已保存我的{{credential}}",
"Important": "重要",
"Make sure to copy your {{credential}} now. You won't be able to see it again!": "请务必立即复制您的{{credential}}。之后您将无法再次查看!",
"Never": "从不",
"Revoke {{credential}}": "撤销{{credential}}",
"SCIM endpoint URL": "SCIM 端点 URL",
"SCIM provisioning": "SCIM 预配",
"SCIM takes precedence over SSO group sync while enabled.": "启用后,SCIM 的优先级高于 SSO 群组同步。",
"You have reached the maximum of {{max}} SCIM tokens. Delete an existing token to create a new one.": "您已达到 {{max}} 个 SCIM 令牌的上限。请删除一个现有令牌以创建新令牌。",
"SCIM token": "SCIM 令牌",
"SCIM tokens": "SCIM 令牌",
"This action cannot be undone. Your identity provider will stop syncing immediately.": "此操作无法撤销。您的身份提供商将立即停止同步。",
"Toggle SCIM provisioning": "切换 SCIM 预配",
"Token": "令牌",
"Page menu": "页面菜单",
"Expand": "展开",
"Collapse": "折叠",
"Comment menu": "评论菜单",
"Group menu": "群组菜单",
"Show hidden breadcrumbs": "显示隐藏的面包屑",
"Breadcrumbs": "面包屑",
"Page actions": "页面操作",
"Pick emoji": "选择表情符号",
"Template menu": "模板菜单",
"Use": "使用",
"Use template": "使用模板",
"Preview template: {{title}}": "预览模板:{{title}}",
"Use a template": "使用模板",
"Search templates...": "搜索模板……",
"Search spaces...": "搜索空间……",
"No templates found": "未找到模板",
"No spaces found": "未找到空间",
"Browse all templates": "浏览所有模板",
"This space": "此空间",
"All templates": "所有模板",
"Global": "全局",
"New template": "新建模板",
"Edit template": "编辑模板",
"Are you sure you want to delete this template?": "你确定要删除此模板吗?",
"Template scope updated": "模板范围已更新",
"Choose which space this template belongs to": "选择此模板所属的空间",
"Scope": "范围",
"Select scope": "选择范围",
"Title": "标题",
"Saving...": "正在保存……",
"Saved": "已保存",
"Save failed. Retry": "保存失败。重试",
"By {{name}}": "作者:{{name}}",
"Updated {{time}}": "更新于 {{time}}",
"Choose destination": "选择目标位置",
"Search pages and spaces...": "搜索页面和空间……",
"No results found": "未找到结果",
"You don't have permission to create pages here": "你无权在此处创建页面",
"Chat menu": "聊天菜单",
"API key menu": "API 密钥菜单",
"Jump to comment selection": "跳转到评论选择",
"Slash commands": "斜杠命令",
"Mention suggestions": "提及建议",
"Link suggestions": "链接建议",
"Diagram editor": "图表编辑器",
"Add comment": "添加评论",
"Find and replace": "查找和替换",
"Main navigation": "主导航",
"Space navigation": "空间导航",
"Settings navigation": "设置导航",
"AI navigation": "AI 导航",
"Breadcrumb": "面包屑",
"Synced block": "同步块",
"Create a block that stays in sync across pages.": "创建一个可在多个页面间保持同步的块。",
"Editing original": "正在编辑原始内容",
"Copy synced block": "复制同步块",
"Unsync": "取消同步",
"Delete synced block": "删除同步块",
"Synced to {{count}} other page_one": "已与另外 {{count}} 个页面同步",
"Synced to {{count}} other page_other": "已与另外 {{count}} 个页面同步",
"ORIGINAL": "原始内容",
"THIS PAGE": "此页面",
"No pages": "没有页面",
"The original synced block no longer exists": "原始同步块已不存在",
"You don't have access to this synced block": "你无权访问此同步块",
"Failed to load this synced block": "加载此同步块失败",
"Fixed editor toolbar": "固定编辑器工具栏",
"Show a formatting toolbar above the editor with quick access to common actions.": "在编辑器上方显示格式工具栏,便于快速访问常用操作。",
"Toggle fixed editor toolbar": "切换固定编辑器工具栏",
"Normal text": "普通文本",
"More inline formatting": "更多内联格式",
"Subscript": "下标",
"Superscript": "上标",
"Inline code": "行内代码",
"Insert media": "插入媒体",
"Mention": "提及",
"Emoji": "表情符号",
"Columns": "分栏",
"More inserts": "更多插入项",
"Embeds": "嵌入内容",
"Diagrams": "图表",
"Advanced": "高级",
"Utility": "实用工具",
"Decrease indent": "减少缩进",
"Increase indent": "增加缩进",
"Clear formatting": "清除格式",
"Code block": "代码块",
"Experimental": "实验性",
"Strikethrough": "删除线",
"Undo": "撤销",
"Redo": "重做",
"Backlinks": "反向链接",
"Last updated by": "最后更新者",
"Last updated": "最后更新",
"Stats": "统计",
"Word count": "字数",
"Characters": "字符数",
"Incoming links": "传入链接",
"Outgoing links": "传出链接",
"Incoming links ({{count}})": "传入链接({{count}}",
"Outgoing links ({{count}})": "传出链接({{count}}",
"No pages link here yet.": "还没有页面链接到这里。",
"This page doesn't link to other pages yet.": "此页面尚未链接到其他页面。",
"Verified until {{date}}": "验证有效期至 {{date}}",
"Labels": "标签",
"Add label": "添加标签",
"No labels yet": "还没有标签",
"Already added": "已添加",
"Invalid label name": "标签名称无效",
"No matches": "无匹配结果",
"Search or create…": "搜索或创建…",
"Remove label {{name}}": "移除标签 {{name}}",
"Failed to add label": "添加标签失败",
"Failed to remove label": "移除标签失败",
"No pages with this label": "没有带有此标签的页面",
"Pages tagged with this label will appear here.": "带有此标签的页面将显示在这里。",
"No pages match your search.": "没有页面匹配你的搜索。",
"Updated {{date}}": "更新于 {{date}}",
"Cell actions": "单元格操作",
"Column actions": "列操作",
"Row actions": "行操作",
"Filter": "筛选",
"Page title": "页面标题",
"Page content": "页面内容",
"Member actions": "成员操作",
"Toggle password visibility": "切换密码可见性",
"Send comment": "发送评论",
"Token actions": "令牌操作",
"Template settings": "模板设置",
"Edit diagram": "编辑图表",
"Edit embed": "编辑嵌入内容",
"Edit drawing": "编辑绘图",
"Delete equation": "删除公式",
"Invite actions": "邀请操作",
"Get started": "开始使用",
"* indicates required fields": "* 表示必填字段",
"List of spaces in this workspace": "此工作区中的空间列表",
"Active sessions": "活动会话",
"Add {{name}} to favorites": "将 {{name}} 添加到收藏",
"Remove {{name}} from favorites": "将 {{name}} 从收藏中移除",
"Added to favorites": "已添加到收藏",
"Removed from favorites": "已从收藏中移除",
"Added {{name}} to favorites": "已将 {{name}} 添加到收藏",
"Removed {{name}} from favorites": "已将 {{name}} 从收藏中移除",
"Page menu for {{name}}": "{{name}} 的页面菜单",
"Create subpage of {{name}}": "创建 {{name}} 的子页面"
} }
+2
View File
@@ -45,6 +45,7 @@ import TemplateEditor from "@/ee/template/pages/template-editor";
import FavoritesPage from "@/pages/favorites/favorites-page"; import FavoritesPage from "@/pages/favorites/favorites-page";
import AiChat from "@/ee/ai-chat/pages/ai-chat.tsx"; import AiChat from "@/ee/ai-chat/pages/ai-chat.tsx";
import VerifyEmail from "@/ee/pages/verify-email.tsx"; import VerifyEmail from "@/ee/pages/verify-email.tsx";
import LabelPage from "@/pages/label/label-page";
export default function App() { export default function App() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -92,6 +93,7 @@ export default function App() {
<Route path={"/ai/chat/:chatId"} element={<AiChat />} /> <Route path={"/ai/chat/:chatId"} element={<AiChat />} />
<Route path={"/spaces"} element={<SpacesPage />} /> <Route path={"/spaces"} element={<SpacesPage />} />
<Route path={"/favorites"} element={<FavoritesPage />} /> <Route path={"/favorites"} element={<FavoritesPage />} />
<Route path={"/labels/:labelName"} element={<LabelPage />} />
<Route path={"/templates"} element={<TemplateList />} /> <Route path={"/templates"} element={<TemplateList />} />
<Route <Route
path={"/templates/:templateId"} path={"/templates/:templateId"}
@@ -80,6 +80,20 @@ export default function AvatarUploader({
} }
}; };
const actionLabel = {
[AvatarIconType.AVATAR]: t("Change avatar"),
[AvatarIconType.SPACE_ICON]: t("Change space icon"),
[AvatarIconType.WORKSPACE_ICON]: t("Change workspace icon"),
}[type];
// Per WCAG 2.5.3 (Label in Name), the accessible name must include the
// visible text. When no image is set, the avatar renders the name's
// initials, so prepend the name to the action label.
const ariaLabel =
!currentImageUrl && fallbackName
? `${fallbackName} ${actionLabel}`
: actionLabel;
const handleRemove = async () => { const handleRemove = async () => {
if (disabled) return; if (disabled) return;
@@ -104,6 +118,8 @@ export default function AvatarUploader({
ref={fileInputRef} ref={fileInputRef}
onChange={handleFileInputChange} onChange={handleFileInputChange}
accept="image/png,image/jpeg,image/jpg" accept="image/png,image/jpeg,image/jpg"
aria-label={ariaLabel}
tabIndex={-1}
style={{ display: "none" }} style={{ display: "none" }}
/> />
@@ -115,6 +131,8 @@ export default function AvatarUploader({
size={size} size={size}
avatarUrl={currentImageUrl} avatarUrl={currentImageUrl}
name={fallbackName} name={fallbackName}
aria-label={ariaLabel}
aria-haspopup="menu"
style={{ style={{
cursor: disabled || isLoading ? "default" : "pointer", cursor: disabled || isLoading ? "default" : "pointer",
opacity: isLoading ? 0.6 : 1, opacity: isLoading ? 0.6 : 1,
+7 -2
View File
@@ -8,15 +8,19 @@ interface CopyProps {
text: string; text: string;
size?: MantineSize; size?: MantineSize;
color?: MantineColor; color?: MantineColor;
/** Override the accessible name (and tooltip) when not yet copied. Lets callers disambiguate adjacent copy buttons for screen readers. */
label?: string;
} }
export default function CopyTextButton({ text, size }: CopyProps) { export default function CopyTextButton({ text, size, label }: CopyProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const copyLabel = label ?? t("Copy");
return ( return (
<CopyButton value={text} timeout={2000}> <CopyButton value={text} timeout={2000}>
{({ copied, copy }) => ( {({ copied, copy }) => (
<Tooltip <Tooltip
label={copied ? t("Copied") : t("Copy")} label={copied ? t("Copied") : copyLabel}
withArrow withArrow
position="right" position="right"
> >
@@ -25,6 +29,7 @@ export default function CopyTextButton({ text, size }: CopyProps) {
variant="subtle" variant="subtle"
onClick={copy} onClick={copy}
size={size} size={size}
aria-label={copied ? t("Copied") : copyLabel}
> >
{copied ? <IconCheck size={16} /> : <IconCopy size={16} />} {copied ? <IconCheck size={16} /> : <IconCopy size={16} />}
</ActionIcon> </ActionIcon>
@@ -81,7 +81,7 @@ export default function ExportModal({
<Modal.Content style={{ overflow: "hidden" }}> <Modal.Content style={{ overflow: "hidden" }}>
<Modal.Header py={0}> <Modal.Header py={0}>
<Modal.Title fw={500}>{t(`Export ${type}`)}</Modal.Title> <Modal.Title fw={500}>{t(`Export ${type}`)}</Modal.Title>
<Modal.CloseButton /> <Modal.CloseButton aria-label={t("Close")} />
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
<Group justify="space-between" wrap="nowrap"> <Group justify="space-between" wrap="nowrap">
@@ -4,7 +4,7 @@ import {
UnstyledButton, UnstyledButton,
Badge, Badge,
Table, Table,
ActionIcon, ThemeIcon,
Button, Button,
} from "@mantine/core"; } from "@mantine/core";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@@ -17,6 +17,7 @@ import { EmptyState } from "@/components/ui/empty-state.tsx";
import { getSpaceUrl } from "@/lib/config.ts"; import { getSpaceUrl } from "@/lib/config.ts";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getInitialsColor } from "@/lib/get-initials-color.ts"; import { getInitialsColor } from "@/lib/get-initials-color.ts";
import rowClasses from "@/components/ui/clickable-table-row.module.css";
interface Props { interface Props {
spaceId?: string; spaceId?: string;
@@ -41,17 +42,18 @@ export default function RecentChanges({ spaceId }: Props) {
<Table highlightOnHover verticalSpacing="sm"> <Table highlightOnHover verticalSpacing="sm">
<Table.Tbody> <Table.Tbody>
{pages.map((page) => ( {pages.map((page) => (
<Table.Tr key={page.id}> <Table.Tr key={page.id} className={rowClasses.row}>
<Table.Td> <Table.Td>
<UnstyledButton <UnstyledButton
className={rowClasses.link}
component={Link} component={Link}
to={buildPageUrl(page?.space.slug, page.slugId, page.title)} to={buildPageUrl(page?.space.slug, page.slugId, page.title)}
> >
<Group wrap="nowrap"> <Group wrap="nowrap">
{page.icon || ( {page.icon || (
<ActionIcon variant="transparent" color="gray" size={18}> <ThemeIcon variant="transparent" color="gray" size={18}>
<IconFileDescription size={18} /> <IconFileDescription size={18} />
</ActionIcon> </ThemeIcon>
)} )}
<Text fw={500} size="md" lineClamp={1}> <Text fw={500} size="md" lineClamp={1}>
@@ -6,12 +6,14 @@ import { useTranslation } from "react-i18next";
export interface SearchInputProps { export interface SearchInputProps {
placeholder?: string; placeholder?: string;
ariaLabel?: string;
debounceDelay?: number; debounceDelay?: number;
onSearch: (value: string) => void; onSearch: (value: string) => void;
} }
export function SearchInput({ export function SearchInput({
placeholder, placeholder,
ariaLabel,
debounceDelay = 500, debounceDelay = 500,
onSearch, onSearch,
}: SearchInputProps) { }: SearchInputProps) {
@@ -28,6 +30,7 @@ export function SearchInput({
<TextInput <TextInput
size="sm" size="sm"
placeholder={placeholder || t("Search...")} placeholder={placeholder || t("Search...")}
aria-label={ariaLabel || placeholder || t("Search")}
leftSection={<IconSearch size={16} />} leftSection={<IconSearch size={16} />}
value={value} value={value}
onChange={(e) => setValue(e.currentTarget.value)} onChange={(e) => setValue(e.currentTarget.value)}
@@ -1,11 +1,11 @@
import { ActionIcon, rem } from "@mantine/core"; import { ThemeIcon } from "@mantine/core";
import React from "react"; import React from "react";
import { IconUsersGroup } from "@tabler/icons-react"; import { IconUsersGroup } from "@tabler/icons-react";
export function IconGroupCircle() { export function IconGroupCircle() {
return ( return (
<ActionIcon variant="light" size="lg" color="gray" radius="xl"> <ThemeIcon variant="light" size="lg" color="gray" radius="xl">
<IconUsersGroup stroke={1.5} /> <IconUsersGroup stroke={1.5} />
</ActionIcon> </ThemeIcon>
); );
} }
@@ -27,5 +27,3 @@
background: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-5)) background: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-5))
} }
} }
@@ -1,18 +1,27 @@
import { Box, ScrollArea, Text } from "@mantine/core"; import { ActionIcon, Box, Group, ScrollArea, Title, Tooltip } from "@mantine/core";
import { IconX } from "@tabler/icons-react";
import CommentListWithTabs from "@/features/comment/components/comment-list-with-tabs.tsx"; import CommentListWithTabs from "@/features/comment/components/comment-list-with-tabs.tsx";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts"; import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
import React, { ReactNode } from "react"; import React, { ReactNode, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { TableOfContents } from "@/features/editor/components/table-of-contents/table-of-contents.tsx"; import { TableOfContents } from "@/features/editor/components/table-of-contents/table-of-contents.tsx";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { pageEditorAtom } from "@/features/editor/atoms/editor-atoms.ts"; import { pageEditorAtom } from "@/features/editor/atoms/editor-atoms.ts";
import AsideChatPanel from "@/ee/ai-chat/components/aside-chat-panel"; import AsideChatPanel from "@/ee/ai-chat/components/aside-chat-panel";
import { PageDetailsAside } from "@/features/page-details/components/page-details-aside.tsx";
import { ASIDE_PANEL_ID } from "@/hooks/use-toggle-aside.tsx";
export default function Aside() { export default function Aside() {
const [{ tab }] = useAtom(asideStateAtom); const [{ tab, isAsideOpen }, setAsideState] = useAtom(asideStateAtom);
const { t } = useTranslation(); const { t } = useTranslation();
const pageEditor = useAtomValue(pageEditorAtom); const pageEditor = useAtomValue(pageEditorAtom);
const closeAside = () => setAsideState((s) => ({ ...s, isAsideOpen: false }));
useEffect(() => {
if (!isAsideOpen) return;
document.getElementById(ASIDE_PANEL_ID)?.focus();
}, [isAsideOpen, tab]);
let title: string; let title: string;
let component: ReactNode; let component: ReactNode;
@@ -30,6 +39,10 @@ export default function Aside() {
component = <AsideChatPanel />; component = <AsideChatPanel />;
title = "AI Chat"; title = "AI Chat";
break; break;
case "details":
component = <PageDetailsAside />;
title = "Details";
break;
default: default:
component = null; component = null;
title = null; title = null;
@@ -40,9 +53,19 @@ export default function Aside() {
{component && ( {component && (
<> <>
{tab !== "chat" && ( {tab !== "chat" && (
<Text mb="md" fw={500}> <Group justify="space-between" wrap="nowrap" mb="md">
{t(title)} <Title order={2} size="h6" fw={500}>{t(title)}</Title>
</Text> <Tooltip label={t("Close")} withArrow>
<ActionIcon
variant="subtle"
color="gray"
onClick={closeAside}
aria-label={t("Close")}
>
<IconX size={18} />
</ActionIcon>
</Tooltip>
</Group>
)} )}
{tab === "comments" || tab === "chat" ? ( {tab === "comments" || tab === "chat" ? (
@@ -1,6 +1,7 @@
import { AppShell, Container } from "@mantine/core"; import { AppShell, Container } from "@mantine/core";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { useTranslation } from "react-i18next";
import SettingsSidebar from "@/components/settings/settings-sidebar.tsx"; import SettingsSidebar from "@/components/settings/settings-sidebar.tsx";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { import {
@@ -17,17 +18,20 @@ import classes from "./app-shell.module.css";
import { useTrialEndAction } from "@/ee/hooks/use-trial-end-action.tsx"; import { useTrialEndAction } from "@/ee/hooks/use-trial-end-action.tsx";
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts"; import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
import GlobalSidebar from "@/components/layouts/global/global-sidebar.tsx"; import GlobalSidebar from "@/components/layouts/global/global-sidebar.tsx";
import { ASIDE_PANEL_ID } from "@/hooks/use-toggle-aside.tsx";
import { MAIN_CONTENT_ID, SkipToMain } from "@/components/ui/skip-to-main.tsx";
export default function GlobalAppShell({ export default function GlobalAppShell({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const { t } = useTranslation();
useTrialEndAction(); useTrialEndAction();
const [mobileOpened] = useAtom(mobileSidebarAtom); const [mobileOpened] = useAtom(mobileSidebarAtom);
const toggleMobile = useToggleSidebar(mobileSidebarAtom); const toggleMobile = useToggleSidebar(mobileSidebarAtom);
const [desktopOpened] = useAtom(desktopSidebarAtom); const [desktopOpened] = useAtom(desktopSidebarAtom);
const [{ isAsideOpen }] = useAtom(asideStateAtom); const [{ isAsideOpen, tab: asideTab }] = useAtom(asideStateAtom);
const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom); const [sidebarWidth, setSidebarWidth] = useAtom(sidebarWidthAtom);
const [isResizing, setIsResizing] = useState(false); const [isResizing, setIsResizing] = useState(false);
const sidebarRef = useRef(null); const sidebarRef = useRef(null);
@@ -79,6 +83,8 @@ export default function GlobalAppShell({
const showGlobalSidebar = !isSpaceRoute && !isSettingsRoute && !isAiRoute; const showGlobalSidebar = !isSpaceRoute && !isSettingsRoute && !isAiRoute;
return ( return (
<>
<SkipToMain />
<AppShell <AppShell
header={{ height: 45 }} header={{ height: 45 }}
navbar={{ navbar={{
@@ -105,6 +111,15 @@ export default function GlobalAppShell({
className={classes.navbar} className={classes.navbar}
withBorder={false} withBorder={false}
ref={sidebarRef} ref={sidebarRef}
aria-label={
isSpaceRoute
? t("Space navigation")
: isSettingsRoute
? t("Settings navigation")
: isAiRoute
? t("AI navigation")
: t("Main navigation")
}
> >
{isSpaceRoute && ( {isSpaceRoute && (
<div className={classes.resizeHandle} onMouseDown={startResizing} /> <div className={classes.resizeHandle} onMouseDown={startResizing} />
@@ -114,7 +129,7 @@ export default function GlobalAppShell({
{isAiRoute && <AiChatSidebar />} {isAiRoute && <AiChatSidebar />}
{showGlobalSidebar && <GlobalSidebar />} {showGlobalSidebar && <GlobalSidebar />}
</AppShell.Navbar> </AppShell.Navbar>
<AppShell.Main> <AppShell.Main id={MAIN_CONTENT_ID} tabIndex={-1}>
{isSettingsRoute ? ( {isSettingsRoute ? (
<Container size={900} pb={80}> <Container size={900} pb={80}>
{children} {children}
@@ -125,10 +140,28 @@ export default function GlobalAppShell({
</AppShell.Main> </AppShell.Main>
{isPageRoute && ( {isPageRoute && (
<AppShell.Aside className={classes.aside} p="md" withBorder={false}> <AppShell.Aside
id={ASIDE_PANEL_ID}
tabIndex={-1}
className={classes.aside}
p="md"
withBorder={false}
aria-label={
asideTab === "comments"
? t("Comments")
: asideTab === "toc"
? t("Table of contents")
: asideTab === "chat"
? t("AI Chat")
: asideTab === "details"
? t("Details")
: undefined
}
>
<Aside /> <Aside />
</AppShell.Aside> </AppShell.Aside>
)} )}
</AppShell> </AppShell>
</>
); );
} }
@@ -31,6 +31,11 @@
color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
} }
&:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
}
&[data-active] { &[data-active] {
&, &,
& :hover { & :hover {
@@ -38,6 +43,16 @@
color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
} }
} }
&[data-disabled] {
cursor: not-allowed;
opacity: 0.5;
@mixin hover {
background-color: transparent;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-1));
}
}
} }
.linkIcon { .linkIcon {
@@ -50,7 +65,7 @@
.sectionHeader { .sectionHeader {
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm); padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
font-size: var(--mantine-font-size-xs); font-size: var(--mantine-font-size-xs);
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-3)); color: var(--mantine-color-dimmed);
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
@@ -86,4 +101,9 @@
); );
color: light-dark(var(--mantine-color-black), var(--mantine-color-white)); color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
} }
&:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
}
} }
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { ScrollArea, Text, Divider, Modal } from "@mantine/core"; import { ScrollArea, Text, Divider, Modal, UnstyledButton, Tooltip } from "@mantine/core";
import { import {
IconHome, IconHome,
IconClock, IconClock,
@@ -7,6 +7,7 @@ import {
IconLayoutGrid, IconLayoutGrid,
IconSettings, IconSettings,
IconUserPlus, IconUserPlus,
IconTemplate,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import classes from "./global-sidebar.module.css"; import classes from "./global-sidebar.module.css";
@@ -20,12 +21,9 @@ import { useDisclosure } from "@mantine/hooks";
import { WorkspaceInviteForm } from "@/features/workspace/components/members/components/workspace-invite-form"; import { WorkspaceInviteForm } from "@/features/workspace/components/members/components/workspace-invite-form";
import { CustomAvatar } from "@/components/ui/custom-avatar"; import { CustomAvatar } from "@/components/ui/custom-avatar";
import { AvatarIconType } from "@/features/attachments/types/attachment.types"; import { AvatarIconType } from "@/features/attachments/types/attachment.types";
import { useHasFeature } from "@/ee/hooks/use-feature";
const mainNavItems = [ import { Feature } from "@/ee/features";
{ label: "Home", icon: IconHome, path: "/home" }, import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
{ label: "Favorites", icon: IconStar, path: "/favorites" },
{ label: "Spaces", icon: IconLayoutGrid, path: "/spaces" },
];
export default function GlobalSidebar() { export default function GlobalSidebar() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -33,6 +31,19 @@ export default function GlobalSidebar() {
const [active, setActive] = useState(location.pathname); const [active, setActive] = useState(location.pathname);
const [mobileSidebarOpened] = useAtom(mobileSidebarAtom); const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom); const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
const hasTemplates = useHasFeature(Feature.TEMPLATES);
const upgradeLabel = useUpgradeLabel();
const mainNavItems = [
{ label: "Home", icon: IconHome, path: "/home" },
{ label: "Favorites", icon: IconStar, path: "/favorites" },
{ label: "Spaces", icon: IconLayoutGrid, path: "/spaces" },
{
label: "Templates",
icon: IconTemplate,
path: "/templates",
disabled: !hasTemplates,
},
];
const { data: favoriteSpacesData, isPending: isFavoritesPending } = useFavoritesQuery("space"); const { data: favoriteSpacesData, isPending: isFavoritesPending } = useFavoritesQuery("space");
const favoriteSpaces = favoriteSpacesData?.pages.flatMap((p) => p.items) ?? []; const favoriteSpaces = favoriteSpacesData?.pages.flatMap((p) => p.items) ?? [];
const sortedFavoriteSpaces = [...favoriteSpaces] const sortedFavoriteSpaces = [...favoriteSpaces]
@@ -58,18 +69,38 @@ export default function GlobalSidebar() {
<div className={classes.navbar}> <div className={classes.navbar}>
<ScrollArea w="100%" style={{ flex: 1 }}> <ScrollArea w="100%" style={{ flex: 1 }}>
<div className={classes.section}> <div className={classes.section}>
{mainNavItems.map((item) => ( {mainNavItems.map((item) =>
item.disabled ? (
<Tooltip
key={item.label}
label={upgradeLabel}
position="right"
withArrow
>
<UnstyledButton
className={classes.link}
data-disabled
aria-disabled="true"
tabIndex={-1}
>
<item.icon className={classes.linkIcon} stroke={2} />
<span>{t(item.label)}</span>
</UnstyledButton>
</Tooltip>
) : (
<Link <Link
key={item.label} key={item.label}
className={classes.link} className={classes.link}
data-active={active === item.path || undefined} data-active={active === item.path || undefined}
aria-current={active === item.path ? "page" : undefined}
to={item.path} to={item.path}
onClick={handleNavClick} onClick={handleNavClick}
> >
<item.icon className={classes.linkIcon} stroke={2} /> <item.icon className={classes.linkIcon} stroke={2} />
<span>{t(item.label)}</span> <span>{t(item.label)}</span>
</Link> </Link>
))} ),
)}
</div> </div>
<Divider my="xs" /> <Divider my="xs" />
@@ -119,20 +150,17 @@ export default function GlobalSidebar() {
</ScrollArea> </ScrollArea>
<div className={classes.bottomSection}> <div className={classes.bottomSection}>
<a <UnstyledButton
className={classes.link} className={classes.link}
onClick={(e) => { onClick={openInvite}
e.preventDefault();
openInvite();
}}
href="#"
> >
<IconUserPlus className={classes.linkIcon} stroke={2} /> <IconUserPlus className={classes.linkIcon} stroke={2} />
<span>{t("Invite People")}</span> <span>{t("Invite People")}</span>
</a> </UnstyledButton>
<Link <Link
className={classes.link} className={classes.link}
data-active={active.startsWith("/settings") || undefined} data-active={active.startsWith("/settings") || undefined}
aria-current={active.startsWith("/settings") ? "page" : undefined}
to="/settings/account/profile" to="/settings/account/profile"
onClick={handleNavClick} onClick={handleNavClick}
> >
@@ -10,6 +10,7 @@ export const desktopSidebarAtom = atomWithWebStorage<boolean>(
export const desktopAsideAtom = atom<boolean>(false); export const desktopAsideAtom = atom<boolean>(false);
// Valid `tab` values: "" | "comments" | "toc" | "chat" | "details"
type AsideStateType = { type AsideStateType = {
tab: string; tab: string;
isAsideOpen: boolean; isAsideOpen: boolean;
@@ -230,32 +230,6 @@ export default function SettingsSidebar() {
} }
const isDisabled = isItemDisabled(item); const isDisabled = isItemDisabled(item);
const linkElement = (
<Link
onMouseEnter={!isDisabled ? prefetchHandler : undefined}
className={classes.link}
data-active={active.startsWith(item.path) || undefined}
data-disabled={isDisabled || undefined}
key={item.label}
to={isDisabled ? "#" : item.path}
onClick={(e) => {
if (isDisabled) {
e.preventDefault();
return;
}
if (mobileSidebarOpened) {
toggleMobileSidebar();
}
}}
style={{
opacity: isDisabled ? 0.5 : 1,
cursor: isDisabled ? "not-allowed" : "pointer",
}}
>
<item.icon className={classes.linkIcon} stroke={2} />
<span>{t(item.label)}</span>
</Link>
);
if (isDisabled) { if (isDisabled) {
return ( return (
@@ -265,12 +239,41 @@ export default function SettingsSidebar() {
position="right" position="right"
withArrow withArrow
> >
{linkElement} <span
className={classes.link}
data-disabled
role="link"
aria-disabled="true"
tabIndex={0}
style={{
opacity: 0.5,
cursor: "not-allowed",
}}
>
<item.icon className={classes.linkIcon} stroke={2} />
<span>{t(item.label)}</span>
</span>
</Tooltip> </Tooltip>
); );
} }
return linkElement; return (
<Link
onMouseEnter={prefetchHandler}
className={classes.link}
data-active={active.startsWith(item.path) || undefined}
key={item.label}
to={item.path}
onClick={() => {
if (mobileSidebarOpened) {
toggleMobileSidebar();
}
}}
>
<item.icon className={classes.linkIcon} stroke={2} />
<span>{t(item.label)}</span>
</Link>
);
})} })}
</div> </div>
); );
@@ -288,7 +291,7 @@ export default function SettingsSidebar() {
}} }}
variant="transparent" variant="transparent"
c="gray" c="gray"
aria-label="Back" aria-label={t("Back")}
> >
<IconArrowLeft stroke={2} /> <IconArrowLeft stroke={2} />
</ActionIcon> </ActionIcon>
@@ -4,7 +4,7 @@ import { Divider, Title } from '@mantine/core';
export default function SettingsTitle({ title }: { title: string }) { export default function SettingsTitle({ title }: { title: string }) {
return ( return (
<> <>
<Title order={3}> <Title order={1} size="h3">
{title} {title}
</Title> </Title>
<Divider my="md" /> <Divider my="md" />
@@ -0,0 +1,29 @@
/*
* Focus styling for list-style tables (recent changes, favorites, all
* spaces, groups, verified pages, shares).
*
* Per WAI-ARIA Authoring Practices and Adrian Roselli's guidance on table
* accessibility (https://adrianroselli.com/2020/02/block-links-cards-clickable-regions-etc.html),
* data tables should not be made fully clickable. Only the title cell is the
* link, and that link is what receives Tab focus.
*
* - `.row` adds a subtle background tint when the row contains the focused
* element, so keyboard users can see which row they're inspecting.
* - `.link` adds a visible :focus-visible outline on the title link itself.
*
* No stretched-link pseudo here on purpose: absolutely-positioned pseudos
* inside table cells cause column reflow on focus in Chromium.
*/
.row:focus-within {
background-color: light-dark(
var(--mantine-color-gray-1),
var(--mantine-color-dark-6)
);
}
.link:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
border-radius: var(--mantine-radius-sm);
}
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Avatar } from "@mantine/core"; import { Avatar, MantineColor } from "@mantine/core";
import { getAvatarUrl } from "@/lib/config.ts"; import { getAvatarUrl } from "@/lib/config.ts";
import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts"; import { AvatarIconType } from "@/features/attachments/types/attachment.types.ts";
@@ -16,19 +16,57 @@ interface CustomAvatarProps {
mt?: string | number; mt?: string | number;
} }
// `color.shade` pairs whose contrast meets WCAG AA (4.5:1) in BOTH variants:
// - filled: white text on the shade as bg
// - light: shade as text on the color's light-bg (10% color.6 over white)
// Avoids lime/yellow/green/orange — even their dark shades have weak
// contrast. grape and indigo were bumped from .7 to darker shades because
// the original picks failed: grape.7 was 4.02/3.61 (both fail) and
// indigo.7 was 4.98/4.39 (light fails by a hair).
const SAFE_INITIALS_COLORS: MantineColor[] = [
"blue.8",
"cyan.9",
"grape.9",
"indigo.8",
"pink.8",
"red.8",
"violet.7",
];
function hashName(input: string) {
let hash = 0;
for (let i = 0; i < input.length; i += 1) {
hash = (hash << 5) - hash + input.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
function pickInitialsColor(name: string) {
return SAFE_INITIALS_COLORS[hashName(name) % SAFE_INITIALS_COLORS.length];
}
function sanitizeInitialsSource(name: string) {
const sanitized = name.replace(/[^\p{L}\p{N}\s]/gu, " ").trim();
return sanitized || name;
}
export const CustomAvatar = React.forwardRef< export const CustomAvatar = React.forwardRef<
HTMLInputElement, HTMLInputElement,
CustomAvatarProps CustomAvatarProps
>(({ avatarUrl, name, type, ...props }: CustomAvatarProps, ref) => { >(({ avatarUrl, name, type, color, ...props }: CustomAvatarProps, ref) => {
const avatarLink = getAvatarUrl(avatarUrl, type); const avatarLink = getAvatarUrl(avatarUrl, type);
const resolvedColor =
!color || color === "initials" ? pickInitialsColor(name ?? "") : color;
const initialsSource = sanitizeInitialsSource(name ?? "");
return ( return (
<Avatar <Avatar
ref={ref} ref={ref}
src={avatarLink} src={avatarLink}
name={name} name={initialsSource}
alt={name} alt={name}
color="initials" color={resolvedColor}
{...props} {...props}
/> />
); );
@@ -16,6 +16,8 @@ export function DestinationPickerModal({
loading, loading,
excludePageId, excludePageId,
pageLimit, pageLimit,
initialSpaceId,
searchSpacesOnly,
}: DestinationPickerModalProps) { }: DestinationPickerModalProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selection, setSelection] = useState<DestinationSelection | null>(null); const [selection, setSelection] = useState<DestinationSelection | null>(null);
@@ -39,13 +41,15 @@ export function DestinationPickerModal({
<Modal.Content> <Modal.Content>
<Modal.Header py={0}> <Modal.Header py={0}>
<Modal.Title fw={500}>{title}</Modal.Title> <Modal.Title fw={500}>{title}</Modal.Title>
<Modal.CloseButton /> <Modal.CloseButton aria-label={t("Close")} />
</Modal.Header> </Modal.Header>
<Modal.Body> <Modal.Body>
<DestinationPicker <DestinationPicker
onSelectionChange={setSelection} onSelectionChange={setSelection}
excludePageId={excludePageId} excludePageId={excludePageId}
pageLimit={pageLimit} pageLimit={pageLimit}
initialSpaceId={initialSpaceId}
searchSpacesOnly={searchSpacesOnly}
/> />
<Group justify="flex-end" mt="md"> <Group justify="flex-end" mt="md">
@@ -13,6 +13,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
transition: background-color 150ms ease; transition: background-color 150ms ease;
user-select: none; user-select: none;
@@ -22,6 +23,11 @@
var(--mantine-color-dark-6) var(--mantine-color-dark-6)
); );
} }
&:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: -2px;
}
} }
.selected { .selected {
@@ -57,7 +63,7 @@
border-radius: var(--mantine-radius-sm); border-radius: var(--mantine-radius-sm);
flex-shrink: 0; flex-shrink: 0;
transition: transform 150ms ease; transition: transform 150ms ease;
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
@mixin hover { @mixin hover {
background-color: light-dark( background-color: light-dark(
@@ -111,7 +117,7 @@
} }
.spaceName { .spaceName {
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
font-size: var(--mantine-font-size-xs); font-size: var(--mantine-font-size-xs);
flex-shrink: 0; flex-shrink: 0;
} }
@@ -1,7 +1,7 @@
import { useState, useCallback } from "react"; import { useState, useCallback, useEffect, useMemo, useRef } from "react";
import { TextInput, ScrollArea, Loader } from "@mantine/core"; import { ActionIcon, TextInput, ScrollArea, Loader } from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks"; import { useDebouncedValue } from "@mantine/hooks";
import { IconSearch, IconFile } from "@tabler/icons-react"; import { IconSearch, IconFileDescription } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useGetSpacesQuery } from "@/features/space/queries/space-query"; import { useGetSpacesQuery } from "@/features/space/queries/space-query";
import { useSearchSuggestionsQuery } from "@/features/search/queries/search-query"; import { useSearchSuggestionsQuery } from "@/features/search/queries/search-query";
@@ -15,23 +15,29 @@ type DestinationPickerProps = {
onSelectionChange: (selection: DestinationSelection | null) => void; onSelectionChange: (selection: DestinationSelection | null) => void;
excludePageId?: string; excludePageId?: string;
pageLimit?: number; pageLimit?: number;
initialSpaceId?: string;
searchSpacesOnly?: boolean;
}; };
export function DestinationPicker({ export function DestinationPicker({
onSelectionChange, onSelectionChange,
excludePageId, excludePageId,
pageLimit = 15, pageLimit = 15,
initialSpaceId,
searchSpacesOnly,
}: DestinationPickerProps) { }: DestinationPickerProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [selection, setSelection] = useState<DestinationSelection | null>(null); const [selection, setSelection] = useState<DestinationSelection | null>(null);
const [debouncedQuery] = useDebouncedValue(searchQuery, 300); const [debouncedQuery] = useDebouncedValue(searchQuery, 300);
const viewportRef = useRef<HTMLDivElement>(null);
const { data: spacesData, isLoading: spacesLoading } = useGetSpacesQuery({ const { data: spacesData, isLoading: spacesLoading } = useGetSpacesQuery({
limit: 100, limit: 100,
}); });
const searchEnabled = debouncedQuery && debouncedQuery.length >= 2; const searchEnabled =
!searchSpacesOnly && debouncedQuery && debouncedQuery.length >= 2;
const { data: searchData, isLoading: searchLoading } = const { data: searchData, isLoading: searchLoading } =
useSearchSuggestionsQuery({ useSearchSuggestionsQuery({
@@ -42,6 +48,18 @@ export function DestinationPicker({
const isSearching = !!searchEnabled; const isSearching = !!searchEnabled;
const filteredSpaces = useMemo(() => {
const items = spacesData?.items ?? [];
if (!searchSpacesOnly || !debouncedQuery) return items;
const fold = (s: string) =>
s
.normalize("NFD")
.replace(/[̀-ͯ]/g, "")
.toLocaleLowerCase();
const term = fold(debouncedQuery);
return items.filter((s) => fold(s.name).includes(term));
}, [spacesData, searchSpacesOnly, debouncedQuery]);
const selectedId = const selectedId =
selection?.type === "space" ? selection.spaceId : selection?.pageId ?? null; selection?.type === "space" ? selection.spaceId : selection?.pageId ?? null;
@@ -87,18 +105,48 @@ export function DestinationPicker({
[updateSelection], [updateSelection],
); );
// Pre-select space when initialSpaceId is set and spaces have loaded.
// Only runs once: skip if user has already made a selection.
useEffect(() => {
if (!initialSpaceId || selection) return;
const match = spacesData?.items?.find((s) => s.id === initialSpaceId);
if (match) {
updateSelection({ type: "space", spaceId: match.id, space: match });
requestAnimationFrame(() => {
const el = viewportRef.current?.querySelector<HTMLElement>(
`[data-space-id="${match.id}"]`,
);
el?.scrollIntoView({ block: "nearest" });
});
}
}, [initialSpaceId, selection, spacesData, updateSelection]);
return ( return (
<> <>
<TextInput <TextInput
leftSection={<IconSearch size={16} />} leftSection={<IconSearch size={16} />}
placeholder={t("Search pages and spaces...")} placeholder={
searchSpacesOnly
? t("Search spaces...")
: t("Search pages and spaces...")
}
aria-label={
searchSpacesOnly
? t("Search spaces...")
: t("Search pages and spaces...")
}
variant="filled" variant="filled"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.currentTarget.value)} onChange={(e) => setSearchQuery(e.currentTarget.value)}
className={classes.searchInput} className={classes.searchInput}
/> />
<ScrollArea h="50vh" offsetScrollbars className={classes.scrollArea}> <ScrollArea
h="50vh"
offsetScrollbars
className={classes.scrollArea}
viewportRef={viewportRef}
>
{isSearching ? ( {isSearching ? (
searchLoading ? ( searchLoading ? (
<div className={classes.emptyState}> <div className={classes.emptyState}>
@@ -111,16 +159,28 @@ export function DestinationPicker({
<div <div
key={page.id} key={page.id}
className={classes.searchResult} className={classes.searchResult}
role="button"
tabIndex={0}
onClick={() => handleSearchResultClick(page)} onClick={() => handleSearchResultClick(page)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleSearchResultClick(page);
}
}}
> >
<div className={classes.iconWrapper}> <div className={classes.iconWrapper}>
{page.icon ? ( {page.icon ? (
page.icon page.icon
) : ( ) : (
<IconFile <ActionIcon
size={16} component="div"
color="var(--mantine-color-gray-5)" variant="transparent"
/> c="gray"
size={22}
>
<IconFileDescription size={18} />
</ActionIcon>
)} )}
</div> </div>
<div className={classes.pageTitle}> <div className={classes.pageTitle}>
@@ -141,8 +201,14 @@ export function DestinationPicker({
<div className={classes.emptyState}> <div className={classes.emptyState}>
<Loader size="xs" /> <Loader size="xs" />
</div> </div>
) : filteredSpaces.length === 0 ? (
<div className={classes.emptyState}>
{searchSpacesOnly && debouncedQuery
? t("No spaces found")
: t("No results found")}
</div>
) : ( ) : (
spacesData?.items?.map((space) => ( filteredSpaces.map((space) => (
<SpaceRow <SpaceRow
key={space.id} key={space.id}
space={space} space={space}
@@ -20,4 +20,6 @@ export type DestinationPickerModalProps = {
loading?: boolean; loading?: boolean;
excludePageId?: string; excludePageId?: string;
pageLimit?: number; pageLimit?: number;
initialSpaceId?: string;
searchSpacesOnly?: boolean;
}; };
@@ -74,7 +74,18 @@ export function PageChildren({
/> />
))} ))}
{hasNextPage && ( {hasNextPage && (
<div className={classes.loadMore} onClick={() => fetchNextPage()}> <div
className={classes.loadMore}
onClick={() => fetchNextPage()}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
fetchNextPage();
}
}}
role="button"
tabIndex={0}
>
{t("Load more")} {t("Load more")}
</div> </div>
)} )}
@@ -1,5 +1,6 @@
import { useState } from "react"; import { KeyboardEvent, useState } from "react";
import { IconChevronRight, IconFile } from "@tabler/icons-react"; import { ActionIcon } from "@mantine/core";
import { IconChevronRight, IconFileDescription } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { IPage } from "@/features/page/types/page.types"; import { IPage } from "@/features/page/types/page.types";
import { PageChildren } from "./page-children"; import { PageChildren } from "./page-children";
@@ -36,23 +37,44 @@ export function PageRow({
.filter(Boolean) .filter(Boolean)
.join(" "); .join(" ");
const handleSelect = () => {
if (!isExcluded) onSelect(page);
};
const handleRowKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
if (e.target !== e.currentTarget) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleSelect();
}
};
return ( return (
<> <>
<div <div
className={rowClasses} className={rowClasses}
style={{ paddingLeft: depth * 20 + 12 }} style={{ paddingLeft: depth * 20 + 12 }}
onClick={() => !isExcluded && onSelect(page)} role="button"
tabIndex={isExcluded ? -1 : 0}
aria-disabled={isExcluded || undefined}
onClick={handleSelect}
onKeyDown={handleRowKeyDown}
> >
{page.hasChildren ? ( {page.hasChildren ? (
<div <ActionIcon
className={`${classes.chevron} ${expanded ? classes.chevronExpanded : ""}`} className={`${classes.chevron} ${expanded ? classes.chevronExpanded : ""}`}
variant="subtle"
color="gray"
size="sm"
aria-label={expanded ? t("Collapse") : t("Expand")}
aria-expanded={expanded}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setExpanded(!expanded); setExpanded(!expanded);
}} }}
> >
<IconChevronRight size={14} /> <IconChevronRight size={14} />
</div> </ActionIcon>
) : ( ) : (
<div style={{ width: 20, flexShrink: 0 }} /> <div style={{ width: 20, flexShrink: 0 }} />
)} )}
@@ -61,10 +83,14 @@ export function PageRow({
{page.icon ? ( {page.icon ? (
page.icon page.icon
) : ( ) : (
<IconFile <ActionIcon
size={16} component="div"
color="var(--mantine-color-gray-5)" variant="transparent"
/> c="gray"
size={22}
>
<IconFileDescription size={18} />
</ActionIcon>
)} )}
</div> </div>
@@ -1,5 +1,5 @@
import { useState } from "react"; import { KeyboardEvent, useState } from "react";
import { Tooltip } from "@mantine/core"; import { ActionIcon, Tooltip } from "@mantine/core";
import { IconChevronRight, IconLock } from "@tabler/icons-react"; import { IconChevronRight, IconLock } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ISpace } from "@/features/space/types/space.types"; import { ISpace } from "@/features/space/types/space.types";
@@ -42,21 +42,43 @@ export function SpaceRow({
.filter(Boolean) .filter(Boolean)
.join(" "); .join(" ");
const handleSelect = () => {
if (writable) onSelectSpace(space);
};
const handleRowKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
if (e.target !== e.currentTarget) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleSelect();
}
};
const rowContent = ( const rowContent = (
<div <div
className={rowClasses} className={rowClasses}
onClick={() => writable && onSelectSpace(space)} data-space-id={space.id}
role="button"
tabIndex={writable ? 0 : -1}
aria-disabled={!writable || undefined}
onClick={handleSelect}
onKeyDown={handleRowKeyDown}
> >
{writable ? ( {writable ? (
<div <ActionIcon
className={`${classes.chevron} ${expanded ? classes.chevronExpanded : ""}`} className={`${classes.chevron} ${expanded ? classes.chevronExpanded : ""}`}
variant="subtle"
color="gray"
size="sm"
aria-label={expanded ? t("Collapse") : t("Expand")}
aria-expanded={expanded}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setExpanded(!expanded); setExpanded(!expanded);
}} }}
> >
<IconChevronRight size={14} /> <IconChevronRight size={14} />
</div> </ActionIcon>
) : ( ) : (
<div style={{ width: 20, flexShrink: 0 }} /> <div style={{ width: 20, flexShrink: 0 }} />
)} )}
+54 -3
View File
@@ -1,4 +1,4 @@
import React, { ReactNode, useState } from "react"; import React, { ReactNode, useEffect, useState } from "react";
import { import {
ActionIcon, ActionIcon,
Popover, Popover,
@@ -7,9 +7,24 @@ import {
} from "@mantine/core"; } from "@mantine/core";
import { useClickOutside, useDisclosure, useWindowEvent } from "@mantine/hooks"; import { useClickOutside, useDisclosure, useWindowEvent } from "@mantine/hooks";
import { Suspense } from "react"; import { Suspense } from "react";
const Picker = React.lazy(() => import("@emoji-mart/react"));
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
// Load the picker module AND the emoji data in parallel inside the lazy
// resolution, then bind the data into the component. React.lazy only finishes
// suspending once both are in memory, so the Suspense boundary hides the
// Remove button until the Picker can render with real content.
const Picker = React.lazy(async () => {
const [pickerModule, dataModule] = await Promise.all([
import("@slidoapp/emoji-mart-react"),
import("@slidoapp/emoji-mart-data"),
]);
const PickerComp = pickerModule.default;
const data = dataModule.default;
return {
default: (props: any) => <PickerComp {...props} data={data} />,
};
});
export interface EmojiPickerInterface { export interface EmojiPickerInterface {
onEmojiSelect: (emoji: any) => void; onEmojiSelect: (emoji: any) => void;
icon: ReactNode; icon: ReactNode;
@@ -19,6 +34,7 @@ export interface EmojiPickerInterface {
size?: string; size?: string;
variant?: string; variant?: string;
c?: string; c?: string;
tabIndex?: number;
}; };
} }
@@ -50,6 +66,38 @@ function EmojiPicker({
} }
}); });
// emoji-mart's built-in autoFocus calls .focus() without preventScroll, which
// makes the browser scroll every scrollable ancestor of the search input to
// bring it on screen — including the page editor's scroll container, so the
// page jumps to the top whenever the picker is opened from a scrolled-down
// position. The search input lives inside the <em-emoji-picker> custom
// element's shadow root, so we poll for it after the dropdown mounts and
// focus it ourselves with preventScroll.
useEffect(() => {
if (!opened || !dropdown) return;
let cancelled = false;
let rafId = 0;
const tryFocus = (attempts: number) => {
if (cancelled) return;
const pickerEl = dropdown.querySelector("em-emoji-picker");
const input = pickerEl?.shadowRoot?.querySelector<HTMLInputElement>(
'input[type="search"]',
);
if (input) {
input.focus({ preventScroll: true });
return;
}
if (attempts < 60) {
rafId = requestAnimationFrame(() => tryFocus(attempts + 1));
}
};
rafId = requestAnimationFrame(() => tryFocus(0));
return () => {
cancelled = true;
cancelAnimationFrame(rafId);
};
}, [opened, dropdown]);
const handleEmojiSelect = (emoji) => { const handleEmojiSelect = (emoji) => {
onEmojiSelect(emoji); onEmojiSelect(emoji);
handlers.close(); handlers.close();
@@ -74,7 +122,11 @@ function EmojiPicker({
c={actionIconProps?.c || "gray"} c={actionIconProps?.c || "gray"}
variant={actionIconProps?.variant || "transparent"} variant={actionIconProps?.variant || "transparent"}
size={actionIconProps?.size} size={actionIconProps?.size}
tabIndex={actionIconProps?.tabIndex}
onClick={handlers.toggle} onClick={handlers.toggle}
aria-label={t("Pick emoji")}
aria-haspopup="dialog"
aria-expanded={opened}
> >
{icon} {icon}
</ActionIcon> </ActionIcon>
@@ -82,7 +134,6 @@ function EmojiPicker({
<Suspense fallback={null}> <Suspense fallback={null}>
<Popover.Dropdown bg="000" style={{ border: "none" }} ref={setDropdown}> <Popover.Dropdown bg="000" style={{ border: "none" }} ref={setDropdown}>
<Picker <Picker
data={async () => (await import("@emoji-mart/data")).default}
onEmojiSelect={handleEmojiSelect} onEmojiSelect={handleEmojiSelect}
perLine={8} perLine={8}
skinTonePosition="search" skinTonePosition="search"
@@ -14,7 +14,14 @@ export interface SidebarToggleProps extends BoxProps, ElementProps<"button"> {
const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>( const SidebarToggle = React.forwardRef<HTMLButtonElement, SidebarToggleProps>(
({ opened, size = "sm", ...others }, ref) => { ({ opened, size = "sm", ...others }, ref) => {
return ( return (
<ActionIcon size={size} {...others} variant="subtle" color="gray" ref={ref}> <ActionIcon
size={size}
aria-expanded={opened}
{...others}
variant="subtle"
color="gray"
ref={ref}
>
{opened ? ( {opened ? (
<IconLayoutSidebarRightExpand /> <IconLayoutSidebarRightExpand />
) : ( ) : (
@@ -0,0 +1,27 @@
.skipLink {
position: absolute;
top: 8px;
left: 8px;
z-index: 9999;
padding: 8px 16px;
background: var(--mantine-color-body);
color: var(--mantine-color-text);
border: 2px solid var(--mantine-color-blue-6);
border-radius: 4px;
text-decoration: none;
font-weight: 500;
font-size: var(--mantine-font-size-sm);
transform: translateY(-200%);
transition: transform 0.15s ease-out;
}
.skipLink:focus {
transform: translateY(0);
outline: none;
}
@media print {
.skipLink {
display: none !important;
}
}
@@ -0,0 +1,13 @@
import { useTranslation } from "react-i18next";
import classes from "./skip-to-main.module.css";
export const MAIN_CONTENT_ID = "main-content";
export function SkipToMain() {
const { t } = useTranslation();
return (
<a href={`#${MAIN_CONTENT_ID}`} className={classes.skipLink}>
{t("Skip to main content")}
</a>
);
}
@@ -132,6 +132,7 @@ export default function AiChatSidebarItem({
size="xs" size="xs"
color="gray" color="gray"
onClick={(e) => e.preventDefault()} onClick={(e) => e.preventDefault()}
aria-label={t("Chat menu")}
> >
<IconDots size={14} /> <IconDots size={14} />
</ActionIcon> </ActionIcon>
@@ -120,7 +120,7 @@ export default function AiChatSidebar() {
return ( return (
<div className={classes.sidebar}> <div className={classes.sidebar}>
<div className={classes.header}> <div className={classes.header}>
<span className={classes.title}>{t("AI Chat")}</span> <h2 className={classes.title}>{t("AI Chat")}</h2>
<Tooltip label={t("New chat")} openDelay={250} withArrow> <Tooltip label={t("New chat")} openDelay={250} withArrow>
<ActionIcon <ActionIcon
component={Link} component={Link}
@@ -137,7 +137,8 @@ export default function AiChatSidebar() {
<TextInput <TextInput
className={classes.searchInput} className={classes.searchInput}
placeholder="Search chats..." placeholder={t("Search chats...")}
aria-label={t("Search chats")}
leftSection={<IconSearch size={14} />} leftSection={<IconSearch size={14} />}
size="xs" size="xs"
value={search} value={search}
@@ -175,7 +176,7 @@ export default function AiChatSidebar() {
)) ))
: groupedChats.map((group) => ( : groupedChats.map((group) => (
<div key={group.key} className={classes.chatGroup}> <div key={group.key} className={classes.chatGroup}>
<div className={classes.chatGroupLabel}>{group.label}</div> <h3 className={classes.chatGroupLabel}>{group.label}</h3>
{group.chats.map((chat) => ( {group.chats.map((chat) => (
<AiChatSidebarItem <AiChatSidebarItem
key={chat.id} key={chat.id}
@@ -178,6 +178,7 @@ export default function AsideChatPanel() {
href="/ai" href="/ai"
variant="subtle" variant="subtle"
color="dark" color="dark"
aria-label={t("New chat")}
onClick={handleNewChat} onClick={handleNewChat}
> >
<IconPlus size={20} stroke={1.75} /> <IconPlus size={20} stroke={1.75} />
@@ -185,13 +186,23 @@ export default function AsideChatPanel() {
</Tooltip> </Tooltip>
<Tooltip label={t("Open full page")} openDelay={250}> <Tooltip label={t("Open full page")} openDelay={250}>
<ActionIcon variant="subtle" color="dark" onClick={handleExpand}> <ActionIcon
variant="subtle"
color="dark"
aria-label={t("Open full page")}
onClick={handleExpand}
>
<IconArrowsDiagonal size={18} stroke={1.5} /> <IconArrowsDiagonal size={18} stroke={1.5} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip label={t("Close")} openDelay={250}> <Tooltip label={t("Close")} openDelay={250}>
<ActionIcon variant="subtle" color="dark" onClick={handleClose}> <ActionIcon
variant="subtle"
color="dark"
aria-label={t("Close")}
onClick={handleClose}
>
<IconX size={20} stroke={1.75} /> <IconX size={20} stroke={1.75} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
@@ -56,22 +56,22 @@ export default function ChatEmptyState({ isStreaming, onSend, onStop }: Props) {
<div className={classes.emptyState}> <div className={classes.emptyState}>
<IconSparkles size={48} stroke={1.5} className={classes.emptyStateIcon} /> <IconSparkles size={48} stroke={1.5} className={classes.emptyStateIcon} />
<div className={classes.emptyStateBrand}>{t("Docmost AI")}</div> <div className={classes.emptyStateBrand}>{t("Docmost AI")}</div>
<div className={classes.emptyStateTitle}> <h1 className={classes.emptyStateTitle}>
{t("What can I help you with?")} {t("What can I help you with?")}
</div> </h1>
<div className={classes.emptyStateInput}> <div className={classes.emptyStateInput}>
<ChatInput <ChatInput
isStreaming={isStreaming} isStreaming={isStreaming}
onSend={onSend} onSend={onSend}
onStop={onStop} onStop={onStop}
placeholder="Ask anything... Use @ to mention pages" placeholder={t("Ask anything... Use @ to mention pages")}
autofocus autofocus
/> />
</div> </div>
<div className={classes.suggestionsSection}> <div className={classes.suggestionsSection}>
<div className={classes.suggestionsLabel}>Get started</div> <h2 className={classes.suggestionsLabel}>{t("Get started")}</h2>
<div className={classes.suggestionsGrid}> <div className={classes.suggestionsGrid}>
{SUGGESTIONS.map((s) => ( {SUGGESTIONS.map((s) => (
<button <button
@@ -200,7 +200,7 @@ export default function ChatInput({
link: false, link: false,
}), }),
Placeholder.configure({ Placeholder.configure({
placeholder: placeholder || "Ask anything... Use @ to mention pages", placeholder: placeholder || t("Ask anything... Use @ to mention pages"),
}), }),
CharacterCount.configure({ CharacterCount.configure({
limit: 50000, limit: 50000,
@@ -225,6 +225,11 @@ export default function ChatInput({
}), }),
], ],
editorProps: { editorProps: {
attributes: {
role: "textbox",
"aria-label": placeholder || t("Ask anything... Use @ to mention pages"),
"aria-multiline": "true",
},
handleDOMEvents: { handleDOMEvents: {
keydown: (_view, event) => { keydown: (_view, event) => {
if ( if (
@@ -275,6 +280,8 @@ export default function ChatInput({
type="file" type="file"
accept={ACCEPTED_FILE_TYPES} accept={ACCEPTED_FILE_TYPES}
multiple multiple
aria-label={t("Add files")}
tabIndex={-1}
style={{ display: "none" }} style={{ display: "none" }}
onChange={(e) => handleFileSelect(e.target.files)} onChange={(e) => handleFileSelect(e.target.files)}
/> />
@@ -329,7 +336,15 @@ export default function ChatInput({
<EditorContent editor={editor} className={classes.editorContent} /> <EditorContent editor={editor} className={classes.editorContent} />
<div className={classes.actions}> <div className={classes.actions}>
<Popover opened={plusMenuOpen} onChange={setPlusMenuOpen} position="top-start" width={220} shadow="md"> <Popover
opened={plusMenuOpen}
onChange={setPlusMenuOpen}
position="top-start"
width={220}
shadow="md"
trapFocus
returnFocus
>
<Popover.Target> <Popover.Target>
<button <button
type="button" type="button"
@@ -2,6 +2,7 @@ import { useEffect, useRef, useCallback, useState } from "react";
import { ErrorBoundary } from "react-error-boundary"; import { ErrorBoundary } from "react-error-boundary";
import { IconArrowDown, IconAlertTriangle } from "@tabler/icons-react"; import { IconArrowDown, IconAlertTriangle } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { VisuallyHidden } from "@mantine/core";
import type { AiChatMessage, AiChatToolCall } from "../types/ai-chat.types"; import type { AiChatMessage, AiChatToolCall } from "../types/ai-chat.types";
import ChatMessage from "./chat-message"; import ChatMessage from "./chat-message";
import classes from "../styles/ai-chat.module.css"; import classes from "../styles/ai-chat.module.css";
@@ -33,6 +34,7 @@ export default function ChatMessageList({
streamingContent, streamingContent,
streamingToolCalls, streamingToolCalls,
}: Props) { }: Props) {
const { t } = useTranslation();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const bottomRef = useRef<HTMLDivElement>(null); const bottomRef = useRef<HTMLDivElement>(null);
const isAtBottomRef = useRef(true); const isAtBottomRef = useRef(true);
@@ -40,6 +42,38 @@ export default function ChatMessageList({
const prevScrollTopRef = useRef(0); const prevScrollTopRef = useRef(0);
const [showScrollButton, setShowScrollButton] = useState(false); const [showScrollButton, setShowScrollButton] = useState(false);
// Dedicated status-region announcement for screen readers. Rather than
// putting aria-live on the whole transcript (which re-fires for every
// streamed token), announce "AI is thinking…" when streaming starts and
// the full assistant reply once streaming completes — a single, clean read.
const [statusAnnouncement, setStatusAnnouncement] = useState("");
const wasStreamingRef = useRef(false);
useEffect(() => {
const justStartedStreaming = isStreaming && !wasStreamingRef.current;
const justFinishedStreaming = !isStreaming && wasStreamingRef.current;
if (justStartedStreaming) {
setStatusAnnouncement(t("AI is thinking..."));
} else if (justFinishedStreaming) {
const lastMessage = messages[messages.length - 1];
if (lastMessage?.role === "assistant" && lastMessage.content) {
// Strip markdown punctuation so screen readers don't read symbols
// like # * _ ` ~ aloud. A plain-text version is fine — the styled
// version stays in the DOM for visual users.
const plainText = lastMessage.content
.replace(/[#*_`~]/g, "")
.replace(/\s+/g, " ")
.trim();
setStatusAnnouncement(plainText);
} else {
setStatusAnnouncement("");
}
}
wasStreamingRef.current = isStreaming;
}, [isStreaming, messages, t]);
const scrollToBottom = useCallback((behavior: ScrollBehavior = "smooth") => { const scrollToBottom = useCallback((behavior: ScrollBehavior = "smooth") => {
const container = containerRef.current; const container = containerRef.current;
if (!container) return; if (!container) return;
@@ -127,7 +161,18 @@ export default function ChatMessageList({
return ( return (
<div className={classes.messageListWrapper}> <div className={classes.messageListWrapper}>
<div ref={containerRef} className={classes.messageList}> {/* Single status region for chat announcements. Kept outside the
scrolling transcript so changes here trigger one polite read per
state change instead of re-announcing every streamed token. */}
<VisuallyHidden role="status" aria-live="polite">
{statusAnnouncement}
</VisuallyHidden>
<div
ref={containerRef}
className={classes.messageList}
aria-label={t("Chat transcript")}
>
{messages.map((msg) => ( {messages.map((msg) => (
<ErrorBoundary <ErrorBoundary
key={msg.id} key={msg.id}
@@ -162,7 +207,7 @@ export default function ChatMessageList({
{showScrollButton && ( {showScrollButton && (
<button <button
type="button" type="button"
aria-label="Scroll to bottom" aria-label={t("Scroll to bottom")}
className={classes.scrollToBottomButton} className={classes.scrollToBottomButton}
onClick={() => scrollToBottom("smooth")} onClick={() => scrollToBottom("smooth")}
> >
@@ -1,5 +1,6 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { useTranslation } from "react-i18next";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { ActionIcon, Tooltip } from "@mantine/core"; import { ActionIcon, Tooltip } from "@mantine/core";
import { import {
@@ -43,6 +44,7 @@ export default function ChatMessage({
streamingToolCalls, streamingToolCalls,
}: Props) { }: Props) {
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation();
const handleContentClick = useCallback( const handleContentClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => { (e: React.MouseEvent<HTMLDivElement>) => {
@@ -78,7 +80,11 @@ export default function ChatMessage({
}[]) || []; }[]) || [];
return ( return (
<div className={classes.userMessage}> <div
className={classes.userMessage}
role="article"
aria-label={t("You said:")}
>
<div className={classes.userBubble}> <div className={classes.userBubble}>
{attachments.length > 0 && ( {attachments.length > 0 && (
<div className={classes.messageAttachments}> <div className={classes.messageAttachments}>
@@ -100,8 +106,16 @@ export default function ChatMessage({
); );
} }
// Only label the article when there's something meaningful to announce.
// Tool-only assistant turns (no text) shouldn't announce "Assistant said:" with empty content.
const hasAnnouncableContent = Boolean(content);
return ( return (
<div className={classes.assistantMessage}> <div
className={classes.assistantMessage}
role="article"
aria-label={hasAnnouncableContent ? t("Assistant said:") : undefined}
>
<div className={classes.messageContent}> <div className={classes.messageContent}>
{toolCalls && toolCalls.length > 0 && ( {toolCalls && toolCalls.length > 0 && (
<ChatToolGroup toolCalls={toolCalls} isStreaming={isStreaming} /> <ChatToolGroup toolCalls={toolCalls} isStreaming={isStreaming} />
@@ -131,7 +145,10 @@ export default function ChatMessage({
</div> </div>
{!isStreaming && message.content && ( {!isStreaming && message.content && (
<div className={classes.messageActions}> <div className={classes.messageActions}>
<CopyTextButton text={message?.content} /> <CopyTextButton
text={message?.content}
label={t("Copy assistant response")}
/>
</div> </div>
)} )}
</div> </div>
@@ -31,7 +31,16 @@ export default function ChatToolGroup({ toolCalls, isStreaming }: Props) {
<div className={classes.toolGroup}> <div className={classes.toolGroup}>
<div <div
className={classes.toolGroupHeader} className={classes.toolGroupHeader}
role="button"
tabIndex={0}
aria-expanded={expanded}
onClick={() => setExpanded((prev) => !prev)} onClick={() => setExpanded((prev) => !prev)}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setExpanded((prev) => !prev);
}
}}
> >
{activeLabel ? ( {activeLabel ? (
<IconLoader2 size={12} className={classes.processingSpinner} /> <IconLoader2 size={12} className={classes.processingSpinner} />
@@ -98,7 +98,7 @@
font-weight: 600; font-weight: 600;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2)); color: var(--mantine-color-dimmed);
margin-bottom: var(--mantine-spacing-xs); margin-bottom: var(--mantine-spacing-xs);
} }
@@ -106,6 +106,7 @@
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0)); color: light-dark(var(--mantine-color-gray-8), var(--mantine-color-dark-0));
margin-top: 0;
margin-bottom: var(--mantine-spacing-xl); margin-bottom: var(--mantine-spacing-xl);
text-align: center; text-align: center;
} }
@@ -125,9 +126,10 @@
.suggestionsLabel { .suggestionsLabel {
font-size: var(--mantine-font-size-xs); font-size: var(--mantine-font-size-xs);
font-weight: 500; font-weight: 500;
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); color: var(--mantine-color-dimmed);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
margin-top: 0;
margin-bottom: var(--mantine-spacing-sm); margin-bottom: var(--mantine-spacing-sm);
} }
@@ -43,7 +43,7 @@
margin-top: 6px; margin-top: 6px;
text-align: center; text-align: center;
font-size: var(--mantine-font-size-xs); font-size: var(--mantine-font-size-xs);
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); color: var(--mantine-color-dimmed);
} }
.attachmentChips { .attachmentChips {
@@ -114,7 +114,7 @@
} }
:global(.ProseMirror p.is-editor-empty:first-child::before) { :global(.ProseMirror p.is-editor-empty:first-child::before) {
color: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-3)); color: var(--mantine-color-placeholder);
content: attr(data-placeholder); content: attr(data-placeholder);
float: left; float: left;
height: 0; height: 0;
@@ -183,7 +183,7 @@
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
background: none; background: none;
cursor: pointer; cursor: pointer;
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-3));
transition: color 150ms, background-color 150ms; transition: color 150ms, background-color 150ms;
@mixin hover { @mixin hover {
@@ -15,6 +15,7 @@
} }
.title { .title {
margin: 0;
font-weight: 600; font-weight: 600;
font-size: var(--mantine-font-size-sm); font-size: var(--mantine-font-size-sm);
} }
@@ -33,10 +34,11 @@
} }
.chatGroupLabel { .chatGroupLabel {
margin: 0;
padding: 4px var(--mantine-spacing-xs); padding: 4px var(--mantine-spacing-xs);
font-size: var(--mantine-font-size-xs); font-size: var(--mantine-font-size-xs);
font-weight: 600; font-weight: 600;
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2)); color: var(--mantine-color-dimmed);
user-select: none; user-select: none;
} }
@@ -104,7 +106,7 @@
.chatItemDate { .chatItemDate {
font-size: var(--mantine-font-size-xs); font-size: var(--mantine-font-size-xs);
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); color: var(--mantine-color-dimmed);
white-space: nowrap; white-space: nowrap;
transition: opacity 150ms; transition: opacity 150ms;
} }
@@ -118,7 +120,8 @@
color: inherit; color: inherit;
} }
.chatItem:hover .chatItemDate { .chatItem:hover .chatItemDate,
.chatItem:focus-within .chatItemDate {
opacity: 0; opacity: 0;
} }
@@ -133,6 +136,12 @@
position: relative; position: relative;
} }
.chatItem:hover .chatItemActions { .chatItem:hover .chatItemActions,
.chatItem:focus-within .chatItemActions {
opacity: 1; opacity: 1;
} }
.chatItemActions :global(.mantine-ActionIcon-root):focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
}
@@ -33,6 +33,7 @@ export function ApiKeyCreatedModal({
onClose={onClose} onClose={onClose}
title={t("{{credential}} created", { credential: t("API key") })} title={t("{{credential}} created", { credential: t("API key") })}
size="lg" size="lg"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<Stack gap="md"> <Stack gap="md">
<Alert <Alert
@@ -44,7 +44,7 @@ export function ApiKeyTable({
<Table.Th>{t("Last used")}</Table.Th> <Table.Th>{t("Last used")}</Table.Th>
<Table.Th>{t("Expires")}</Table.Th> <Table.Th>{t("Expires")}</Table.Th>
<Table.Th>{t("Created")}</Table.Th> <Table.Th>{t("Created")}</Table.Th>
<Table.Th></Table.Th> <Table.Th aria-label={t("Action")} />
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
@@ -106,7 +106,11 @@ export function ApiKeyTable({
<Table.Td> <Table.Td>
<Menu position="bottom-end" withinPortal> <Menu position="bottom-end" withinPortal>
<Menu.Target> <Menu.Target>
<ActionIcon variant="subtle" color="gray"> <ActionIcon
variant="subtle"
color="gray"
aria-label={t("API key menu")}
>
<IconDots size={16} /> <IconDots size={16} />
</ActionIcon> </ActionIcon>
</Menu.Target> </Menu.Target>
@@ -107,6 +107,7 @@ export function CreateApiKeyModal({
onClose={handleClose} onClose={handleClose}
title={t("Create {{credential}}", { credential: t("API key") })} title={t("Create {{credential}}", { credential: t("API key") })}
size="md" size="md"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}> <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Stack gap="md"> <Stack gap="md">
@@ -32,6 +32,7 @@ export function RevokeApiKeyModal({
onClose={onClose} onClose={onClose}
title={t("Revoke {{credential}}", { credential: t("API key") })} title={t("Revoke {{credential}}", { credential: t("API key") })}
size="md" size="md"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<Stack gap="md"> <Stack gap="md">
<Text> <Text>
@@ -55,6 +55,7 @@ export function UpdateApiKeyModal({
onClose={onClose} onClose={onClose}
title={t("Update {{credential}}", { credential: t("API key") })} title={t("Update {{credential}}", { credential: t("API key") })}
size="md" size="md"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}> <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Stack gap="md"> <Stack gap="md">
@@ -111,6 +111,11 @@ export function LdapLoginModal({
placeholder={t("Enter your LDAP password")} placeholder={t("Enter your LDAP password")}
variant="filled" variant="filled"
disabled={isLoading} disabled={isLoading}
visibilityToggleButtonProps={{
"aria-label": t("Toggle password visibility"),
"aria-hidden": false,
tabIndex: 0,
}}
{...form.getInputProps("password")} {...form.getInputProps("password")}
/> />
+64 -5
View File
@@ -1,4 +1,4 @@
import { useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useWorkspacePublicDataQuery } from "@/features/workspace/queries/workspace-query.ts"; import { useWorkspacePublicDataQuery } from "@/features/workspace/queries/workspace-query.ts";
import { Button, Divider, Stack } from "@mantine/core"; import { Button, Divider, Stack } from "@mantine/core";
import { IconLock, IconServer } from "@tabler/icons-react"; import { IconLock, IconServer } from "@tabler/icons-react";
@@ -7,15 +7,37 @@ import { buildSsoLoginUrl } from "@/ee/security/sso.utils.ts";
import { SSO_PROVIDER } from "@/ee/security/contants.ts"; import { SSO_PROVIDER } from "@/ee/security/contants.ts";
import { GoogleIcon } from "@/components/icons/google-icon.tsx"; import { GoogleIcon } from "@/components/icons/google-icon.tsx";
import { LdapLoginModal } from "@/ee/components/ldap-login-modal.tsx"; import { LdapLoginModal } from "@/ee/components/ldap-login-modal.tsx";
import { getRedirectParam } from "@/lib/app-route.ts";
import useCurrentUser from "@/features/user/hooks/use-current-user.ts";
const SSO_AUTO_ATTEMPT_KEY = "docmost:ssoAutoAttempt";
const SSO_AUTO_ATTEMPT_TTL_MS = 5 * 60_000;
function recentAutoAttempt(): boolean {
try {
const raw = window.sessionStorage.getItem(SSO_AUTO_ATTEMPT_KEY);
if (!raw) return false;
const ts = Number(raw);
return Number.isFinite(ts) && Date.now() - ts < SSO_AUTO_ATTEMPT_TTL_MS;
} catch {
return false;
}
}
function markAutoAttempt(): void {
try {
window.sessionStorage.setItem(SSO_AUTO_ATTEMPT_KEY, String(Date.now()));
} catch {
/* sessionStorage unavailable (private mode, etc.) — best effort */
}
}
export default function SsoLogin() { export default function SsoLogin() {
const { data, isLoading } = useWorkspacePublicDataQuery(); const { data, isLoading } = useWorkspacePublicDataQuery();
const { data: currentUser } = useCurrentUser();
const [ldapModalOpened, setLdapModalOpened] = useState(false); const [ldapModalOpened, setLdapModalOpened] = useState(false);
const [selectedLdapProvider, setSelectedLdapProvider] = useState<IAuthProvider | null>(null); const [selectedLdapProvider, setSelectedLdapProvider] = useState<IAuthProvider | null>(null);
const autoRedirectedRef = useRef(false);
if (!data?.authProviders || data?.authProviders?.length === 0) {
return null;
}
const handleSsoLogin = (provider: IAuthProvider) => { const handleSsoLogin = (provider: IAuthProvider) => {
if (provider.type === SSO_PROVIDER.LDAP) { if (provider.type === SSO_PROVIDER.LDAP) {
@@ -28,10 +50,47 @@ export default function SsoLogin() {
providerId: provider.id, providerId: provider.id,
type: provider.type, type: provider.type,
workspaceId: data.id, workspaceId: data.id,
redirect: getRedirectParam() ?? undefined,
}); });
} }
}; };
// Auto-redirect when SSO is enforced and there is exactly one non-LDAP
// provider. The user has no other option, so skip the extra click.
useEffect(() => {
if (autoRedirectedRef.current) return;
if (!data?.enforceSso) return;
if (!data.authProviders || data.authProviders.length !== 1) return;
const onlyProvider = data.authProviders[0];
if (onlyProvider.type === SSO_PROVIDER.LDAP) return;
// Already signed in: let useRedirectIfAuthenticated handle navigation
// instead of racing it through the IdP.
if (currentUser?.user) return;
// Explicit logout: don't immediately bounce them back to the IdP.
const params = new URLSearchParams(window.location.search);
if (params.has("logout")) return;
// Circuit-breaker: if we already auto-redirected within the TTL, the
// user came back (likely from an IdP failure). Show the page so they
// can read errors or pick a different account.
if (recentAutoAttempt()) return;
autoRedirectedRef.current = true;
markAutoAttempt();
window.location.href = buildSsoLoginUrl({
providerId: onlyProvider.id,
type: onlyProvider.type,
workspaceId: data.id,
redirect: getRedirectParam() ?? undefined,
});
}, [data, currentUser]);
if (!data?.authProviders || data?.authProviders?.length === 0) {
return null;
}
const getProviderIcon = (provider: IAuthProvider) => { const getProviderIcon = (provider: IAuthProvider) => {
if (provider.type === SSO_PROVIDER.GOOGLE) { if (provider.type === SSO_PROVIDER.GOOGLE) {
return <GoogleIcon size={16} />; return <GoogleIcon size={16} />;
@@ -130,6 +130,11 @@ export function MfaBackupCodesModal({
label={t("Confirm password")} label={t("Confirm password")}
placeholder={t("Enter your password")} placeholder={t("Enter your password")}
variant="filled" variant="filled"
visibilityToggleButtonProps={{
"aria-label": t("Toggle password visibility"),
"aria-hidden": false,
tabIndex: 0,
}}
{...form.getInputProps("confirmPassword")} {...form.getInputProps("confirmPassword")}
autoFocus autoFocus
data-autofocus data-autofocus
@@ -107,6 +107,11 @@ export function MfaDisableModal({
<PasswordInput <PasswordInput
label={t("Password")} label={t("Password")}
placeholder={t("Enter your password")} placeholder={t("Enter your password")}
visibilityToggleButtonProps={{
"aria-label": t("Toggle password visibility"),
"aria-hidden": false,
tabIndex: 0,
}}
{...form.getInputProps("confirmPassword")} {...form.getInputProps("confirmPassword")}
autoFocus autoFocus
data-autofocus data-autofocus
@@ -79,7 +79,13 @@ export function PageShareModal({ readOnly }: PageShareModalProps) {
{t("Share")} {t("Share")}
</Button> </Button>
<Modal opened={opened} onClose={close} title={t("Share")} size={600}> <Modal
opened={opened}
onClose={close}
title={t("Share")}
size={600}
closeButtonProps={{ "aria-label": t("Close") }}
>
<Tabs value={activeTab} color="dark" onChange={setActiveTab}> <Tabs value={activeTab} color="dark" onChange={setActiveTab}>
<Tabs.List mb="md"> <Tabs.List mb="md">
<Tabs.Tab value="access">{t("Access")}</Tabs.Tab> <Tabs.Tab value="access">{t("Access")}</Tabs.Tab>
@@ -1,4 +1,12 @@
import { ActionIcon, Group, Menu, Modal, Text, Tooltip } from "@mantine/core"; import {
ActionIcon,
Group,
Menu,
Modal,
Text,
Tooltip,
UnstyledButton,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks"; import { useDisclosure } from "@mantine/hooks";
import { import {
IconRosetteDiscountCheckFilled, IconRosetteDiscountCheckFilled,
@@ -38,6 +46,7 @@ export function PageVerificationModal({
<Modal <Modal
opened={opened} opened={opened}
onClose={onClose} onClose={onClose}
aria-label={status === "none" ? t("Set up verification") : t("Verify page")}
title={ title={
<Group gap="xs"> <Group gap="xs">
<IconShieldCheck <IconShieldCheck
@@ -91,13 +100,18 @@ export function PageVerificationBadge({
if (!pageId) return null; if (!pageId) return null;
if (!hasVerificationFeature) { if (!hasVerificationFeature) {
if (readOnly) return null; if (readOnly) return null;
const lockedLabel = `${t("Add verification")}${upgradeLabel}`;
// Use ActionIcon (a real <button>) instead of a ThemeIcon so the tooltip
// is reachable on keyboard focus, and screen readers announce the upgrade
// hint via the accessible name. Click is a no-op since the feature is
// gated; the tooltip explains why.
return ( return (
<Tooltip <Tooltip label={lockedLabel} withArrow openDelay={250}>
label={`${t("Add verification")}${upgradeLabel}`} <ActionIcon
withArrow variant="subtle"
openDelay={250} color="gray"
aria-label={lockedLabel}
> >
<ActionIcon variant="subtle" color="gray">
<IconShieldCheck size={20} stroke={1.5} /> <IconShieldCheck size={20} stroke={1.5} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
@@ -109,28 +123,48 @@ export function PageVerificationBadge({
if (status === "none" && readOnly) return null; if (status === "none" && readOnly) return null;
const tooltipLabel =
status === "verified" && verificationInfo?.expiresAt
? t("Verified until {{date}}", {
date: new Date(verificationInfo.expiresAt).toLocaleDateString(
undefined,
{ month: "long", day: "numeric", year: "numeric" },
),
})
: getStatusLabel(status, t);
return ( return (
<> <>
{status !== "none" ? ( {status !== "none" ? (
<Tooltip label={getStatusLabel(status, t)} withArrow openDelay={250}> <Tooltip label={tooltipLabel} withArrow openDelay={250}>
<Group <UnstyledButton
gap={4}
onClick={open} onClick={open}
style={{ cursor: "pointer" }} aria-label={tooltipLabel}
wrap="nowrap" style={{
display: "inline-flex",
alignItems: "center",
gap: 4,
cursor: "pointer",
}}
> >
<IconRosetteDiscountCheckFilled <IconRosetteDiscountCheckFilled
size={18} size={18}
color={`var(--mantine-color-${getStatusColor(status).replace(".", "-")})`} color={`var(--mantine-color-${getStatusColor(status).replace(".", "-")})`}
aria-hidden="true"
/> />
<Text size="sm" c={getStatusColor(status)}> <Text size="sm" c={getStatusColor(status)}>
{getStatusLabel(status, t)} {getStatusLabel(status, t)}
</Text> </Text>
</Group> </UnstyledButton>
</Tooltip> </Tooltip>
) : !readOnly ? ( ) : !readOnly ? (
<Tooltip label={t("Set up verification")} withArrow openDelay={250}> <Tooltip label={t("Set up verification")} withArrow openDelay={250}>
<ActionIcon variant="subtle" color="gray" onClick={open}> <ActionIcon
variant="subtle"
color="gray"
aria-label={t("Set up verification")}
onClick={open}
>
<IconShieldCheck size={20} stroke={1.5} /> <IconShieldCheck size={20} stroke={1.5} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
@@ -18,6 +18,7 @@ import { CustomAvatar } from "@/components/ui/custom-avatar";
import { buildPageUrl } from "@/features/page/page.utils"; import { buildPageUrl } from "@/features/page/page.utils";
import { format } from "date-fns"; import { format } from "date-fns";
import NoTableResults from "@/components/common/no-table-results"; import NoTableResults from "@/components/common/no-table-results";
import rowClasses from "@/components/ui/clickable-table-row.module.css";
const MAX_VISIBLE_VERIFIERS = 5; const MAX_VISIBLE_VERIFIERS = 5;
@@ -124,12 +125,13 @@ export default function VerificationListTable({
); );
return ( return (
<Table.Tr key={item.id}> <Table.Tr key={item.id} className={rowClasses.row}>
<Table.Td> <Table.Td>
<Anchor <Anchor
size="sm" size="sm"
underline="never" underline="never"
style={{ color: "var(--mantine-color-text)" }} style={{ color: "var(--mantine-color-text)" }}
className={rowClasses.link}
component={Link} component={Link}
to={pageUrl} to={pageUrl}
> >
@@ -52,6 +52,7 @@ export function CreateScimTokenModal({
onClose={handleClose} onClose={handleClose}
title={t("Create {{credential}}", { credential: t("SCIM token") })} title={t("Create {{credential}}", { credential: t("SCIM token") })}
size="md" size="md"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}> <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Stack gap="md"> <Stack gap="md">
@@ -29,6 +29,7 @@ export function RevokeScimTokenModal({
onClose={onClose} onClose={onClose}
title={t("Revoke {{credential}}", { credential: t("SCIM token") })} title={t("Revoke {{credential}}", { credential: t("SCIM token") })}
size="md" size="md"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<Stack gap="md"> <Stack gap="md">
<Text> <Text>
@@ -32,6 +32,7 @@ export function ScimTokenCreatedModal({
onClose={onClose} onClose={onClose}
title={t("{{credential}} created", { credential: t("SCIM token") })} title={t("{{credential}} created", { credential: t("SCIM token") })}
size="lg" size="lg"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<Stack gap="md"> <Stack gap="md">
<Alert <Alert
@@ -37,7 +37,7 @@ export function ScimTokenTable({
<Table.Th>{t("Created by")}</Table.Th> <Table.Th>{t("Created by")}</Table.Th>
<Table.Th>{t("Last used")}</Table.Th> <Table.Th>{t("Last used")}</Table.Th>
<Table.Th>{t("Created")}</Table.Th> <Table.Th>{t("Created")}</Table.Th>
<Table.Th></Table.Th> <Table.Th aria-label={t("Action")} />
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
@@ -93,7 +93,11 @@ export function ScimTokenTable({
<Table.Td> <Table.Td>
<Menu position="bottom-end" withinPortal> <Menu position="bottom-end" withinPortal>
<Menu.Target> <Menu.Target>
<ActionIcon variant="subtle" color="gray"> <ActionIcon
variant="subtle"
color="gray"
aria-label={t("Token actions")}
>
<IconDots size={16} /> <IconDots size={16} />
</ActionIcon> </ActionIcon>
</Menu.Target> </Menu.Target>
@@ -52,6 +52,7 @@ export function UpdateScimTokenModal({
onClose={onClose} onClose={onClose}
title={t("Update {{credential}}", { credential: t("SCIM token") })} title={t("Update {{credential}}", { credential: t("SCIM token") })}
size="md" size="md"
closeButtonProps={{ "aria-label": t("Close") }}
> >
<form onSubmit={form.onSubmit((values) => handleSubmit(values))}> <form onSubmit={form.onSubmit((values) => handleSubmit(values))}>
<Stack gap="md"> <Stack gap="md">
@@ -34,7 +34,7 @@ function AllowMemberTemplatesToggle() {
const [checked, setChecked] = useState( const [checked, setChecked] = useState(
workspace?.settings?.templates?.allowMemberTemplates === true, workspace?.settings?.templates?.allowMemberTemplates === true,
); );
const hasSecuritySettings = useHasFeature(Feature.SECURITY_SETTINGS); const hasTemplates = useHasFeature(Feature.TEMPLATES);
const upgradeLabel = useUpgradeLabel(); const upgradeLabel = useUpgradeLabel();
const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => { const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -54,15 +54,11 @@ function AllowMemberTemplatesToggle() {
}; };
return ( return (
<Tooltip <Tooltip label={upgradeLabel} disabled={hasTemplates} refProp="rootRef">
label={upgradeLabel}
disabled={hasSecuritySettings}
refProp="rootRef"
>
<Switch <Switch
checked={checked} checked={checked}
onChange={handleChange} onChange={handleChange}
disabled={!hasSecuritySettings} disabled={!hasTemplates}
aria-label={t("Toggle allow members to create templates")} aria-label={t("Toggle allow members to create templates")}
/> />
</Tooltip> </Tooltip>
@@ -141,6 +141,7 @@ export default function SsoProviderList() {
<ActionIcon <ActionIcon
variant="subtle" variant="subtle"
color="gray" color="gray"
aria-label={t("Edit {{name}}", { name: provider.name })}
onClick={() => handleEdit(provider)} onClick={() => handleEdit(provider)}
> >
<IconPencil size={16} /> <IconPencil size={16} />
@@ -152,7 +153,13 @@ export default function SsoProviderList() {
withinPortal withinPortal
> >
<Menu.Target> <Menu.Target>
<ActionIcon variant="subtle" color="gray"> <ActionIcon
variant="subtle"
color="gray"
aria-label={t("More actions for {{name}}", {
name: provider.name,
})}
>
<IconDots size={16} /> <IconDots size={16} />
</ActionIcon> </ActionIcon>
</Menu.Target> </Menu.Target>
@@ -32,6 +32,7 @@ export default function SsoProviderModal({
ssoProviderType: provider.type.toUpperCase(), ssoProviderType: provider.type.toUpperCase(),
})} })}
onClose={onClose} onClose={onClose}
closeButtonProps={{ "aria-label": t("Close") }}
> >
{provider.type === SSO_PROVIDER.SAML && ( {provider.type === SSO_PROVIDER.SAML && (
<SsoSamlForm provider={provider} onClose={onClose} /> <SsoSamlForm provider={provider} onClose={onClose} />
@@ -137,7 +137,6 @@ export default function Security() {
{ max: SCIM_TOKEN_LIMIT }, { max: SCIM_TOKEN_LIMIT },
)} )}
disabled={(scimData?.items.length ?? 0) < SCIM_TOKEN_LIMIT} disabled={(scimData?.items.length ?? 0) < SCIM_TOKEN_LIMIT}
refProp="rootRef"
> >
<Button <Button
onClick={() => setCreateOpen(true)} onClick={() => setCreateOpen(true)}
+10 -3
View File
@@ -18,14 +18,21 @@ export function buildSsoLoginUrl(opts: {
providerId: string; providerId: string;
type: SSO_PROVIDER; type: SSO_PROVIDER;
workspaceId?: string; workspaceId?: string;
redirect?: string;
}): string { }): string {
const { providerId, type, workspaceId } = opts; const { providerId, type, workspaceId, redirect } = opts;
const domain = getAppUrl(); const domain = getAppUrl();
const params = new URLSearchParams();
if (redirect) params.set("redirect", redirect);
if (type === SSO_PROVIDER.GOOGLE) { if (type === SSO_PROVIDER.GOOGLE) {
return `${getServerAppUrl()}/api/sso/${type}/login?workspaceId=${workspaceId}`; if (workspaceId) params.set("workspaceId", workspaceId);
return `${getServerAppUrl()}/api/sso/${type}/login?${params.toString()}`;
} }
return `${domain}/api/sso/${type}/${providerId}/login`; const query = params.toString();
const base = `${domain}/api/sso/${type}/${providerId}/login`;
return query ? `${base}?${query}` : base;
} }
export function getGoogleSignupUrl(): string { export function getGoogleSignupUrl(): string {
@@ -8,6 +8,11 @@
@mixin hover { @mixin hover {
transform: scale(1.02); transform: scale(1.02);
} }
&:focus-visible {
outline: 2px solid var(--mantine-primary-color-filled);
outline-offset: 2px;
}
} }
.cardBody { .cardBody {
@@ -50,18 +55,27 @@
.footer { .footer {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; gap: 6px;
gap: var(--mantine-spacing-xs);
padding-top: var(--mantine-spacing-sm); padding-top: var(--mantine-spacing-sm);
margin-top: var(--mantine-spacing-lg); margin-top: var(--mantine-spacing-lg);
border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); border-top: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5));
} }
.scopeDot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
flex-shrink: 0;
background-color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
}
.menuTarget { .menuTarget {
opacity: 0; opacity: 0;
transition: opacity 100ms ease; transition: opacity 100ms ease;
.card:hover & { .card:hover &,
.card:focus-within & {
opacity: 1; opacity: 1;
} }
} }
@@ -1,4 +1,4 @@
import { Card, Text, ActionIcon, Menu, Group } from "@mantine/core"; import { Button, Card, Text, ActionIcon, Menu, Group } from "@mantine/core";
import { import {
IconDots, IconDots,
IconEdit, IconEdit,
@@ -12,6 +12,7 @@ import classes from "./template-card.module.css";
type TemplateCardProps = { type TemplateCardProps = {
template: ITemplate; template: ITemplate;
spaceName?: string; spaceName?: string;
onPreview: (template: ITemplate) => void;
onUse: (template: ITemplate) => void; onUse: (template: ITemplate) => void;
onEdit?: (template: ITemplate) => void; onEdit?: (template: ITemplate) => void;
onDelete?: (template: ITemplate) => void; onDelete?: (template: ITemplate) => void;
@@ -21,6 +22,7 @@ type TemplateCardProps = {
export default function TemplateCard({ export default function TemplateCard({
template, template,
spaceName, spaceName,
onPreview,
onUse, onUse,
onEdit, onEdit,
onDelete, onDelete,
@@ -34,7 +36,17 @@ export default function TemplateCard({
padding="lg" padding="lg"
className={classes.card} className={classes.card}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
onClick={() => onUse(template)} role="button"
tabIndex={0}
aria-label={t("Preview template: {{title}}", { title: template.title })}
onClick={() => onPreview(template)}
onKeyDown={(e) => {
if (e.target !== e.currentTarget) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onPreview(template);
}
}}
> >
<div className={classes.cardBody}> <div className={classes.cardBody}>
<Group justify="space-between" align="flex-start" wrap="nowrap" mb="md"> <Group justify="space-between" align="flex-start" wrap="nowrap" mb="md">
@@ -47,6 +59,17 @@ export default function TemplateCard({
)} )}
<Group gap={6} wrap="nowrap"> <Group gap={6} wrap="nowrap">
<Button
size="compact-xs"
variant="filled"
className={classes.menuTarget}
onClick={(e) => {
e.stopPropagation();
onUse(template);
}}
>
{t("Use")}
</Button>
{canManage && ( {canManage && (
<Menu width={150} shadow="md" withArrow> <Menu width={150} shadow="md" withArrow>
<Menu.Target> <Menu.Target>
@@ -56,6 +79,7 @@ export default function TemplateCard({
color="gray" color="gray"
className={classes.menuTarget} className={classes.menuTarget}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
aria-label={t("Template menu")}
> >
<IconDots size={16} /> <IconDots size={16} />
</ActionIcon> </ActionIcon>
@@ -90,6 +114,7 @@ export default function TemplateCard({
<div className={classes.title}>{template.title}</div> <div className={classes.title}>{template.title}</div>
<div className={classes.footer}> <div className={classes.footer}>
<span className={classes.scopeDot} aria-hidden="true" />
<Text size="sm" fw={500} c="dimmed"> <Text size="sm" fw={500} c="dimmed">
{template.spaceId ? (spaceName || t("Space")) : t("Global")} {template.spaceId ? (spaceName || t("Space")) : t("Global")}
</Text> </Text>
@@ -0,0 +1,70 @@
.row {
position: relative;
display: flex;
align-items: center;
gap: var(--mantine-spacing-sm);
padding: var(--mantine-spacing-xs) var(--mantine-spacing-sm);
border-radius: var(--mantine-radius-sm);
width: 100%;
@mixin hover {
background-color: light-dark(
var(--mantine-color-gray-1),
var(--mantine-color-dark-6)
);
}
}
.icon {
width: 22px;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 16px;
line-height: 1;
}
.title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: var(--mantine-font-size-sm);
text-align: left;
}
.scope {
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
font-size: var(--mantine-font-size-xs);
flex-shrink: 0;
transition: opacity 100ms ease;
.row:hover &,
.row:focus-within & {
opacity: 0;
}
}
.useButton {
position: absolute;
top: 50%;
right: var(--mantine-spacing-sm);
transform: translateY(-50%);
opacity: 0;
transition: opacity 100ms ease;
.row:hover &,
.row:focus-within &,
&:focus-visible {
opacity: 1;
}
}
.empty {
display: flex;
align-items: center;
justify-content: center;
padding: var(--mantine-spacing-xl);
}
@@ -0,0 +1,259 @@
import { useMemo, useState } from "react";
import {
Button,
Modal,
TextInput,
ScrollArea,
Loader,
Text,
UnstyledButton,
Group,
SegmentedControl,
} from "@mantine/core";
import { useDebouncedValue } from "@mantine/hooks";
import {
IconArrowRight,
IconSearch,
IconFileText,
} from "@tabler/icons-react";
import { Link, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import {
useGetTemplatesQuery,
useUseTemplateMutation,
} from "@/ee/template/queries/template-query";
import { useGetSpacesQuery } from "@/features/space/queries/space-query";
import { ITemplate } from "@/ee/template/types/template.types";
import UseTemplateModal from "@/ee/template/components/use-template-modal";
import TemplatePreviewModal from "@/ee/template/components/template-preview-modal";
import { buildPageUrl } from "@/features/page/page.utils";
import classes from "./template-picker-modal.module.css";
type TemplatePickerModalProps = {
opened: boolean;
onClose: () => void;
/** Pre-select this space in the destination picker after a template is chosen. */
initialSpaceId?: string;
};
type ScopeFilter = "current" | "all";
export default function TemplatePickerModal({
opened,
onClose,
initialSpaceId,
}: TemplatePickerModalProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const useTemplateMutation = useUseTemplateMutation();
const [query, setQuery] = useState("");
const [debouncedQuery] = useDebouncedValue(query, 200);
const [scope, setScope] = useState<ScopeFilter>(
initialSpaceId ? "current" : "all",
);
// Two-stage selection: previewing first, then destination-picker.
// `previewTemplate` is set when the user clicks a row in the picker.
// `destinationTemplate` is set when they click "Use template" in the preview.
const [previewTemplate, setPreviewTemplate] = useState<ITemplate | null>(
null,
);
const [destinationTemplate, setDestinationTemplate] =
useState<ITemplate | null>(null);
const { data, isPending } = useGetTemplatesQuery({
spaceId: scope === "current" ? initialSpaceId : undefined,
});
const { data: spacesData } = useGetSpacesQuery({ limit: 100 });
const spaceNamesById = useMemo(() => {
const map = new Map<string, string>();
spacesData?.items?.forEach((s) => map.set(s.id, s.name));
return map;
}, [spacesData]);
const filtered = useMemo(() => {
const all = data?.pages.flatMap((p) => p.items) ?? [];
const term = debouncedQuery.trim().toLowerCase();
if (!term) return all;
return all.filter((tpl) => tpl.title.toLowerCase().includes(term));
}, [data, debouncedQuery]);
const createInInitialSpace = async (tpl: ITemplate) => {
if (!initialSpaceId) return;
try {
const page = await useTemplateMutation.mutateAsync({
templateId: tpl.id,
spaceId: initialSpaceId,
});
setPreviewTemplate(null);
onClose();
const space = spacesData?.items?.find((s) => s.id === initialSpaceId);
if (page?.slugId && space?.slug) {
navigate(buildPageUrl(space.slug, page.slugId, page.title));
}
} catch {
// error notification handled by mutation's onError
}
};
const handlePick = (tpl: ITemplate) => {
setPreviewTemplate(tpl);
};
const handleQuickUse = (tpl: ITemplate) => {
if (initialSpaceId) {
createInInitialSpace(tpl);
return;
}
setDestinationTemplate(tpl);
};
const handlePreviewClose = () => {
// Closing preview returns to the picker list (no full unmount).
setPreviewTemplate(null);
};
const handlePreviewUse = () => {
if (initialSpaceId && previewTemplate) {
createInInitialSpace(previewTemplate);
return;
}
// Move from preview into destination-picker stage.
setDestinationTemplate(previewTemplate);
setPreviewTemplate(null);
};
const handleDestinationClose = () => {
setDestinationTemplate(null);
onClose();
};
const handleClose = () => {
setQuery("");
setScope(initialSpaceId ? "current" : "all");
setPreviewTemplate(null);
setDestinationTemplate(null);
onClose();
};
return (
<>
<Modal
opened={opened && !previewTemplate && !destinationTemplate}
onClose={handleClose}
size={550}
padding="lg"
yOffset="10vh"
title={<Text fw={500}>{t("Use a template")}</Text>}
>
<TextInput
leftSection={<IconSearch size={16} />}
placeholder={t("Search templates...")}
variant="filled"
value={query}
onChange={(e) => setQuery(e.currentTarget.value)}
mb="xs"
autoFocus
/>
{initialSpaceId && (
<SegmentedControl
fullWidth
size="xs"
mb="sm"
value={scope}
onChange={(v) => setScope(v as ScopeFilter)}
data={[
{ label: t("This space"), value: "current" },
{ label: t("All templates"), value: "all" },
]}
/>
)}
<ScrollArea h="50vh" offsetScrollbars>
{isPending ? (
<div className={classes.empty}>
<Loader size="xs" />
</div>
) : filtered.length === 0 ? (
<div className={classes.empty}>
<Text size="sm" c="dimmed">
{t("No templates found")}
</Text>
</div>
) : (
filtered.map((tpl) => (
<UnstyledButton
key={tpl.id}
className={classes.row}
onClick={() => handlePick(tpl)}
>
<div className={classes.icon}>
{tpl.icon ? (
<span>{tpl.icon}</span>
) : (
<IconFileText
size={16}
color="var(--mantine-color-gray-6)"
/>
)}
</div>
<div className={classes.title}>{tpl.title}</div>
<div className={classes.scope}>
{tpl.spaceId
? spaceNamesById.get(tpl.spaceId) ?? t("Space")
: t("Global")}
</div>
<Button
size="compact-xs"
variant="filled"
className={classes.useButton}
loading={useTemplateMutation.isPending}
disabled={useTemplateMutation.isPending}
onClick={(e) => {
e.stopPropagation();
handleQuickUse(tpl);
}}
>
{t("Use")}
</Button>
</UnstyledButton>
))
)}
</ScrollArea>
<Group justify="flex-end" mt="md">
<Button
component={Link}
to="/templates"
variant="subtle"
size="sm"
rightSection={<IconArrowRight size={16} />}
onClick={handleClose}
>
{t("Browse all templates")}
</Button>
</Group>
</Modal>
{previewTemplate && (
<TemplatePreviewModal
templateId={previewTemplate.id}
opened={true}
onClose={handlePreviewClose}
onUse={handlePreviewUse}
useLoading={useTemplateMutation.isPending}
/>
)}
{destinationTemplate && (
<UseTemplateModal
template={destinationTemplate}
opened={true}
onClose={handleDestinationClose}
initialSpaceId={initialSpaceId}
/>
)}
</>
);
}
@@ -9,6 +9,7 @@ type TemplatePreviewModalProps = {
onClose: () => void; onClose: () => void;
onUse: () => void; onUse: () => void;
onEdit?: () => void; onEdit?: () => void;
useLoading?: boolean;
}; };
export default function TemplatePreviewModal({ export default function TemplatePreviewModal({
@@ -17,6 +18,7 @@ export default function TemplatePreviewModal({
onClose, onClose,
onUse, onUse,
onEdit, onEdit,
useLoading,
}: TemplatePreviewModalProps) { }: TemplatePreviewModalProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { data: template, isLoading } = useGetTemplateByIdQuery(templateId); const { data: template, isLoading } = useGetTemplateByIdQuery(templateId);
@@ -24,7 +26,7 @@ export default function TemplatePreviewModal({
const title = template?.title || t("Untitled"); const title = template?.title || t("Untitled");
return ( return (
<Modal.Root size={1200} opened={opened} onClose={onClose}> <Modal.Root size={1200} opened={opened} onClose={onClose} aria-label={title}>
<Modal.Overlay /> <Modal.Overlay />
<Modal.Content style={{ overflow: "hidden" }}> <Modal.Content style={{ overflow: "hidden" }}>
<Modal.Header> <Modal.Header>
@@ -37,15 +39,20 @@ export default function TemplatePreviewModal({
</Group> </Group>
</Modal.Title> </Modal.Title>
<Group gap="sm"> <Group gap="sm">
<Button
size="xs"
onClick={onUse}
loading={useLoading}
disabled={useLoading}
>
{t("Use template")}
</Button>
{onEdit && ( {onEdit && (
<Button size="xs" variant="default" onClick={onEdit}> <Button size="xs" variant="default" onClick={onEdit}>
{t("Edit")} {t("Edit")}
</Button> </Button>
)} )}
<Button size="xs" onClick={onUse}> <Modal.CloseButton aria-label={t("Close")} />
{t("Use template")}
</Button>
<Modal.CloseButton />
</Group> </Group>
</Modal.Header> </Modal.Header>
<Modal.Body p={0}> <Modal.Body p={0}>
@@ -10,12 +10,14 @@ type UseTemplateModalProps = {
template: ITemplate; template: ITemplate;
opened: boolean; opened: boolean;
onClose: () => void; onClose: () => void;
initialSpaceId?: string;
}; };
export default function UseTemplateModal({ export default function UseTemplateModal({
template, template,
opened, opened,
onClose, onClose,
initialSpaceId,
}: UseTemplateModalProps) { }: UseTemplateModalProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -54,6 +56,8 @@ export default function UseTemplateModal({
actionLabel={t("Create page")} actionLabel={t("Create page")}
onSelect={handleSelect} onSelect={handleSelect}
loading={useTemplateMutation.isPending} loading={useTemplateMutation.isPending}
initialSpaceId={initialSpaceId ?? template.spaceId}
searchSpacesOnly
/> />
); );
} }
@@ -75,6 +75,18 @@ export default function TemplateEditor() {
const editor = useEditor({ const editor = useEditor({
extensions: templateExtensions, extensions: templateExtensions,
content: "", content: "",
editorProps: {
handleDOMEvents: {
keydown: (_view, event) => {
if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
const slashCommand = document.querySelector("#slash-command");
if (slashCommand) {
return true;
}
}
},
},
},
onUpdate() { onUpdate() {
if (loadedRef.current) { if (loadedRef.current) {
markDirty(); markDirty();
@@ -271,6 +283,7 @@ export default function TemplateEditor() {
variant="subtle" variant="subtle"
color="gray" color="gray"
size="md" size="md"
aria-label={t("Template settings")}
onClick={() => { onClick={() => {
setDraftSpaceId(spaceId); setDraftSpaceId(spaceId);
openSettings(); openSettings();
@@ -160,7 +160,8 @@ export default function TemplateList() {
? spaceNameMap.get(template.spaceId) ? spaceNameMap.get(template.spaceId)
: undefined : undefined
} }
onUse={handlePreview} onPreview={handlePreview}
onUse={handleUse}
onEdit={handleEdit} onEdit={handleEdit}
onDelete={handleDelete} onDelete={handleDelete}
canManage={isWorkspaceAdmin} canManage={isWorkspaceAdmin}
@@ -6,6 +6,7 @@ import {
UseQueryResult, UseQueryResult,
InfiniteData, InfiniteData,
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import { useAtom, useStore } from "jotai";
import { import {
getTemplates, getTemplates,
getTemplateById, getTemplateById,
@@ -18,6 +19,12 @@ import { ITemplate } from "@/ee/template/types/template.types";
import { IPagination } from "@/lib/types.ts"; import { IPagination } from "@/lib/types.ts";
import { notifications } from "@mantine/notifications"; import { notifications } from "@mantine/notifications";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { invalidateOnCreatePage } from "@/features/page/queries/page-query.ts";
import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts";
import { treeModel } from "@/features/page/tree/model/tree-model";
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
import { IPage } from "@/features/page/types/page.types.ts";
import { useQueryEmit } from "@/features/websocket/use-query-emit.ts";
export function useGetTemplatesQuery(params?: { spaceId?: string }) { export function useGetTemplatesQuery(params?: { spaceId?: string }) {
const { spaceId } = params ?? {}; const { spaceId } = params ?? {};
@@ -149,13 +156,64 @@ export function useDeleteTemplateMutation() {
export function useUseTemplateMutation() { export function useUseTemplateMutation() {
const { t } = useTranslation(); const { t } = useTranslation();
const [, setTreeData] = useAtom(treeDataAtom);
const store = useStore();
const emit = useQueryEmit();
return useMutation({ return useMutation<
mutationFn: (data: { IPage,
templateId: string; Error,
spaceId: string; { templateId: string; spaceId: string; parentPageId?: string }
parentPageId?: string; >({
}) => useTemplate(data), mutationFn: (data) => useTemplate(data),
onSuccess: (page) => {
// React Query sidebar-pages cache update (same path useCreatePageMutation takes).
invalidateOnCreatePage(page);
const parentId = page.parentPageId ?? null;
const newNode: SpaceTreeNode = {
id: page.id,
slugId: page.slugId,
name: page.title,
icon: page.icon,
position: page.position,
spaceId: page.spaceId,
parentPageId: page.parentPageId,
hasChildren: false,
children: [],
};
// Only mutate the tree atom and broadcast if it currently represents
// this space. Cross-space template-use (e.g., from the gallery picking
// a different space) lets the target space's clients pick up the new
// page on their next React Query refetch (focus, navigation, etc.).
// Without this guard we'd both pollute the local tree and send a wrong
// `index` to remote clients in the target space.
const current = store.get(treeDataAtom);
const treeIsForThisSpace = current[0]?.spaceId === page.spaceId;
if (!treeIsForThisSpace) return;
const lastIndex =
parentId === null
? current.length
: (treeModel.find(current, parentId)?.children?.length ?? 0);
setTreeData((prev) =>
treeModel.insert(prev, parentId, newNode, lastIndex),
);
setTimeout(() => {
emit({
operation: "addTreeNode",
spaceId: page.spaceId,
payload: {
parentId,
index: lastIndex,
data: newNode,
},
});
}, 50);
},
onError: (error) => { onError: (error) => {
const errorMessage = error["response"]?.data?.message; const errorMessage = error["response"]?.data?.message;
notifications.show({ notifications.show({
@@ -1,5 +1,6 @@
import api from "@/lib/api-client"; import api from "@/lib/api-client";
import { ITemplate } from "@/ee/template/types/template.types"; import { ITemplate } from "@/ee/template/types/template.types";
import { IPage } from "@/features/page/types/page.types";
import { IPagination } from "@/lib/types.ts"; import { IPagination } from "@/lib/types.ts";
export async function getTemplates(params?: { export async function getTemplates(params?: {
@@ -40,7 +41,7 @@ export async function useTemplate(data: {
templateId: string; templateId: string;
spaceId: string; spaceId: string;
parentPageId?: string; parentPageId?: string;
}): Promise<any> { }): Promise<IPage> {
const req = await api.post("/templates/use", data); const req = await api.post<IPage>("/templates/use", data);
return req.data; return req.data;
} }
@@ -20,7 +20,7 @@ export function AuthLayout({ children }: AuthLayoutProps) {
Docmost Docmost
</Text> </Text>
</Group> </Group>
{children} <main>{children}</main>
</> </>
); );
} }
@@ -103,6 +103,11 @@ export function InviteSignUpForm() {
placeholder={t("Your password")} placeholder={t("Your password")}
variant="filled" variant="filled"
mt="md" mt="md"
visibilityToggleButtonProps={{
"aria-label": t("Toggle password visibility"),
"aria-hidden": false,
tabIndex: 0,
}}
{...form.getInputProps("password")} {...form.getInputProps("password")}
/> />
<Button type="submit" fullWidth mt="xl" loading={isLoading}> <Button type="submit" fullWidth mt="xl" loading={isLoading}>
@@ -54,6 +54,13 @@ export function LoginForm() {
await signIn(data); await signIn(data);
} }
function handleValidationFailure(errors: Record<string, unknown>) {
const firstInvalidId = Object.keys(errors)[0];
if (firstInvalidId) {
document.getElementById(firstInvalidId)?.focus();
}
}
if (isDataLoading) { if (isDataLoading) {
return null; return null;
} }
@@ -66,7 +73,7 @@ export function LoginForm() {
<AuthLayout> <AuthLayout>
<Container size={420} className={classes.container}> <Container size={420} className={classes.container}>
<Box p="xl" className={classes.containerBox}> <Box p="xl" className={classes.containerBox}>
<Title order={2} ta="center" fw={500} mb="md"> <Title order={1} size="h2" ta="center" fw={500} mb="md">
{t("Login")} {t("Login")}
</Title> </Title>
@@ -74,21 +81,31 @@ export function LoginForm() {
{!data?.enforceSso && ( {!data?.enforceSso && (
<> <>
<form onSubmit={form.onSubmit(onSubmit)}> <form onSubmit={form.onSubmit(onSubmit, handleValidationFailure)}>
<TextInput <TextInput
id="email" id="email"
type="email" type="email"
label={t("Email")} label={t("Email")}
placeholder="email@example.com" placeholder="email@example.com"
variant="filled" variant="filled"
autoComplete="email"
errorProps={{ role: "alert" }}
{...form.getInputProps("email")} {...form.getInputProps("email")}
/> />
<PasswordInput <PasswordInput
id="password"
label={t("Password")} label={t("Password")}
placeholder={t("Your password")} placeholder={t("Your password")}
variant="filled" variant="filled"
mt="md" mt="md"
autoComplete="current-password"
errorProps={{ role: "alert" }}
visibilityToggleButtonProps={{
"aria-label": t("Toggle password visibility"),
"aria-hidden": false,
tabIndex: 0,
}}
{...form.getInputProps("password")} {...form.getInputProps("password")}
/> />
@@ -52,6 +52,11 @@ export function PasswordResetForm({ resetToken }: PasswordResetFormProps) {
placeholder={t("Your new password")} placeholder={t("Your new password")}
variant="filled" variant="filled"
mt="md" mt="md"
visibilityToggleButtonProps={{
"aria-label": t("Toggle password visibility"),
"aria-hidden": false,
tabIndex: 0,
}}
{...form.getInputProps("newPassword")} {...form.getInputProps("newPassword")}
/> />
@@ -98,6 +98,11 @@ export function SetupWorkspaceForm() {
placeholder={t("Enter a strong password")} placeholder={t("Enter a strong password")}
variant="filled" variant="filled"
mt="md" mt="md"
visibilityToggleButtonProps={{
"aria-label": t("Toggle password visibility"),
"aria-hidden": false,
tabIndex: 0,
}}
{...form.getInputProps("password")} {...form.getInputProps("password")}
/> />
<Button type="submit" fullWidth mt="xl" loading={isLoading}> <Button type="submit" fullWidth mt="xl" loading={isLoading}>
@@ -166,7 +166,7 @@ export default function useAuth() {
const handleLogout = async () => { const handleLogout = async () => {
setCurrentUser(RESET); setCurrentUser(RESET);
await logout(); await logout();
window.location.replace(APP_ROUTE.AUTH.LOGIN); window.location.replace(`${APP_ROUTE.AUTH.LOGIN}?logout=1`);
}; };
const handleForgotPassword = async (data: IForgotPassword) => { const handleForgotPassword = async (data: IForgotPassword) => {
@@ -144,6 +144,7 @@ function CommentDialog({ editor, pageId, readOnly }: CommentDialogProps) {
withCloseButton withCloseButton
withBorder withBorder
data-comment-dialog data-comment-dialog
aria-label={t("Add comment")}
> >
<Stack gap={2}> <Stack gap={2}>
<Group> <Group>
@@ -19,6 +19,7 @@ interface CommentEditorProps {
editable: boolean; editable: boolean;
placeholder?: string; placeholder?: string;
autofocus?: boolean; autofocus?: boolean;
surface?: "default" | "muted";
} }
const CommentEditor = forwardRef( const CommentEditor = forwardRef(
@@ -30,6 +31,7 @@ const CommentEditor = forwardRef(
editable, editable,
placeholder, placeholder,
autofocus, autofocus,
surface,
}: CommentEditorProps, }: CommentEditorProps,
ref, ref,
) => { ) => {
@@ -66,6 +68,9 @@ const CommentEditor = forwardRef(
}), }),
], ],
editorProps: { editorProps: {
attributes: {
"aria-label": placeholder || t("Comment"),
},
handleDOMEvents: { handleDOMEvents: {
keydown: (_view, event) => { keydown: (_view, event) => {
if ( if (
@@ -131,6 +136,7 @@ const CommentEditor = forwardRef(
ref={focusRef} ref={focusRef}
className={classes.commentEditor} className={classes.commentEditor}
data-editable={editable || undefined} data-editable={editable || undefined}
data-surface={surface}
> >
<EditorContent <EditorContent
editor={commentEditor} editor={commentEditor}
@@ -173,6 +173,15 @@ function CommentListItem({
<Box <Box
className={classes.textSelection} className={classes.textSelection}
onClick={() => handleCommentClick(comment)} onClick={() => handleCommentClick(comment)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleCommentClick(comment);
}
}}
role="button"
tabIndex={0}
aria-label={t("Jump to comment selection")}
> >
<Text size="sm">{comment?.selection}</Text> <Text size="sm">{comment?.selection}</Text>
</Box> </Box>
@@ -383,6 +383,7 @@ const PageCommentInput = ({ onSave, isLoading }) => {
onSave={handleSave} onSave={handleSave}
editable={true} editable={true}
placeholder={t("Add a comment...")} placeholder={t("Add a comment...")}
surface="muted"
/> />
</div> </div>
</Group> </Group>
@@ -391,6 +392,7 @@ const PageCommentInput = ({ onSave, isLoading }) => {
variant="filled" variant="filled"
radius="xl" radius="xl"
size="sm" size="sm"
aria-label={t("Send comment")}
onClick={handleSave} onClick={handleSave}
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
loading={isLoading} loading={isLoading}
@@ -46,7 +46,11 @@ function CommentMenu({
return ( return (
<Menu shadow="md" width={200}> <Menu shadow="md" width={200}>
<Menu.Target> <Menu.Target>
<ActionIcon variant="default" style={{ border: "none" }}> <ActionIcon
variant="default"
style={{ border: "none" }}
aria-label={t("Comment menu")}
>
<IconDots size={20} stroke={2} /> <IconDots size={20} stroke={2} />
</ActionIcon> </ActionIcon>
</Menu.Target> </Menu.Target>
@@ -22,6 +22,11 @@
.commentEditor { .commentEditor {
&[data-editable][data-surface="muted"] .ProseMirror:not(.focused) {
border-radius: var(--mantine-radius-sm);
box-shadow: 0 0 0 1px light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-4));
}
.focused { .focused {
border-radius: var(--mantine-radius-sm); border-radius: var(--mantine-radius-sm);
box-shadow: 0 0 0 2px var(--mantine-color-blue-3); box-shadow: 0 0 0 2px var(--mantine-color-blue-3);
@@ -1,5 +1,6 @@
import { atom } from "jotai"; import { atom } from "jotai";
import { Editor } from "@tiptap/core"; import { Editor } from "@tiptap/core";
import { PageEditMode } from "@/features/user/types/user.types.ts";
export const pageEditorAtom = atom<Editor | null>(null); export const pageEditorAtom = atom<Editor | null>(null);
@@ -12,3 +13,7 @@ export const yjsConnectionStatusAtom = atom<string>("");
export const showAiMenuAtom = atom(false); export const showAiMenuAtom = atom(false);
export const showLinkMenuAtom = atom(false); export const showLinkMenuAtom = atom(false);
// Current page's edit mode — initialized from the user's saved preference on
// first load, can be toggled locally without persisting to the server.
export const currentPageEditModeAtom = atom<PageEditMode>(PageEditMode.Edit);
@@ -2,6 +2,7 @@ import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react"; import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react";
import { useCallback } from "react"; import { useCallback } from "react";
import { Node as PMNode } from "@tiptap/pm/model"; import { Node as PMNode } from "@tiptap/pm/model";
import { isEditorReady } from "@docmost/editor-ext";
import { import {
EditorMenuProps, EditorMenuProps,
ShouldShowProps, ShouldShowProps,
@@ -46,7 +47,7 @@ export function AudioMenu({ editor }: EditorMenuProps) {
); );
const getReferencedVirtualElement = useCallback(() => { const getReferencedVirtualElement = useCallback(() => {
if (!editor) return; if (!isEditorReady(editor)) return;
const { selection } = editor.state; const { selection } = editor.state;
const predicate = (node: PMNode) => node.type.name === "audio"; const predicate = (node: PMNode) => node.type.name === "audio";
const parent = findParentNode(predicate)(selection); const parent = findParentNode(predicate)(selection);
@@ -36,6 +36,7 @@ export default function AudioView(props: NodeViewProps) {
preload="metadata" preload="metadata"
controls controls
src={safeSrc} src={safeSrc}
aria-label={placeholder?.name || t("Audio")}
/> />
)} )}
{!safeSrc && previewSrc && ( {!safeSrc && previewSrc && (
@@ -45,6 +46,7 @@ export default function AudioView(props: NodeViewProps) {
preload="metadata" preload="metadata"
controls controls
src={previewSrc} src={previewSrc}
aria-label={placeholder?.name || t("Audio")}
/> />
<Loader size={20} pos="absolute" top={6} right={6} /> <Loader size={20} pos="absolute" top={6} right={6} />
</Group> </Group>
@@ -60,7 +62,7 @@ export default function AudioView(props: NodeViewProps) {
</Group> </Group>
)} )}
{!safeSrc && !previewSrc && !placeholder && ( {!safeSrc && !previewSrc && !placeholder && (
<audio className={classes.audio} controls /> <audio className={classes.audio} controls aria-label={t("Audio")} />
)} )}
</div> </div>
</NodeViewWrapper> </NodeViewWrapper>

Some files were not shown because too many files have changed in this diff Show More