diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 072a4bd73..602172dda 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -10,6 +10,7 @@ "BoardMember.schemeAdmin": "Admin", "BoardMember.schemeEditor": "Editor", "BoardMember.schemeNone": "None", + "BoardMember.schemeViewer": "Viewer", "BoardPage.newVersion": "A new version of Boards is available, click here to reload.", "BoardPage.syncFailed": "Board may be deleted or access revoked.", "BoardTemplateSelector.add-template": "New template", @@ -89,6 +90,7 @@ "Categories.CreateCategoryDialog.CreateText": "Create", "Categories.CreateCategoryDialog.Placeholder": "Name your category", "Categories.CreateCategoryDialog.UpdateText": "Update", + "CenterPanel.Login": "Login", "CenterPanel.Share": "Share", "ColorOption.selectColor": "Select {color} Color", "Comment.delete": "Delete", @@ -109,6 +111,10 @@ "ContentBlock.moveDown": "Move down", "ContentBlock.moveUp": "Move up", "ContentBlock.text": "text", + "DateRange.clear": "Clear", + "DateRange.empty": "Empty", + "DateRange.endDate": "End date", + "DateRange.today": "Today", "DeleteBoardDialog.confirm-cancel": "Cancel", "DeleteBoardDialog.confirm-delete": "Delete", "DeleteBoardDialog.confirm-info": "Are you sure you want to delete the board “{boardTitle}”? Deleting it will delete all cards in the board.", @@ -133,7 +139,6 @@ "GalleryCard.copyLink": "Copy link", "GalleryCard.delete": "Delete", "GalleryCard.duplicate": "Duplicate", - "General.BoardCount": "{count, plural, one {# Board} other {# Boards}}", "GroupBy.hideEmptyGroups": "Hide {count} empty groups", "GroupBy.showHiddenGroups": "Show {count} hidden groups", "GroupBy.ungroup": "Ungroup", @@ -144,6 +149,20 @@ "KanbanCard.untitled": "Untitled", "Mutator.new-card-from-template": "new card from template", "Mutator.new-template-from-card": "new template from card", + "OnboardingTour.AddComments.Body": "You can comment on issues, and even @mention your fellow Mattermost users to get their attention.", + "OnboardingTour.AddComments.Title": "Add comments", + "OnboardingTour.AddDescription.Body": "Add a description to your card so your teammates know what the card is about.", + "OnboardingTour.AddDescription.Title": "Add description", + "OnboardingTour.AddProperties.Body": "Add various properties to cards to make them more powerful!", + "OnboardingTour.AddProperties.Title": "Add properties", + "OnboardingTour.AddView.Body": "Go here to create a new view to organise your board using different layouts.", + "OnboardingTour.AddView.Title": "Add a new view", + "OnboardingTour.CopyLink.Body": "You can share your cards with teammates by copying the link and pasting it in a channel, Direct Message, or Group Message.", + "OnboardingTour.CopyLink.Title": "Copy link", + "OnboardingTour.OpenACard.Body": "Open a card to explore the powerful ways that Boards can help you organize your work.", + "OnboardingTour.OpenACard.Title": "Open a card", + "OnboardingTour.ShareBoard.Body": "You can share your board internally, within your team, or publish it publicly for visibility outside of your organization.", + "OnboardingTour.ShareBoard.Title": "Share board", "PropertyMenu.Delete": "Delete", "PropertyMenu.changeType": "Change property type", "PropertyMenu.selectType": "Select property type", @@ -172,6 +191,8 @@ "RegistrationLink.tokenRegenerated": "Registration link regenerated", "ShareBoard.PublishDescription": "Publish and share a “read only” link with everyone on the web", "ShareBoard.PublishTitle": "Publish to the web", + "ShareBoard.ShareInternal": "Share internally", + "ShareBoard.ShareInternalDescription": "Users who have permissions will be able to use this link", "ShareBoard.Title": "Share Board", "ShareBoard.confirmRegenerateToken": "This will invalidate previously shared links. Continue?", "ShareBoard.copiedLink": "Copied!", @@ -187,18 +208,20 @@ "Sidebar.changePassword": "Change password", "Sidebar.delete-board": "Delete board", "Sidebar.duplicate-board": "Duplicate board", - "Sidebar.template-from-board": "New template from board", "Sidebar.export-archive": "Export archive", "Sidebar.import": "Import", "Sidebar.import-archive": "Import archive", "Sidebar.invite-users": "Invite users", "Sidebar.logout": "Log out", "Sidebar.no-boards-in-category": "No boards inside", + "Sidebar.product-tour": "Product tour", "Sidebar.random-icons": "Random icons", "Sidebar.set-language": "Set language", "Sidebar.set-theme": "Set theme", "Sidebar.settings": "Settings", + "Sidebar.template-from-board": "New template from board", "Sidebar.untitled-board": "(Untitled Board)", + "Sidebar.untitled-view": "(Untitled View)", "SidebarCategories.BlocksMenu.Move": "Move To...", "SidebarCategories.CategoryMenu.CreateNew": "Create New Category", "SidebarCategories.CategoryMenu.Delete": "Delete Category", @@ -217,6 +240,9 @@ "TableHeaderMenu.sort-descending": "Sort descending", "TableRow.open": "Open", "TopBar.give-feedback": "Give Feedback", + "URLProperty.copiedLink": "Copied!", + "URLProperty.copy": "Copy", + "URLProperty.edit": "Edit", "ValueSelector.noOptions": "No options. Start typing to add the first one!", "ValueSelector.valueSelector": "Value selector", "ValueSelectorLabel.openMenu": "Open menu", @@ -257,49 +283,32 @@ "ViewTitle.random-icon": "Random", "ViewTitle.remove-icon": "Remove icon", "ViewTitle.show-description": "show description", - "ViewTitle.untitled-board": "Untitled board", + "ViewTitle.untitled-board": "Untitled Board", "WelcomePage.Description": "Boards is a project management tool that helps define, organize, track and manage work across teams, using a familiar kanban board view", "WelcomePage.Explore.Button": "Take a tour", - "WelcomePage.NoThanks.Text": "No thanks, I'll figure it out myself", "WelcomePage.Heading": "Welcome To Boards", + "WelcomePage.NoThanks.Text": "No thanks, I'll figure it out myself", "Workspace.editing-board-template": "You're editing a board template.", "calendar.month": "Month", "calendar.today": "TODAY", "calendar.week": "Week", + "createImageBlock.failed": "Unable to upload the file. File size limit reached.", "default-properties.badges": "Comments and Description", "default-properties.title": "Title", - "error.relogin": "Log in again", + "error.page.title": "Sorry, something went wrong", + "generic.previous": "Previous", + "imagePaste.upload-failed": "Some files not uploaded. File size limit reached", "login.log-in-button": "Log in", "login.log-in-title": "Log in", - "error.workspace-undefined": "Not a valid workspace.", - "error.page.title": "Sorry, something went wrong", - "error.not-logged-in": "Your session may have expired or you're not logged in.", - "error.back-to-home": "Back to Home", - "error.back-to-boards": "Back to boards", - "error.go-login": "Log in", - "error.unknown": "An error occurred.", "login.register-button": "or create an account if you don't have one", "register.login-button": "or log in if you already have an account", - "OnboardingTour.OpenACard.Title": "Open a card", - "OnboardingTour.OpenACard.Body": "Open a card to explore the powerful ways that Boards can help you organize your work.", - "OnboardingTour.AddProperties.Title": "Add properties", - "OnboardingTour.AddProperties.Body": "Add various properties to cards to make them more powerful!", - "OnboardingTour.AddComments.Title": "Add comments", - "OnboardingTour.AddComments.Body": "You can comment on issues, and even @mention your fellow Mattermost users to get their attention.", - "OnboardingTour.AddDescription.Title": "Add description", - "OnboardingTour.AddDescription.Body": "Add a description to your card so your teammates know what the card is about.", - "OnboardingTour.AddView.Title": "Add a new view", - "OnboardingTour.AddView.Body": "Go here to create a new view to organise your board using different layouts.", - "OnboardingTour.CopyLink.Title": "Copy link", - "OnboardingTour.CopyLink.Body": "You can share your cards with teammates by copying the link and pasting it in a channel, Direct Message, or Group Message.", - "OnboardingTour.ShareBoard.Title": "Share board", - "OnboardingTour.ShareBoard.Body": "You can share your board internally, within your team, or publish it publicly for visibility outside of your organization.", + "register.signup-title": "Sign up for your account", + "share-board.publish": "Publish", + "share-board.share": "Share", + "shareBoard.lastAdmin": "Boards must have at least one Administrator", "tutorial_tip.finish_tour": "Done", "tutorial_tip.got_it": "Got it", "tutorial_tip.ok": "Next", - "generic.previous": "Previous", - "tutorial_tip.seen": "Seen this before?", - "tutorial_tip.out": "Opt out of these tips", - "register.signup-title": "Sign up for your account", - "shareBoard.lastAdmin": "Boards must have at least one Administrator" -} + "tutorial_tip.out": "Opt out of these tips.", + "tutorial_tip.seen": "Seen this before?" +} \ No newline at end of file diff --git a/webapp/package.json b/webapp/package.json index 63d5bfd4e..ce9cdc94c 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -12,7 +12,7 @@ "check": "eslint --ext .tsx,.ts . --quiet --cache && stylelint **/*.scss", "fix": "eslint --ext .tsx,.ts . --quiet --fix --cache && stylelint --fix **/*.scss", "fix:scss": "prettier --write './src/**/*.scss'", - "i18n-extract": "formatjs extract ../mattermost-plugin/webapp/src/*/*/*.ts? src/*/*/*.ts? src/*/*.ts? src/*.ts? --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json", + "i18n-extract": "formatjs extract ../mattermost-plugin/webapp/src/*/*/*.ts? src/*.ts? src/*/*.ts? src/*/*/*.ts? src/*/*/*/*.ts? --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json", "runserver-test": "cd cypress && \"../../bin/focalboard-server\"", "cypress:ci": "start-server-and-test runserver-test http://localhost:8088 cypress:run", "cypress:run": "cypress run", diff --git a/webapp/src/components/addContentMenuItem.tsx b/webapp/src/components/addContentMenuItem.tsx index e2a9d8ade..3292f6f16 100644 --- a/webapp/src/components/addContentMenuItem.tsx +++ b/webapp/src/components/addContentMenuItem.tsx @@ -37,7 +37,7 @@ const AddContentMenuItem = (props:Props): JSX.Element => { name={handler.getDisplayText(intl)} icon={handler.getIcon()} onClick={async () => { - const newBlock = await handler.createBlock(card.boardId) + const newBlock = await handler.createBlock(card.boardId, intl) newBlock.parentId = card.id newBlock.boardId = card.boardId diff --git a/webapp/src/components/cardDetail/cardDetailContext.tsx b/webapp/src/components/cardDetail/cardDetailContext.tsx index ed3639094..026cc99ea 100644 --- a/webapp/src/components/cardDetail/cardDetailContext.tsx +++ b/webapp/src/components/cardDetail/cardDetailContext.tsx @@ -44,7 +44,7 @@ export const CardDetailProvider = (props: CardDetailProps): ReactElement => { }) const {card} = props const addBlock = useCallback(async (handler: ContentHandler, index: number, auto: boolean) => { - const block = await handler.createBlock(card.boardId) + const block = await handler.createBlock(card.boardId, intl) block.parentId = card.id block.boardId = card.boardId const typeName = handler.getDisplayText(intl) diff --git a/webapp/src/components/cardDetail/imagePaste.tsx b/webapp/src/components/cardDetail/imagePaste.tsx index 2174402e1..e94386b6d 100644 --- a/webapp/src/components/cardDetail/imagePaste.tsx +++ b/webapp/src/components/cardDetail/imagePaste.tsx @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import {useEffect, useCallback} from 'react' +import {useIntl} from 'react-intl' import {ImageBlock, createImageBlock} from '../../blocks/imageBlock' import {sendFlashMessage} from '../flashMessages' @@ -9,6 +10,7 @@ import octoClient from '../../octoClient' import mutator from '../../mutator' export default function useImagePaste(boardId: string, cardId: string, contentOrder: Array): void { + const intl = useIntl() const uploadItems = useCallback(async (items: FileList) => { let newImage: File|null = null const uploads: Promise[] = [] @@ -40,7 +42,7 @@ export default function useImagePaste(boardId: string, cardId: string, contentOr } if (someFilesNotUploaded) { - sendFlashMessage({content: "Some files not uploaded. File size limit reached", severity: 'normal'}) + sendFlashMessage({content: intl.formatMessage({id: 'imagePaste.upload-failed', defaultMessage: 'Some files not uploaded. File size limit reached'}), severity: 'normal'}) } mutator.performAsUndoGroup(async () => { diff --git a/webapp/src/components/content/contentRegistry.tsx b/webapp/src/components/content/contentRegistry.tsx index 09b4c4e7b..9d657f58d 100644 --- a/webapp/src/components/content/contentRegistry.tsx +++ b/webapp/src/components/content/contentRegistry.tsx @@ -11,7 +11,7 @@ export type ContentHandler = { type: BlockTypes, getDisplayText: (intl: IntlShape) => string, getIcon: () => JSX.Element, - createBlock: (boardId: string) => Promise, + createBlock: (boardId: string, intl: IntlShape) => Promise, createComponent: (block: ContentBlock, readonly: boolean, onAddElement?: () => void, onDeleteElement?: () => void) => JSX.Element, } diff --git a/webapp/src/components/content/imageElement.tsx b/webapp/src/components/content/imageElement.tsx index 36d166ac8..408dfcb5d 100644 --- a/webapp/src/components/content/imageElement.tsx +++ b/webapp/src/components/content/imageElement.tsx @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useEffect, useState} from 'react' +import {IntlShape} from 'react-intl' import {ContentBlock} from '../../blocks/contentBlock' import {ImageBlock, createImageBlock} from '../../blocks/imageBlock' @@ -45,9 +46,9 @@ const ImageElement = (props: Props): JSX.Element|null => { contentRegistry.registerContentType({ type: 'image', - getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}), + getDisplayText: (intl: IntlShape) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}), getIcon: () => , - createBlock: async (boardId: string) => { + createBlock: async (boardId: string, intl: IntlShape) => { return new Promise( (resolve) => { Utils.selectLocalFile(async (file) => { @@ -58,7 +59,7 @@ contentRegistry.registerContentType({ block.fields.fileId = fileId || '' resolve(block) } else { - sendFlashMessage({content: 'Unable to upload the file. File size limit reached.', severity: 'normal'}) + sendFlashMessage({content: intl.formatMessage({id: 'createImageBlock.failed', defaultMessage: 'Unable to upload the file. File size limit reached.'}), severity: 'normal'}) } }, '.jpg,.jpeg,.png,.gif')