✨(frontend) make components accessible to screen readers
adds proper aria props and translation keys for accessibility support Signed-off-by: Cyril <[email protected]>
This commit is contained in:
@@ -307,6 +307,11 @@ test.describe('Doc Editor', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure AI feature is enabled
|
||||
await overrideConfig(page, {
|
||||
AI_FEATURE_ENABLED: true,
|
||||
});
|
||||
|
||||
await createDoc(page, 'doc-ai', browserName, 1);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Hello World');
|
||||
@@ -314,7 +319,7 @@ test.describe('Doc Editor', () => {
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.getByText('Hello').selectText();
|
||||
|
||||
await page.getByRole('button', { name: 'AI' }).click();
|
||||
await page.locator('[data-test="ai-actions"]').click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
@@ -400,11 +405,11 @@ test.describe('Doc Editor', () => {
|
||||
/* eslint-disable playwright/no-conditional-expect */
|
||||
/* eslint-disable playwright/no-conditional-in-test */
|
||||
if (!ai_transform && !ai_translate) {
|
||||
await expect(page.getByRole('button', { name: 'AI' })).toBeHidden();
|
||||
await expect(page.locator('[data-test="ai-actions"]')).toBeHidden();
|
||||
return;
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'AI' }).click();
|
||||
await page.locator('[data-test="ai-actions"]').click();
|
||||
|
||||
if (ai_transform) {
|
||||
await expect(
|
||||
|
||||
@@ -175,10 +175,10 @@ test.describe('Document search', () => {
|
||||
|
||||
// Expect to find the first doc
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(firstDocTitle),
|
||||
page.getByRole('presentation').getByText(firstDocTitle),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(secondDocTitle),
|
||||
page.getByRole('presentation').getByText(secondDocTitle),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'close' }).click();
|
||||
@@ -196,13 +196,13 @@ test.describe('Document search', () => {
|
||||
|
||||
// Now there is a sub page - expect to have the focus on the current doc
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(secondDocTitle),
|
||||
page.getByRole('presentation').getByText(secondDocTitle),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(secondChildDocTitle),
|
||||
page.getByRole('presentation').getByText(secondChildDocTitle),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('presentation').getByLabel(firstDocTitle),
|
||||
page.getByRole('presentation').getByText(firstDocTitle),
|
||||
).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
updateDocTitle,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
import { addNewMember } from './utils-share';
|
||||
import { clickOnAddRootSubPage, createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Doc Tree', () => {
|
||||
@@ -185,14 +184,13 @@ test.describe('Doc Tree', () => {
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree.getByText(docChild)).toBeVisible();
|
||||
await docTree.click();
|
||||
const child = docTree
|
||||
.getByRole('treeitem')
|
||||
.locator('.--docs-sub-page-item')
|
||||
.filter({
|
||||
hasText: docChild,
|
||||
});
|
||||
const child = docTree.locator('.--docs-sub-page-item').filter({
|
||||
hasText: docChild,
|
||||
});
|
||||
await child.hover();
|
||||
// Wait a bit for the hover effect to take place
|
||||
const menu = child.getByText(`more_horiz`);
|
||||
await expect(menu).toBeVisible();
|
||||
await menu.click();
|
||||
await page.getByText('Move to my docs').click();
|
||||
|
||||
@@ -215,43 +213,38 @@ test.describe('Doc Tree', () => {
|
||||
|
||||
await verifyDocName(page, docParent);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await addNewMember(page, 0, 'Owner', 'impress');
|
||||
|
||||
const list = page.getByTestId('doc-share-quick-search');
|
||||
const currentUser = list.getByTestId(
|
||||
`doc-share-member-row-user@${browserName}.test`,
|
||||
);
|
||||
const currentUserRole = currentUser.getByLabel('doc-role-dropdown');
|
||||
await currentUserRole.click();
|
||||
await page.getByLabel('Administrator').click();
|
||||
await list.click();
|
||||
|
||||
await page.getByRole('button', { name: 'Ok' }).click();
|
||||
|
||||
// Create a child document first
|
||||
const { name: docChild } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'doc-tree-detach-child',
|
||||
);
|
||||
|
||||
// Now try to detach the child document - this should work for the owner
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree.getByText(docChild)).toBeVisible();
|
||||
await docTree.click();
|
||||
const child = docTree
|
||||
.getByRole('treeitem')
|
||||
.locator('.--docs-sub-page-item')
|
||||
.filter({
|
||||
hasText: docChild,
|
||||
});
|
||||
const child = docTree.locator('.--docs-sub-page-item').filter({
|
||||
hasText: docChild,
|
||||
});
|
||||
await child.hover();
|
||||
// Wait a bit for the hover effect to take place
|
||||
const menu = child.getByText(`more_horiz`);
|
||||
await expect(menu).toBeVisible();
|
||||
await menu.click();
|
||||
|
||||
// The owner should be able to detach the document
|
||||
await page.getByRole('menuitem', { name: 'Move to my docs' }).click();
|
||||
|
||||
// Verify the document was detached - it should no longer be in the current tree
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Move to my docs' }),
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
page.getByRole('textbox', { name: 'doc title input' }),
|
||||
).not.toHaveText(docChild);
|
||||
|
||||
// Verify the document is now on the home page
|
||||
const header = page.locator('header').first();
|
||||
await header.locator('h1').getByText('Docs').click();
|
||||
await expect(page.getByText(docChild)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@ type FocusableNode<T> = NodeRendererProps<TreeDataItem<T>>['node'] & {
|
||||
/**
|
||||
* Hook to manage keyboard navigation for actionable items in a tree view.
|
||||
*
|
||||
* Provides two modes:
|
||||
* 1. Activation: F2/Enter moves focus to first actionable element
|
||||
* 2. Navigation: Arrow keys navigate between actions, Escape returns to tree node
|
||||
*
|
||||
* Disables navigation when dropdown menu is open to prevent conflicts.
|
||||
*/
|
||||
export const useActionableMode = <T>(
|
||||
@@ -23,11 +19,21 @@ export const useActionableMode = <T>(
|
||||
const actionsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!node?.isFocused) {
|
||||
const modalOpen = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
if (!node?.isFocused || modalOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toActions = (e: KeyboardEvent) => {
|
||||
const modalOpen = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
if (modalOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'F2' || e.key === 'Enter') {
|
||||
const isAlreadyInActions = actionsRef.current?.contains(
|
||||
document.activeElement,
|
||||
@@ -61,6 +67,13 @@ export const useActionableMode = <T>(
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
if (modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
e.stopPropagation();
|
||||
node?.focus?.();
|
||||
|
||||
+8
-1
@@ -56,7 +56,14 @@ export const useDropdownFocusManagement = ({
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
// Try to find sub-document by closest ancestor
|
||||
const modal = document.querySelector(
|
||||
'[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content',
|
||||
);
|
||||
if (modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only handle focus return if no modal is open
|
||||
let subPageItem = actionsRef?.current?.closest('.--docs-sub-page-item');
|
||||
|
||||
// If not found, try to find by data-testid
|
||||
|
||||
+4
@@ -49,6 +49,7 @@ export const SimpleDocItem = ({
|
||||
$overflow="auto"
|
||||
$width="100%"
|
||||
className="--docs--simple-doc-item"
|
||||
role="presentation"
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
@@ -59,6 +60,7 @@ export const SimpleDocItem = ({
|
||||
`}
|
||||
$padding={`${spacingsTokens['3xs']} 0`}
|
||||
data-testid={isPinned ? `doc-pinned-${doc.id}` : undefined}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{isPinned ? (
|
||||
<PinnedDocumentIcon
|
||||
@@ -88,6 +90,7 @@ export const SimpleDocItem = ({
|
||||
$variation="1000"
|
||||
$weight="500"
|
||||
$css={ItemTextCss}
|
||||
aria-describedby="doc-title"
|
||||
>
|
||||
{displayTitle}
|
||||
</Text>
|
||||
@@ -97,6 +100,7 @@ export const SimpleDocItem = ({
|
||||
$align="center"
|
||||
$gap={spacingsTokens['3xs']}
|
||||
$margin={{ top: '-2px' }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Text $variation="600" $size="xs">
|
||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@gouvfr-lasuite/ui-kit';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, BoxButton, Icon, Text } from '@/components';
|
||||
@@ -40,6 +41,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
const { node } = props;
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
@@ -88,11 +90,23 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
|
||||
useTreeItemKeyboardActivate(isActive, handleActivate);
|
||||
|
||||
// prepare the text for the screen reader
|
||||
const docTitle = doc.title || untitledDocument;
|
||||
const hasChildren = (doc.children?.length || 0) > 0;
|
||||
const isExpanded = node.isOpen;
|
||||
const isSelected = treeContext?.treeData.selectedNode?.id === doc.id;
|
||||
|
||||
const ariaLabel = `${docTitle}${hasChildren ? `, ${isExpanded ? t('expanded') : t('collapsed')}` : ''}${isSelected ? `, ${t('selected')}` : ''}`;
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="--docs-sub-page-item"
|
||||
draggable={doc.abilities.move && isDesktop}
|
||||
$position="relative"
|
||||
role="treeitem"
|
||||
aria-label={ariaLabel}
|
||||
aria-selected={isSelected}
|
||||
aria-expanded={hasChildren ? isExpanded : undefined}
|
||||
$css={css`
|
||||
background-color: ${isActive
|
||||
? 'var(--c--theme--colors--greyscale-100)'
|
||||
@@ -114,6 +128,8 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
|
||||
&:focus-within .light-doc-item-actions {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
background: var(--c--theme--colors--greyscale-100);
|
||||
}
|
||||
|
||||
@@ -130,6 +146,8 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
|
||||
.light-doc-item-actions {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
background: var(--c--theme--colors--greyscale-100);
|
||||
}
|
||||
}
|
||||
@@ -141,7 +159,6 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
>
|
||||
<TreeViewItem {...props} onClick={handleActivate}>
|
||||
<BoxButton
|
||||
as="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleActivate();
|
||||
@@ -152,6 +169,8 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
$align="center"
|
||||
$minHeight="24px"
|
||||
data-testid={`doc-sub-page-item-${doc.id}`}
|
||||
aria-label={`${t('Open document')} ${docTitle}`}
|
||||
role="button"
|
||||
>
|
||||
<Box $width="16px" $height="16px">
|
||||
<DocIcon emoji={emoji} defaultIcon={<SubPageIcon />} $size="sm" />
|
||||
@@ -190,6 +209,8 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
$direction="row"
|
||||
$align="center"
|
||||
className="light-doc-item-actions"
|
||||
role="group"
|
||||
aria-label={`${t('Actions for')} ${docTitle}`}
|
||||
>
|
||||
<DocTreeItemActions
|
||||
doc={doc}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@gouvfr-lasuite/ui-kit';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, StyledLink } from '@/components';
|
||||
@@ -31,6 +32,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
const router = useRouter();
|
||||
const { isDesktop } = useResponsive();
|
||||
const [treeRoot, setTreeRoot] = useState<HTMLElement | null>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [initialOpenState, setInitialOpenState] = useState<OpenMap | undefined>(
|
||||
undefined,
|
||||
@@ -152,6 +154,8 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
ref={setTreeRoot}
|
||||
data-testid="doc-tree"
|
||||
$height="100%"
|
||||
role="tree"
|
||||
aria-label={t('Document tree')}
|
||||
$css={css`
|
||||
.c__tree-view--container {
|
||||
z-index: 1;
|
||||
@@ -171,6 +175,9 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
>
|
||||
<Box
|
||||
data-testid="doc-tree-root-item"
|
||||
role="treeitem"
|
||||
aria-label={`${t('Root document')}: ${treeContext.root?.title || t('Untitled document')}`}
|
||||
aria-selected={rootIsSelected}
|
||||
$css={css`
|
||||
padding: ${spacingsTokens['2xs']};
|
||||
border-radius: 4px;
|
||||
@@ -212,6 +219,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
);
|
||||
router.push(`/docs/${treeContext?.root?.id}`);
|
||||
}}
|
||||
aria-label={`${t('Open root document')}: ${treeContext.root?.title || t('Untitled document')}`}
|
||||
>
|
||||
<Box $direction="row" $align="center" $width="100%">
|
||||
<SimpleDocItem doc={treeContext.root} showAccesses={true} />
|
||||
|
||||
+10
-2
@@ -211,7 +211,11 @@ export const DocTreeItemActions = ({
|
||||
className="icon-button"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
aria-label={t('More options')}
|
||||
aria-label={
|
||||
t('More options for') + ` ${doc.title || t('Untitled document')}`
|
||||
}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={isOpen}
|
||||
onKeyDown={handleMoreOptionsKeyDown}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
@@ -223,7 +227,10 @@ export const DocTreeItemActions = ({
|
||||
onClick={handleAddChildClick}
|
||||
onKeyDown={handleAddChildKeyDown}
|
||||
color="primary"
|
||||
aria-label={t('Add child document')}
|
||||
aria-label={
|
||||
t('Add child document to') +
|
||||
` ${doc.title || t('Untitled document')}`
|
||||
}
|
||||
$hasTransition={false}
|
||||
>
|
||||
<Icon
|
||||
@@ -231,6 +238,7 @@ export const DocTreeItemActions = ({
|
||||
$variation="800"
|
||||
$theme="primary"
|
||||
iconName="add_box"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BoxButton>
|
||||
)}
|
||||
|
||||
@@ -157,7 +157,17 @@
|
||||
"Document access mode": "Mod aotreet an teul",
|
||||
"Warning": "Diwallit",
|
||||
"Why you can't edit the document?": "Perak ne c'hellit ket aozañ ar restr?",
|
||||
"Write": "Skrivañ"
|
||||
"Write": "Skrivañ",
|
||||
"Document tree": "Gwezennadur ar restroù",
|
||||
"Root document": "Restr gwrizienn",
|
||||
"Open root document": "Digeriñ ar restr gwrizienn",
|
||||
"Open document": "Digeriñ ar restr",
|
||||
"Actions for": "Obererezhioù evit",
|
||||
"Add child document to": "Ouzhpennañ ur restr bugel da",
|
||||
"More options for": "Dibarzhioù ouzhpenn evit",
|
||||
"expanded": "digeret",
|
||||
"collapsed": "serret",
|
||||
"selected": "diuzet"
|
||||
}
|
||||
},
|
||||
"de": {
|
||||
@@ -365,6 +375,16 @@
|
||||
"Warning": "Warnung",
|
||||
"Why you can't edit the document?": "Warum können Sie dieses Dokument nicht bearbeiten?",
|
||||
"Write": "Schreiben",
|
||||
"Document tree": "Dokumentenbaum",
|
||||
"Root document": "Stammdokument",
|
||||
"Open root document": "Stammdokument öffnen",
|
||||
"Open document": "Dokument öffnen",
|
||||
"Actions for": "Aktionen für",
|
||||
"Add child document to": "Unterdokument hinzufügen zu",
|
||||
"More options for": "Weitere Optionen für",
|
||||
"expanded": "erweitert",
|
||||
"collapsed": "zusammengeklappt",
|
||||
"selected": "ausgewählt",
|
||||
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Sie sind der einzige Besitzer dieser Gruppe. Machen Sie ein anderes Mitglied zum Gruppenbesitzer, bevor Sie Ihre eigene Rolle ändern oder aus Ihrem Dokument entfernen können.",
|
||||
"You must be at least the editor of the target document": "Sie müssen dafür mindestens die Rolle \"Mitbearbeiter\" haben",
|
||||
"You must be the owner to move the document": "Sie müssen Besitzer des Dokuments sein, um es zu verschieben",
|
||||
@@ -390,7 +410,17 @@
|
||||
"Shared with {{count}} users_one": "Shared with {{count}} user",
|
||||
"Shared with {{count}} users_other": "Shared with {{count}} users",
|
||||
"Updated": "Updated",
|
||||
"Add child document": "Add child document"
|
||||
"Add child document": "Add child document",
|
||||
"Document tree": "Document tree",
|
||||
"Root document": "Root document",
|
||||
"Open root document": "Open root document",
|
||||
"Open document": "Open document",
|
||||
"Actions for": "Actions for",
|
||||
"Add child document to": "Add child document to",
|
||||
"More options for": "More options for",
|
||||
"expanded": "expanded",
|
||||
"collapsed": "collapsed",
|
||||
"selected": "selected"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
@@ -575,6 +605,16 @@
|
||||
"Document access mode": "Modo de acceso al documento",
|
||||
"Warning": "Aviso",
|
||||
"Write": "Escribe",
|
||||
"Document tree": "Árbol de documentos",
|
||||
"Root document": "Documento raíz",
|
||||
"Open root document": "Abrir documento raíz",
|
||||
"Open document": "Abrir documento",
|
||||
"Actions for": "Acciones para",
|
||||
"Add child document to": "Añadir documento hijo a",
|
||||
"More options for": "Más opciones para",
|
||||
"expanded": "expandido",
|
||||
"collapsed": "contraído",
|
||||
"selected": "seleccionado",
|
||||
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Eres el único propietario de este grupo, haz que otro miembro sea el propietario del grupo para poder cambiar tu propio rol o ser eliminado del documento.",
|
||||
"Your current document will revert to this version.": "Tu documento actual se revertirá a esta versión.",
|
||||
"Your {{format}} was downloaded succesfully": "Su {{format}} se ha descargado correctamente",
|
||||
@@ -814,6 +854,16 @@
|
||||
"Warning": "Attention",
|
||||
"Why you can't edit the document?": "Pourquoi vous ne pouvez pas modifier le document ?",
|
||||
"Write": "Écrire",
|
||||
"Document tree": "Arborescence des documents",
|
||||
"Root document": "Document racine",
|
||||
"Open root document": "Ouvrir le document racine",
|
||||
"Open document": "Ouvrir le document",
|
||||
"Actions for": "Actions pour",
|
||||
"Add child document to": "Ajouter un document enfant à",
|
||||
"More options for": "Plus d'options pour",
|
||||
"expanded": "développé",
|
||||
"collapsed": "replié",
|
||||
"selected": "sélectionné",
|
||||
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Vous êtes le seul propriétaire de ce groupe, faites d'un autre membre le propriétaire du groupe, avant de pouvoir modifier votre propre rôle ou vous supprimer du document.",
|
||||
"You can view this document but need additional access to see its members or modify settings.": "Vous pouvez voir ce document mais vous avez besoin d'un accès supplémentaire pour voir ses membres ou modifier les paramètres.",
|
||||
"You cannot restrict access to a subpage relative to its parent page.": "Vous ne pouvez pas restreindre l'accès à une sous-page par rapport à sa page parente.",
|
||||
@@ -1155,6 +1205,16 @@
|
||||
"Document access mode": "Document toegangsmodus",
|
||||
"Warning": "Waarschuwing",
|
||||
"Write": "Schrijf",
|
||||
"Document tree": "Documentenboom",
|
||||
"Root document": "Hoofddocument",
|
||||
"Open root document": "Hoofddocument openen",
|
||||
"Open document": "Document openen",
|
||||
"Actions for": "Acties voor",
|
||||
"Add child document to": "Onderliggend document toevoegen aan",
|
||||
"More options for": "Meer opties voor",
|
||||
"expanded": "uitgeklapt",
|
||||
"collapsed": "ingeklapt",
|
||||
"selected": "geselecteerd",
|
||||
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "U bent de enige eigenaar van deze groep, maak een ander lid de groepseigenaar voordat u uw eigen rol kunt wijzigen of kan worden verwijderd van het document.",
|
||||
"Your current document will revert to this version.": "Uw huidige document wordt teruggezet naar deze versie.",
|
||||
"Your {{format}} was downloaded succesfully": "Jouw {{format}} is succesvol gedownload",
|
||||
|
||||
Reference in New Issue
Block a user