diff --git a/client/src/components/attachments/Attachments/ItemContent.jsx b/client/src/components/attachments/Attachments/ItemContent.jsx
index b6dc5eaf..045871ab 100644
--- a/client/src/components/attachments/Attachments/ItemContent.jsx
+++ b/client/src/components/attachments/Attachments/ItemContent.jsx
@@ -9,6 +9,7 @@ import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Button, Icon, Label, Loader } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
@@ -161,9 +162,11 @@ const ItemContent = React.forwardRef(({ id, onOpen }, ref) => {
{canEdit && (
-
+
+
+
)}
diff --git a/client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomField.jsx b/client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomField.jsx
index f12c497a..67bc893a 100755
--- a/client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomField.jsx
+++ b/client/src/components/base-custom-field-groups/BaseCustomFieldGroupStep/CustomField.jsx
@@ -8,7 +8,9 @@ import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Draggable } from 'react-beautiful-dnd';
+import { useTranslation } from 'react-i18next';
import { Button, Icon } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
@@ -18,6 +20,7 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
const selectCustomFieldById = useMemo(() => selectors.makeSelectCustomFieldById(), []);
const customField = useSelector((state) => selectCustomFieldById(state, id));
+ const [t] = useTranslation();
const handleEditClick = useCallback(() => {
onEdit(id);
@@ -36,14 +39,16 @@ const CustomField = React.memo(({ id, index, onEdit }) => {
{customField.showOnFrontOfCard && }
{customField.name}
-
+
+
+
);
diff --git a/client/src/components/board-memberships/BoardMemberships/BoardMemberships.jsx b/client/src/components/board-memberships/BoardMemberships/BoardMemberships.jsx
index c7aabf23..110da773 100644
--- a/client/src/components/board-memberships/BoardMemberships/BoardMemberships.jsx
+++ b/client/src/components/board-memberships/BoardMemberships/BoardMemberships.jsx
@@ -7,7 +7,9 @@ import groupBy from 'lodash/groupBy';
import React, { useMemo } from 'react';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import { usePopup } from '../../../lib/popup';
import selectors from '../../../selectors';
@@ -20,6 +22,7 @@ import styles from './BoardMemberships.module.scss';
const BoardMemberships = React.memo(() => {
const boardMemberships = useSelector(selectors.selectMembershipsForCurrentBoard);
+ const [t] = useTranslation();
const canAdd = useSelector((state) => {
const user = selectors.selectCurrentUser(state);
@@ -57,7 +60,9 @@ const BoardMemberships = React.memo(() => {
)}
{canAdd && (
-
+
+
+
)}
>
diff --git a/client/src/components/boards/Board/GridView.jsx b/client/src/components/boards/Board/GridView.jsx
index a45b1133..d940223c 100755
--- a/client/src/components/boards/Board/GridView.jsx
+++ b/client/src/components/boards/Board/GridView.jsx
@@ -11,7 +11,7 @@ import { useInView } from 'react-intersection-observer';
import { useTranslation } from 'react-i18next';
import { Button, Icon, Loader } from 'semantic-ui-react';
import { useWindowWidth } from '../../../lib/hooks';
-import { Masonry } from '../../../lib/custom-ui';
+import { Masonry, Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import { BoardMembershipRoles } from '../../../constants/Enums';
@@ -81,14 +81,16 @@ const GridView = React.memo(
{onCardPaste && clipboard && canPasteCard && (
-
+
+
+
)}
diff --git a/client/src/components/boards/Board/ListView.jsx b/client/src/components/boards/Board/ListView.jsx
index aa281324..712c6652 100755
--- a/client/src/components/boards/Board/ListView.jsx
+++ b/client/src/components/boards/Board/ListView.jsx
@@ -10,6 +10,7 @@ import { shallowEqual, useSelector } from 'react-redux';
import { useInView } from 'react-intersection-observer';
import { useTranslation } from 'react-i18next';
import { Button, Icon, Loader } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import { BoardMembershipRoles } from '../../../constants/Enums';
@@ -74,14 +75,16 @@ const ListView = React.memo(
{onCardPaste && clipboard && canPasteCard && (
-
+
+
+
)}
))}
diff --git a/client/src/components/boards/BoardActions/Filters.jsx b/client/src/components/boards/BoardActions/Filters.jsx
index b193a7ed..6b3138f6 100644
--- a/client/src/components/boards/BoardActions/Filters.jsx
+++ b/client/src/components/boards/BoardActions/Filters.jsx
@@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
import { useDidUpdate } from '../../../lib/hooks';
import { usePopup } from '../../../lib/popup';
-import { Input } from '../../../lib/custom-ui';
+import { Input, Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
@@ -162,11 +162,13 @@ const Filters = React.memo(() => {
{userIds.length === 0 && withCurrentUserSelector && (
-
+
+
+
)}
{userIds.map((userId) => (
diff --git a/client/src/components/boards/BoardActions/RightSide/RightSide.jsx b/client/src/components/boards/BoardActions/RightSide/RightSide.jsx
index 8eb5b936..aa2da471 100644
--- a/client/src/components/boards/BoardActions/RightSide/RightSide.jsx
+++ b/client/src/components/boards/BoardActions/RightSide/RightSide.jsx
@@ -5,7 +5,9 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
+import { Tooltip } from '../../../../lib/custom-ui';
import { usePopup } from '../../../../lib/popup';
import selectors from '../../../../selectors';
@@ -16,10 +18,20 @@ import ActionsStep from './ActionsStep';
import styles from './RightSide.module.scss';
+// One complete sentence per view rather than a view name interpolated into a
+// frame. A name that has to fit a slot has to be inflected to fit it, and the
+// frame is not the same shape in every language.
+const TOOLTIP_BY_VIEW = {
+ [BoardViews.KANBAN]: 'action.switchToKanbanView',
+ [BoardViews.GRID]: 'action.switchToGridView',
+ [BoardViews.LIST]: 'action.switchToListView',
+};
+
const RightSide = React.memo(() => {
const board = useSelector(selectors.selectCurrentBoard);
const dispatch = useDispatch();
+ const [t] = useTranslation();
const handleSelectViewClick = useCallback(
({ currentTarget: { value: view } }) => {
@@ -40,24 +52,27 @@ const RightSide = React.memo(() => {
{views.map((view) => (
-
+
+
+
))}
>
diff --git a/client/src/components/boards/Boards/Boards.jsx b/client/src/components/boards/Boards/Boards.jsx
index 24db0c9a..ada07ebd 100755
--- a/client/src/components/boards/Boards/Boards.jsx
+++ b/client/src/components/boards/Boards/Boards.jsx
@@ -6,7 +6,9 @@
import React, { useCallback, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
+import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import { closePopup, usePopup } from '../../../lib/popup';
import selectors from '../../../selectors';
@@ -32,6 +34,7 @@ const Boards = React.memo(() => {
});
const dispatch = useDispatch();
+ const [t] = useTranslation();
const tabsWrapperRef = useRef(null);
@@ -75,7 +78,9 @@ const Boards = React.memo(() => {
{placeholder}
{canAdd && (
-
+
+
+
)}
diff --git a/client/src/components/boards/Boards/Item.jsx b/client/src/components/boards/Boards/Item.jsx
index 2b9a8551..a9aa25cb 100644
--- a/client/src/components/boards/Boards/Item.jsx
+++ b/client/src/components/boards/Boards/Item.jsx
@@ -9,7 +9,9 @@ import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router';
import { Draggable } from 'react-beautiful-dnd';
+import { useTranslation } from 'react-i18next';
import { Button, Icon } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
@@ -40,6 +42,7 @@ const Item = React.memo(({ id, index }) => {
});
const dispatch = useDispatch();
+ const [t] = useTranslation();
const handleEditClick = useCallback(() => {
dispatch(entryActions.openBoardSettingsModal(id));
@@ -64,9 +67,11 @@ const Item = React.memo(({ id, index }) => {
{board.name}
{canEdit && (
-
+
+
+
)}
>
) : (
diff --git a/client/src/components/cards/Card/Card.jsx b/client/src/components/cards/Card/Card.jsx
index c1c7c5c7..847e4f81 100755
--- a/client/src/components/cards/Card/Card.jsx
+++ b/client/src/components/cards/Card/Card.jsx
@@ -7,7 +7,9 @@ import React, { useCallback, useContext, useMemo, useRef, useState } from 'react
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Button, Icon } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import { push } from '../../../lib/redux-router';
import { closePopup, usePopup } from '../../../lib/popup';
@@ -57,6 +59,7 @@ const Card = React.memo(({ id, isInline }) => {
});
const dispatch = useDispatch();
+ const [t] = useTranslation();
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
const [, , handleCardMouseEnter, handleCardMouseLeave] = useContext(BoardShortcutsContext);
@@ -151,9 +154,11 @@ const Card = React.memo(({ id, isInline }) => {
{canUseActions && (
-
+
+
+
)}
>
diff --git a/client/src/components/cards/CardModal/CardModal.jsx b/client/src/components/cards/CardModal/CardModal.jsx
index 4098e82d..32d3771e 100755
--- a/client/src/components/cards/CardModal/CardModal.jsx
+++ b/client/src/components/cards/CardModal/CardModal.jsx
@@ -6,7 +6,9 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Icon } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import { push } from '../../../lib/redux-router';
import selectors from '../../../selectors';
@@ -45,6 +47,7 @@ const CardModal = React.memo(() => {
});
const dispatch = useDispatch();
+ const [t] = useTranslation();
const handleClose = useCallback(() => {
dispatch(push(Paths.BOARDS.replace(':id', card.boardId)));
@@ -102,9 +105,11 @@ const CardModal = React.memo(() => {
onClose={handleClose}
>
{prevCardId && (
-
+
+
+
)}
{canEdit ? (
diff --git a/client/src/components/cards/CardModal/CustomFieldGroups/Item.jsx b/client/src/components/cards/CardModal/CustomFieldGroups/Item.jsx
index 23e37bb0..7e85ed0d 100644
--- a/client/src/components/cards/CardModal/CustomFieldGroups/Item.jsx
+++ b/client/src/components/cards/CardModal/CustomFieldGroups/Item.jsx
@@ -7,7 +7,9 @@ import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Button, Icon } from 'semantic-ui-react';
+import { Tooltip } from '../../../../lib/custom-ui';
import selectors from '../../../../selectors';
import { usePopupInClosableContext } from '../../../../hooks';
@@ -23,6 +25,7 @@ const Item = React.memo(({ id, dragHandleProps }) => {
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));
+ const [t] = useTranslation();
const canEdit = useSelector((state) => {
if (customFieldGroup.boardId) {
@@ -51,9 +54,11 @@ const Item = React.memo(({ id, dragHandleProps }) => {
{customFieldGroup.isPersisted && canEdit && (
-
+
+
+
)}
{customFieldGroup.name}
diff --git a/client/src/components/cards/CardModal/ProjectContent.jsx b/client/src/components/cards/CardModal/ProjectContent.jsx
index 99f1f033..9acf5d42 100644
--- a/client/src/components/cards/CardModal/ProjectContent.jsx
+++ b/client/src/components/cards/CardModal/ProjectContent.jsx
@@ -9,6 +9,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Button, Checkbox, Grid, Icon } from 'semantic-ui-react';
import { useDidUpdate } from '../../../lib/hooks';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
@@ -361,12 +362,14 @@ const ProjectContent = React.memo(() => {
onUserSelect={handleUserSelect}
onUserDeselect={handleUserDeselect}
>
-
+
+
+
)}
@@ -401,12 +404,14 @@ const ProjectContent = React.memo(() => {
onSelect={handleLabelSelect}
onDeselect={handleLabelDeselect}
>
-
+
+
+
)}
@@ -465,17 +470,19 @@ const ProjectContent = React.memo(() => {
)}
{canEditStopwatch && (
-
+
+
+
)}
)}
@@ -507,9 +514,11 @@ const ProjectContent = React.memo(() => {
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */
-
+
+
+
{card.description}
) : (
@@ -554,14 +563,16 @@ const ProjectContent = React.memo(() => {
{t('common.list')}
{canUseLists ? (
-
+
+
) : (
diff --git a/client/src/components/cards/CardModal/StoryContent.jsx b/client/src/components/cards/CardModal/StoryContent.jsx
index a7150e83..5e6b7b60 100644
--- a/client/src/components/cards/CardModal/StoryContent.jsx
+++ b/client/src/components/cards/CardModal/StoryContent.jsx
@@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next';
import { Gallery, Item as GalleryItem } from 'react-photoswipe-gallery';
import { Button, Grid, Icon } from 'semantic-ui-react';
import { useDidUpdate } from '../../../lib/hooks';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
@@ -383,12 +384,14 @@ const StoryContent = React.memo(() => {
onSelect={handleLabelSelect}
onDeselect={handleLabelDeselect}
>
-
-
-
+
+
+
+
+
)}
@@ -419,9 +422,11 @@ const StoryContent = React.memo(() => {
className={classNames(styles.descriptionText, styles.cursorPointer)}
onClick={handleEditDescriptionClick}
>
-
-
-
+
+
+
+
+
{card.description}
) : (
@@ -477,14 +482,16 @@ const StoryContent = React.memo(() => {
{t('common.list')}
{canUseLists ? (
-
-
-
-
- {list.name || t(`common.${list.type}`)}
+
+
+
+
+
+ {list.name || t(`common.${list.type}`)}
+
-
-
+
+
) : (
diff --git a/client/src/components/cards/CardModal/TaskLists/Item.jsx b/client/src/components/cards/CardModal/TaskLists/Item.jsx
index a93be777..1aa69195 100644
--- a/client/src/components/cards/CardModal/TaskLists/Item.jsx
+++ b/client/src/components/cards/CardModal/TaskLists/Item.jsx
@@ -9,8 +9,10 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { Draggable } from 'react-beautiful-dnd';
+import { useTranslation } from 'react-i18next';
import { Button, Icon } from 'semantic-ui-react';
import { useToggle } from '../../../../lib/hooks';
+import { Tooltip } from '../../../../lib/custom-ui';
import selectors from '../../../../selectors';
import { usePopupInClosableContext } from '../../../../hooks';
@@ -24,6 +26,7 @@ const Item = React.memo(({ id, index }) => {
const selectTaskListById = useMemo(() => selectors.makeSelectTaskListById(), []);
const taskList = useSelector((state) => selectTaskListById(state, id));
+ const [t] = useTranslation();
const canEdit = useSelector((state) => {
const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
@@ -67,22 +70,30 @@ const Item = React.memo(({ id, index }) => {
{taskList.isPersisted && withActions && (
{taskList.hideCompletedTasks && (
-
-
-
+
+
+
+
)}
{canEdit && (
-
-
-
+
+
+
+
+
)}
diff --git a/client/src/components/common/AdministrationModal/UsersPane/Item.jsx b/client/src/components/common/AdministrationModal/UsersPane/Item.jsx
index 2289c484..39ec28ca 100755
--- a/client/src/components/common/AdministrationModal/UsersPane/Item.jsx
+++ b/client/src/components/common/AdministrationModal/UsersPane/Item.jsx
@@ -10,6 +10,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { Button, Icon, Table } from 'semantic-ui-react';
import { useEventCallback } from '../../../../lib/hooks';
+import { Tooltip } from '../../../../lib/custom-ui';
import selectors from '../../../../selectors';
import entryActions from '../../../../entry-actions';
@@ -92,9 +93,11 @@ const Item = React.memo(({ id, onEdit }) => {
-
-
-
+
+
+
+
+
diff --git a/client/src/components/common/Header/Header.jsx b/client/src/components/common/Header/Header.jsx
index df0c5253..e92af462 100755
--- a/client/src/components/common/Header/Header.jsx
+++ b/client/src/components/common/Header/Header.jsx
@@ -7,7 +7,9 @@ import React, { useCallback } from 'react';
import classNames from 'classnames';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router';
+import { useTranslation } from 'react-i18next';
import { Button, Icon, Menu } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import { usePopup } from '../../../lib/popup';
import selectors from '../../../selectors';
@@ -72,6 +74,7 @@ const Header = React.memo(() => {
}, shallowEqual);
const dispatch = useDispatch();
+ const [t] = useTranslation();
const handleToggleFavoritesClick = useCallback(() => {
dispatch(entryActions.toggleFavorites(!isFavoritesEnabled));
@@ -102,55 +105,69 @@ const Header = React.memo(() => {
-
+
+
+
);
diff --git a/client/src/components/custom-field-groups/CustomFieldGroupsStep/Item.jsx b/client/src/components/custom-field-groups/CustomFieldGroupsStep/Item.jsx
index 11b3f375..cd20218f 100755
--- a/client/src/components/custom-field-groups/CustomFieldGroupsStep/Item.jsx
+++ b/client/src/components/custom-field-groups/CustomFieldGroupsStep/Item.jsx
@@ -8,7 +8,9 @@ import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Draggable } from 'react-beautiful-dnd';
+import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
@@ -18,6 +20,7 @@ const Item = React.memo(({ id, index, onEdit }) => {
const selectCustomFieldGroupById = useMemo(() => selectors.makeSelectCustomFieldGroupById(), []);
const customFieldGroup = useSelector((state) => selectCustomFieldGroupById(state, id));
+ const [t] = useTranslation();
const handleEditClick = useCallback(() => {
onEdit(id);
@@ -35,14 +38,16 @@ const Item = React.memo(({ id, index, onEdit }) => {
>
{customFieldGroup.name}
-
+
+
+
);
diff --git a/client/src/components/custom-fields/CustomField/CustomField.jsx b/client/src/components/custom-fields/CustomField/CustomField.jsx
index 3e811dd3..f19c3b84 100644
--- a/client/src/components/custom-fields/CustomField/CustomField.jsx
+++ b/client/src/components/custom-fields/CustomField/CustomField.jsx
@@ -6,7 +6,9 @@
import React, { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Button, Icon } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
@@ -49,6 +51,7 @@ const CustomField = React.memo(({ id, customFieldGroupId }) => {
});
const dispatch = useDispatch();
+ const [t] = useTranslation();
const [isCopied, setIsCopied] = useState(false);
const handleValueUpdate = useCallback(
@@ -95,9 +98,11 @@ const CustomField = React.memo(({ id, customFieldGroupId }) => {
)}
{customFieldValue && customFieldValue.content && (
-
-
-
+
+
+
+
+
)}
diff --git a/client/src/components/labels/LabelsStep/Item.jsx b/client/src/components/labels/LabelsStep/Item.jsx
index 281cda06..8d757f48 100755
--- a/client/src/components/labels/LabelsStep/Item.jsx
+++ b/client/src/components/labels/LabelsStep/Item.jsx
@@ -11,7 +11,9 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { Draggable } from 'react-beautiful-dnd';
+import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import { BoardMembershipRoles } from '../../../constants/Enums';
@@ -23,6 +25,7 @@ const Item = React.memo(({ id, index, isActive, onSelect, onDeselect, onEdit })
const selectLabelById = useMemo(() => selectors.makeSelectLabelById(), []);
const label = useSelector((state) => selectLabelById(state, id));
+ const [t] = useTranslation();
const canEdit = useSelector((state) => {
const boardMembership = selectors.selectCurrentUserMembershipForCurrentBoard(state);
@@ -63,14 +66,16 @@ const Item = React.memo(({ id, index, isActive, onSelect, onDeselect, onEdit })
{label.name}
{canEdit && (
-
+
+
+
)}
);
diff --git a/client/src/components/lists/List/List.jsx b/client/src/components/lists/List/List.jsx
index e6658d30..453d1701 100755
--- a/client/src/components/lists/List/List.jsx
+++ b/client/src/components/lists/List/List.jsx
@@ -13,6 +13,7 @@ import { useTranslation } from 'react-i18next';
import { Draggable, Droppable } from 'react-beautiful-dnd';
import { Button, Icon } from 'semantic-ui-react';
import { useDidUpdate, useToggle, useTransitioning } from '../../../lib/hooks';
+import { Tooltip } from '../../../lib/custom-ui';
import { usePopup } from '../../../lib/popup';
import selectors from '../../../selectors';
@@ -240,16 +241,20 @@ const List = React.memo(({ id, index }) => {
{list.isPersisted &&
(canEdit ? (
-
-
-
+
+
+
+
+
) : (
canArchiveCards && (
-
-
-
+
+
+
+
+
)
))}
@@ -275,14 +280,16 @@ const List = React.memo(({ id, index }) => {
{clipboard && canPasteCard && (
-
-
-
+
+
+
+
+
)}
)}
diff --git a/client/src/components/notification-services/NotificationServices/Item.jsx b/client/src/components/notification-services/NotificationServices/Item.jsx
index fa8a7ec8..acb3cab9 100644
--- a/client/src/components/notification-services/NotificationServices/Item.jsx
+++ b/client/src/components/notification-services/NotificationServices/Item.jsx
@@ -6,8 +6,10 @@
import React, { useCallback, useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Button, Dropdown, Form, Icon, Input } from 'semantic-ui-react';
import { useDidUpdate, usePrevious, useToggle } from '../../../lib/hooks';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import entryActions from '../../../entry-actions';
@@ -31,6 +33,7 @@ const Item = React.memo(({ id }) => {
const notificationService = useSelector((state) => selectNotificationServiceById(state, id));
const dispatch = useDispatch();
+ const [t] = useTranslation();
const defaultData = useMemo(
() => ({
@@ -183,24 +186,32 @@ const Item = React.memo(({ id }) => {
onChange={handleFieldChange}
onBlur={handleUrlBlur}
/>
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
);
diff --git a/client/src/components/notification-services/NotificationServices/NotificationServices.jsx b/client/src/components/notification-services/NotificationServices/NotificationServices.jsx
index edd5657c..2f9b07ec 100644
--- a/client/src/components/notification-services/NotificationServices/NotificationServices.jsx
+++ b/client/src/components/notification-services/NotificationServices/NotificationServices.jsx
@@ -6,9 +6,10 @@
import React, { useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-import { Trans } from 'react-i18next';
+import { Trans, useTranslation } from 'react-i18next';
import { Button, Dropdown, Form, Icon, Input, Message } from 'semantic-ui-react';
import { useDidUpdate, useToggle } from '../../../lib/hooks';
+import { Tooltip } from '../../../lib/custom-ui';
import { useEscapeInterceptor, useForm, useNestedRef } from '../../../hooks';
import { NotificationServiceFormats } from '../../../constants/Enums';
@@ -22,6 +23,8 @@ const DEFAULT_DATA = {
};
const NotificationServices = React.memo(({ ids, onCreate }) => {
+ const [t] = useTranslation();
+
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
const [focusUrlFieldState, focusUrlField] = useToggle();
@@ -117,9 +120,11 @@ const NotificationServices = React.memo(({ ids, onCreate }) => {
onChange={handleFieldChange}
onBlur={handleUrlBlur}
/>
-
-
-
+
+
+
+
+
)}
>
diff --git a/client/src/components/project-managers/ProjectManagers/ProjectManagers.jsx b/client/src/components/project-managers/ProjectManagers/ProjectManagers.jsx
index b8eb8e90..a903d135 100644
--- a/client/src/components/project-managers/ProjectManagers/ProjectManagers.jsx
+++ b/client/src/components/project-managers/ProjectManagers/ProjectManagers.jsx
@@ -5,7 +5,9 @@
import React from 'react';
import { useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
import { Button } from 'semantic-ui-react';
+import { Tooltip } from '../../../lib/custom-ui';
import selectors from '../../../selectors';
import { usePopupInClosableContext } from '../../../hooks';
@@ -18,6 +20,7 @@ import styles from './ProjectManagers.module.scss';
const ProjectManagers = React.memo(() => {
const projectManagers = useSelector(selectors.selectManagersForCurrentProject);
+ const [t] = useTranslation();
const canAdd = useSelector((state) => {
const user = selectors.selectCurrentUser(state);
@@ -47,7 +50,9 @@ const ProjectManagers = React.memo(() => {
))}
{canAdd && (
-
+
+
+
)}
diff --git a/client/src/components/projects/ProjectSettingsModal/BackgroundPane/Image.jsx b/client/src/components/projects/ProjectSettingsModal/BackgroundPane/Image.jsx
index 9692caa0..b65ada80 100644
--- a/client/src/components/projects/ProjectSettingsModal/BackgroundPane/Image.jsx
+++ b/client/src/components/projects/ProjectSettingsModal/BackgroundPane/Image.jsx
@@ -5,7 +5,9 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
import { Button, Icon, Label } from 'semantic-ui-react';
+import { Tooltip } from '../../../../lib/custom-ui';
import { usePopupInClosableContext } from '../../../../hooks';
import ConfirmationStep from '../../../common/ConfirmationStep';
@@ -13,6 +15,8 @@ import ConfirmationStep from '../../../common/ConfirmationStep';
import styles from './Image.module.scss';
const Image = React.memo(({ url, isActive, onSelect, onDeselect, onDelete }) => {
+ const [t] = useTranslation();
+
const handleClick = useCallback(() => {
if (isActive) {
onDeselect();
@@ -52,9 +56,11 @@ const Image = React.memo(({ url, isActive, onSelect, onDeselect, onDelete }) =>
buttonContent="action.deleteBackgroundImage"
onConfirm={onDelete}
>
-
-
-
+
+
+
+
+
)}
diff --git a/client/src/components/task-lists/TaskList/Task/Task.jsx b/client/src/components/task-lists/TaskList/Task/Task.jsx
index 104c414f..4ec0ad3e 100755
--- a/client/src/components/task-lists/TaskList/Task/Task.jsx
+++ b/client/src/components/task-lists/TaskList/Task/Task.jsx
@@ -10,8 +10,10 @@ import classNames from 'classnames';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router';
import { Draggable } from 'react-beautiful-dnd';
+import { useTranslation } from 'react-i18next';
import { Button, Checkbox, Icon } from 'semantic-ui-react';
import { useDidUpdate } from '../../../../lib/hooks';
+import { Tooltip } from '../../../../lib/custom-ui';
import selectors from '../../../../selectors';
import entryActions from '../../../../entry-actions';
@@ -60,6 +62,7 @@ const Task = React.memo(({ id, index }) => {
}, shallowEqual);
const dispatch = useDispatch();
+ const [t] = useTranslation();
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
const [, , setIsClosableActive] = useContext(ClosableContext);
@@ -203,16 +206,20 @@ const Task = React.memo(({ id, index }) => {
className={styles.assigneeUserAvatar}
/>
) : (
-
-
-
+
+
+
+
+
)}
)}
-
-
-
+
+
+
+
+
>
) : (
diff --git a/client/src/hooks/use-closable-modal.jsx b/client/src/hooks/use-closable-modal.jsx
index f165fecb..ab337cde 100644
--- a/client/src/hooks/use-closable-modal.jsx
+++ b/client/src/hooks/use-closable-modal.jsx
@@ -5,6 +5,7 @@
import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
import { Modal } from 'semantic-ui-react';
import useClosable from './use-closable';
@@ -22,6 +23,8 @@ export default (initialClosableValue) => {
const ClosableModal = useMemo(() => {
// eslint-disable-next-line no-shadow
const ClosableModal = React.memo(({ closeIcon, onClose, ...props }) => {
+ const [t] = useTranslation();
+
const handleClose = useCallback(
(event) => {
if (isClosableActiveRef.current) {
@@ -41,16 +44,32 @@ export default (initialClosableValue) => {
[closeIcon, onClose],
);
+ const semanticCloseIcon =
+ closeIcon === true
+ ? {
+ name: 'close',
+ 'aria-label': t('action.close'),
+ title: t('action.close'),
+ }
+ : closeIcon;
+
return (
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
-
+
);
});
ClosableModal.propTypes = {
- closeIcon: PropTypes.bool,
+ closeIcon: PropTypes.oneOfType([
+ PropTypes.bool,
+ PropTypes.shape({
+ name: PropTypes.string,
+ 'aria-label': PropTypes.string,
+ title: PropTypes.string,
+ }),
+ ]),
onClose: PropTypes.func,
};
diff --git a/client/src/lib/custom-ui/components/Popup/PopupHeader.jsx b/client/src/lib/custom-ui/components/Popup/PopupHeader.jsx
index 7790d955..b88bfc88 100755
--- a/client/src/lib/custom-ui/components/Popup/PopupHeader.jsx
+++ b/client/src/lib/custom-ui/components/Popup/PopupHeader.jsx
@@ -5,16 +5,26 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
+import Tooltip from '../Tooltip';
import styles from './PopupHeader.module.css';
-const PopupHeader = React.memo(({ children, onBack }) => (
-
- {onBack && }
- {children}
-
-));
+const PopupHeader = React.memo(({ children, onBack }) => {
+ const [t] = useTranslation();
+
+ return (
+
+ {onBack && (
+
+
+
+ )}
+ {children}
+
+ );
+});
PopupHeader.propTypes = {
children: PropTypes.node.isRequired,
diff --git a/client/src/lib/custom-ui/components/Tooltip/Tooltip.jsx b/client/src/lib/custom-ui/components/Tooltip/Tooltip.jsx
new file mode 100644
index 00000000..50bd625d
--- /dev/null
+++ b/client/src/lib/custom-ui/components/Tooltip/Tooltip.jsx
@@ -0,0 +1,69 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import React, { useMemo } from 'react';
+import PropTypes from 'prop-types';
+import { Popup as SemanticUIPopup } from 'semantic-ui-react';
+
+import styles from './Tooltip.module.css';
+
+const callAll =
+ (...callbacks) =>
+ (...args) => {
+ callbacks.forEach((callback) => {
+ if (callback) {
+ callback(...args);
+ }
+ });
+ };
+
+const Tooltip = React.forwardRef(
+ ({ children, content, disabled, position, 'aria-label': ariaLabel, onClick, ...props }, ref) => {
+ const trigger = useMemo(() => {
+ const contentAriaLabel = typeof content === 'string' ? content : undefined;
+
+ return React.cloneElement(children, {
+ ...props,
+ ref,
+ 'aria-label': children.props['aria-label'] || ariaLabel || contentAriaLabel,
+ onClick: callAll(children.props.onClick, onClick),
+ });
+ }, [children, content, props, ref, ariaLabel, onClick]);
+
+ if (disabled || !content) {
+ return trigger;
+ }
+
+ return (
+
+ );
+ },
+);
+
+Tooltip.propTypes = {
+ children: PropTypes.element.isRequired,
+ content: PropTypes.node.isRequired,
+ disabled: PropTypes.bool,
+ 'aria-label': PropTypes.string,
+ onClick: PropTypes.func,
+ position: PropTypes.string,
+};
+
+Tooltip.defaultProps = {
+ disabled: false,
+ 'aria-label': undefined,
+ onClick: undefined,
+ position: 'top center',
+};
+
+export default React.memo(Tooltip);
diff --git a/client/src/lib/custom-ui/components/Tooltip/Tooltip.module.css b/client/src/lib/custom-ui/components/Tooltip/Tooltip.module.css
new file mode 100644
index 00000000..8ce47f46
--- /dev/null
+++ b/client/src/lib/custom-ui/components/Tooltip/Tooltip.module.css
@@ -0,0 +1,12 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+:global(#app) .wrapper {
+ border-radius: 3px;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 16px;
+ padding: 6px 8px;
+}
diff --git a/client/src/lib/custom-ui/components/Tooltip/index.js b/client/src/lib/custom-ui/components/Tooltip/index.js
new file mode 100644
index 00000000..67140d74
--- /dev/null
+++ b/client/src/lib/custom-ui/components/Tooltip/index.js
@@ -0,0 +1,8 @@
+/*!
+ * Copyright (c) 2024 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import Tooltip from './Tooltip';
+
+export default Tooltip;
diff --git a/client/src/lib/custom-ui/index.js b/client/src/lib/custom-ui/index.js
index 9ad0d929..7820717a 100755
--- a/client/src/lib/custom-ui/index.js
+++ b/client/src/lib/custom-ui/index.js
@@ -7,5 +7,6 @@ import Input from './components/Input';
import Popup from './components/Popup';
import Masonry from './components/Masonry';
import FilePicker from './components/FilePicker';
+import Tooltip from './components/Tooltip';
-export { Input, Popup, Masonry, FilePicker };
+export { Input, Popup, Masonry, FilePicker, Tooltip };
diff --git a/client/src/lib/popup/use-popup.jsx b/client/src/lib/popup/use-popup.jsx
index 3c487b83..147736b0 100644
--- a/client/src/lib/popup/use-popup.jsx
+++ b/client/src/lib/popup/use-popup.jsx
@@ -6,13 +6,17 @@
import { ResizeObserver } from '@juggle/resize-observer';
import React, { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
import { Button, Popup as SemanticUIPopup } from 'semantic-ui-react';
+import { Tooltip } from '../custom-ui';
import styles from './Popup.module.css';
export default (Step, { position, onOpen, onClose } = {}) => {
return useMemo(() => {
const Popup = React.forwardRef(({ children, ...stepProps }, ref) => {
+ const [t] = useTranslation();
+
const [stepParams, setStepParams] = useState(null);
const wrapperRef = useRef(null);
@@ -116,7 +120,9 @@ export default (Step, { position, onOpen, onClose } = {}) => {
onClick={handleClick}
>
-
+
+
+
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
diff --git a/client/src/locales/de-DE/core.js b/client/src/locales/de-DE/core.js
index 37f906a8..589033d8 100644
--- a/client/src/locales/de-DE/core.js
+++ b/client/src/locales/de-DE/core.js
@@ -32,6 +32,7 @@ export default {
addCustomField_title: 'Datenfeld hinzufügen',
addManager_title: 'Projektleiter hinzufügen',
addMember_title: 'Mitglied hinzufügen',
+ addNotificationService_title: 'Benachrichtigungsdienst hinzufügen',
addTaskList_title: 'Aufgaben hinzufügen',
addUser_title: 'Benutzer hinzufügen',
admin: 'Administrator',
@@ -187,6 +188,12 @@ export default {
lastUsed: 'Zuletzt verwendet',
never: 'Nie',
noTrustedDevices: 'Keine vertrauenswürdigen Geräte.',
+ openBoardActions: 'Arbeitsbereich-Aktionen öffnen',
+ openCardActions: 'Kartenaktionen öffnen',
+ openListActions: 'Listenaktionen öffnen',
+ openNotifications: 'Benachrichtigungen öffnen',
+ openProjectActions: 'Projektaktionen öffnen',
+ openProjectSettings: 'Projekteinstellungen öffnen',
orEnterSecretManually: 'Oder geben Sie dieses Geheimnis manuell ein:',
recoveryCodesExhausted:
'Alle Wiederherstellungscodes wurden verwendet. Erzeugen Sie neue Codes, um einen Fallback zu haben.',
@@ -206,6 +213,7 @@ export default {
scanQrCodeWithApp:
'Scannen Sie diesen QR-Code mit einer Authenticator-App wie Google Authenticator oder Authy.',
security_title: 'Sicherheit',
+ showHiddenProjects: 'Ausgeblendete Projekte anzeigen',
totpCode: 'TOTP-Code',
totpOrRecoveryCode: 'TOTP- oder Wiederherstellungscode',
trustedDevices_title: 'Vertrauenswürdige Browser',
@@ -469,6 +477,7 @@ export default {
addComment: 'Kommentar hinzufügen',
addCustomField: 'Datenfeld hinzufügen',
addCustomFieldGroup: 'Feldgruppe hinzufügen',
+ addLabel: 'Label hinzufügen',
addList: 'Liste hinzufügen',
addMember: 'Mitglied hinzufügen',
addMoreDetailedDescription: 'Eine detaillierte Beschreibung hinzufügen',
@@ -486,6 +495,7 @@ export default {
back: 'Zurück',
cancel: 'Abbrechen',
changePassword: 'Passwort ändern',
+ close: 'Schließen',
copy: 'Kopieren',
copyAll: 'Alle kopieren',
copyCard_title: 'Karte Kopieren',
@@ -528,6 +538,7 @@ export default {
deleteUser_title: 'Benutzer löschen',
deleteWebhook: 'Webhook löschen',
disable2fa: '2FA deaktivieren',
+ disableEditMode: 'Bearbeitungsmodus beenden',
dismissAll: 'Alle verwerfen',
done: 'Fertig',
download: 'Herunterladen',
@@ -550,6 +561,11 @@ export default {
emptyTrash: 'Papierkorb leeren',
emptyTrash_title: 'Papierkorb leeren',
enable2fa: '2FA aktivieren',
+ enableEditMode: 'Bearbeitungsmodus starten',
+ filterByCurrentUser: 'Nach aktuellem Benutzer filtern',
+ goBack: 'Zurück',
+ hideHiddenProjects: 'Ausgeblendete Projekte verbergen',
+ hideProjectList: 'Projektliste ausblenden',
import: 'Import',
join: 'Beitreten',
leave: 'Verlassen',
@@ -565,6 +581,7 @@ export default {
move: 'Verschieben',
moveCard_title: 'Karte bewegen',
moveList_title: 'Liste verschieben',
+ paste: 'Einfügen',
regenerate: 'Neu generieren',
regenerateApiKey: 'API-Schlüssel neu generieren',
regenerateRecoveryCodes: 'Wiederherstellungscodes neu generieren',
@@ -579,6 +596,7 @@ export default {
reset2fa: '2FA zurücksetzen',
restoreToList: 'Wiederherstellen in {{list}}',
returnToBoard: 'Zurück zum Arbeitsbereich',
+ returnToProjects: 'Zurück zu den Projekten',
revoke: 'Widerrufen',
save: 'Speichern',
sendTestEmail: 'Test-E-Mail senden',
@@ -589,11 +607,17 @@ export default {
showFewerAttachments: 'Weniger Anhänge anzeigen',
showLess: 'Weniger anzeigen',
showMore: 'Mehr anzeigen',
+ showProjectList: 'Projektliste anzeigen',
sortList_title: 'Liste sortieren',
start: 'Start',
stayLoggedIn: 'Angemeldet bleiben',
stop: 'Stopp',
subscribe: 'Abonnieren',
+ switchToGridProjectsView: 'Projekte als Raster anzeigen',
+ switchToGridView: 'Zur Rasteransicht wechseln',
+ switchToGroupedProjectsView: 'Projekte gruppiert anzeigen',
+ switchToKanbanView: 'Zur Kanban-Ansicht wechseln',
+ switchToListView: 'Zur Listenansicht wechseln',
unsubscribe: 'De-abonnieren',
uploadNewAvatar: 'Neuen Avatar hochladen',
uploadNewImage: 'Neues Bild hochladen',
diff --git a/client/src/locales/en-US/core.js b/client/src/locales/en-US/core.js
index 2e4ac772..18d391dd 100644
--- a/client/src/locales/en-US/core.js
+++ b/client/src/locales/en-US/core.js
@@ -27,6 +27,7 @@ export default {
addCustomField_title: 'Add Custom Field',
addManager_title: 'Add Manager',
addMember_title: 'Add Member',
+ addNotificationService_title: 'Add Notification Service',
addTaskList_title: 'Add Task List',
addUser_title: 'Add User',
admin: 'Admin',
@@ -166,6 +167,12 @@ export default {
lastUsed: 'Last used',
never: 'Never',
noTrustedDevices: 'No trusted devices.',
+ openBoardActions: 'Open board actions',
+ openCardActions: 'Open card actions',
+ openListActions: 'Open list actions',
+ openNotifications: 'Open notifications',
+ openProjectActions: 'Open project actions',
+ openProjectSettings: 'Open project settings',
orEnterSecretManually: 'Or enter this secret manually:',
recoveryCodesExhausted:
'All recovery codes have been used. Regenerate a new set to keep a fallback in case you lose your authenticator app.',
@@ -184,6 +191,7 @@ export default {
scanQrCodeWithApp:
'Scan this QR code with an authenticator app such as Google Authenticator or Authy.',
security_title: 'Security',
+ showHiddenProjects: 'Show hidden projects',
totpCode: 'TOTP code',
totpOrRecoveryCode: 'TOTP or recovery code',
trustedDevices_title: 'Trusted Browsers',
@@ -443,6 +451,7 @@ export default {
addComment: 'Add comment',
addCustomField: 'Add custom field',
addCustomFieldGroup: 'Add custom field group',
+ addLabel: 'Add label',
addList: 'Add list',
addMember: 'Add member',
addMoreDetailedDescription: 'Add more detailed description',
@@ -460,6 +469,7 @@ export default {
back: 'Back',
cancel: 'Cancel',
changePassword: 'Change password',
+ close: 'Close',
copy: 'Copy',
copyAll: 'Copy all',
copyCard_title: 'Copy Card',
@@ -502,6 +512,7 @@ export default {
deleteUser_title: 'Delete User',
deleteWebhook: 'Delete webhook',
disable2fa: 'Disable 2FA',
+ disableEditMode: 'Disable edit mode',
dismissAll: 'Dismiss all',
done: 'Done',
download: 'Download',
@@ -524,6 +535,11 @@ export default {
emptyTrash: 'Empty trash',
emptyTrash_title: 'Empty Trash',
enable2fa: 'Enable 2FA',
+ enableEditMode: 'Enable edit mode',
+ filterByCurrentUser: 'Filter by current user',
+ goBack: 'Go back',
+ hideHiddenProjects: 'Hide hidden projects',
+ hideProjectList: 'Hide project list',
import: 'Import',
join: 'Join',
leave: 'Leave',
@@ -539,6 +555,7 @@ export default {
move: 'Move',
moveCard_title: 'Move Card',
moveList_title: 'Move List',
+ paste: 'Paste',
regenerate: 'Regenerate',
regenerateApiKey: 'Regenerate API key',
regenerateRecoveryCodes: 'Regenerate recovery codes',
@@ -553,6 +570,7 @@ export default {
reset2fa: 'Reset 2FA',
restoreToList: 'Restore to {{list}}',
returnToBoard: 'Return to board',
+ returnToProjects: 'Return to projects',
revoke: 'Revoke',
save: 'Save',
sendTestEmail: 'Send test email',
@@ -563,11 +581,17 @@ export default {
showFewerAttachments: 'Show fewer attachments',
showLess: 'Show less',
showMore: 'Show more',
+ showProjectList: 'Show project list',
sortList_title: 'Sort List',
start: 'Start',
stayLoggedIn: 'Stay logged in',
stop: 'Stop',
subscribe: 'Subscribe',
+ switchToGridProjectsView: 'Show projects as a grid',
+ switchToGridView: 'Switch to grid view',
+ switchToGroupedProjectsView: 'Show projects grouped',
+ switchToKanbanView: 'Switch to kanban view',
+ switchToListView: 'Switch to list view',
unsubscribe: 'Unsubscribe',
uploadNewAvatar: 'Upload new avatar',
uploadNewImage: 'Upload new image',
diff --git a/client/src/locales/ru-RU/core.js b/client/src/locales/ru-RU/core.js
index 96bfbd24..d968a9e6 100644
--- a/client/src/locales/ru-RU/core.js
+++ b/client/src/locales/ru-RU/core.js
@@ -32,6 +32,7 @@ export default {
addCustomField_title: 'Добавить настраиваемое поле',
addManager_title: 'Добавление менеджера',
addMember_title: 'Добавление участника',
+ addNotificationService_title: 'Добавить сервис уведомлений',
addTaskList_title: 'Добавить список задач',
addUser_title: 'Добавление пользователя',
admin: 'Админ',
@@ -300,6 +301,12 @@ export default {
onlyOneManagerShouldRemainToMakeThisProjectPrivate:
'Должен остаться только один менеджер, чтобы сделать этот проект частным',
openBoard_title: 'Откройте доску',
+ openBoardActions: 'Открыть действия доски',
+ openCardActions: 'Открыть действия карточки',
+ openListActions: 'Открыть действия списка',
+ openNotifications: 'Открыть уведомления',
+ openProjectActions: 'Открыть действия проекта',
+ openProjectSettings: 'Открыть настройки проекта',
optional_inline: 'необязательно',
orEnterSecretManually: 'Или введите этот секретный ключ вручную:',
organization: 'Организация',
@@ -367,6 +374,7 @@ export default {
settings: 'Настройки',
shared: 'Общий',
sharedWithMe_title: 'Общие со мной',
+ showHiddenProjects: 'Показать скрытые проекты',
showOnFrontOfCard: 'Показать на лицевой стороне карточки',
smtp: 'SMTP',
sortList_title: 'Сортировка списка',
@@ -459,6 +467,7 @@ export default {
addCustomField: 'Добавить настраиваемое поле',
addCustomFieldGroup: 'Добавить группу настраиваемых полей',
addList: 'Добавить список',
+ addLabel: 'Добавить метку',
addMember: 'Добавить участника',
addMoreDetailedDescription: 'Добавить более подробное описание',
addTask: 'Добавить задачу',
@@ -475,6 +484,7 @@ export default {
back: 'Назад',
cancel: 'Отменить',
changePassword: 'Изменить пароль',
+ close: 'Закрыть',
copy: 'Копировать',
copyAll: 'Копировать все',
copyCard_title: 'Копировать карточку',
@@ -536,9 +546,15 @@ export default {
editTitle_title: 'Изменить название',
editType_title: 'Изменить тип',
editUsername_title: 'Изменить имя пользователя',
+ enableEditMode: 'Включить режим редактирования',
emptyTrash: 'Очистить корзину',
emptyTrash_title: 'Очистить корзину',
+ disableEditMode: 'Выключить режим редактирования',
enable2fa: 'Включить 2FA',
+ filterByCurrentUser: 'Фильтр по текущему пользователю',
+ goBack: 'Назад',
+ hideHiddenProjects: 'Скрыть скрытые проекты',
+ hideProjectList: 'Скрыть список проектов',
import: 'Импорт',
join: 'Присоединиться',
leave: 'Покинуть',
@@ -554,6 +570,7 @@ export default {
move: 'Переместить',
moveCard_title: 'Переместить карточку',
moveList_title: 'Переместить список',
+ paste: 'Вставить',
regenerate: 'Перегенерировать',
regenerateApiKey: 'Перегенерировать ключ API',
regenerateRecoveryCodes: 'Перегенерировать коды восстановления',
@@ -568,9 +585,11 @@ export default {
reset2fa: 'Сбросить 2FA',
restoreToList: 'Восстановить в {{list}}',
returnToBoard: 'Вернуться на доску',
+ returnToProjects: 'Вернуться к проектам',
revoke: 'Отозвать',
save: 'Сохранить',
sendTestEmail: 'Отправить тестовое письмо',
+ showProjectList: 'Показать список проектов',
showActive: 'Показать активные',
showAllAttachments: 'Показать все вложения ({{hidden}} скрыто)',
showCardsWithThisUser: 'Показать карточки с этим пользователем',
@@ -583,6 +602,13 @@ export default {
stayLoggedIn: 'Остаться в системе',
stop: 'Остановить',
subscribe: 'Подписаться',
+ switchToGridProjectsView: 'Показать проекты сеткой',
+ switchToGridView: 'Переключиться на вид сетки',
+ switchToGroupedProjectsView: 'Показать проекты по группам',
+ switchToKanbanView: 'Переключиться на вид канбана',
+ switchToListView: 'Переключиться на вид списка',
+ unlinkSso: 'Отвязать SSO',
+ unlinkSso_title: 'Отвязать SSO',
unsubscribe: 'Отписаться',
uploadNewAvatar: 'Загрузить новый аватар',
uploadNewImage: 'Загрузить новое изображение',