From 3d1f54f62f6bfdbbcfdbeab356896fca2a62be89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Glatzl?= Date: Wed, 11 Nov 2020 22:17:36 +0100 Subject: [PATCH] new translation labels & Portuguese from Portugal translation --- app/components/Avatar/AvatarWithPresence.js | 24 ++- app/components/Breadcrumb.js | 11 +- .../DocumentPreview/DocumentPreview.js | 17 +- app/components/DropdownMenu/DropdownMenu.js | 9 +- app/components/InputSearch.js | 6 +- app/components/Sidebar/Main.js | 24 +-- .../Sidebar/components/Collections.js | 8 +- .../Sidebar/components/DocumentLink.js | 5 +- app/index.js | 3 +- app/menus/AccountMenu.js | 28 +-- app/menus/CollectionMenu.js | 23 +-- app/menus/DocumentMenu.js | 77 +++++---- app/menus/GroupMenu.js | 16 +- app/menus/NewChildDocumentMenu.js | 10 +- app/menus/NewDocumentMenu.js | 9 +- app/menus/NewTemplateMenu.js | 8 +- app/menus/RevisionMenu.js | 14 +- app/menus/ShareMenu.js | 16 +- app/menus/TemplatesMenu.js | 11 +- app/menus/UserMenu.js | 37 ++-- app/scenes/Collection.js | 43 +++-- .../AddGroupsToCollection.js | 29 ++-- .../AddPeopleToCollection.js | 25 +-- .../CollectionGroupMemberListItem.js | 15 +- .../components/MemberListItem.js | 25 ++- .../components/UserListItem.js | 15 +- app/scenes/Settings/Profile.js | 1 + shared/translations/de_DE.json | 160 +++++++++++++++-- shared/translations/default.json | 124 ++++++++++++++ shared/translations/i18n.js | 15 +- shared/translations/i18n.test.js | 136 +++++++-------- shared/translations/pt_PT.json | 161 +++++++++++++++--- 32 files changed, 802 insertions(+), 303 deletions(-) diff --git a/app/components/Avatar/AvatarWithPresence.js b/app/components/Avatar/AvatarWithPresence.js index 0f1e97b8a2..8c2647110d 100644 --- a/app/components/Avatar/AvatarWithPresence.js +++ b/app/components/Avatar/AvatarWithPresence.js @@ -4,6 +4,7 @@ import { observable } from "mobx"; import { observer } from "mobx-react"; import { EditIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import styled from "styled-components"; import User from "models/User"; import UserProfile from "scenes/UserProfile"; @@ -18,6 +19,7 @@ type Props = { lastViewedAt: string, }; +@withTranslation() @observer class AvatarWithPresence extends React.Component { @observable isOpen: boolean = false; @@ -37,6 +39,7 @@ class AvatarWithPresence extends React.Component { isPresent, isEditing, isCurrentUser, + t, } = this.props; return ( @@ -44,13 +47,20 @@ class AvatarWithPresence extends React.Component { - {user.name} {isCurrentUser && "(You)"} -
- {isPresent - ? isEditing - ? "currently editing" - : "currently viewing" - : `viewed ${distanceInWordsToNow(new Date(lastViewedAt))} ago`} + {t( + "{{ userName }} {{ you }}
{{ action }}
", + { + userName: user.name, + you: isCurrentUser && t("(You)"), + action: isPresent + ? isEditing + ? t("currently editing") + : t("currently viewing") + : t("viewed {{ timeAgo }} ago", { + timeAgo: distanceInWordsToNow(new Date(lastViewedAt)), + }), + } + )} } placement="bottom" diff --git a/app/components/Breadcrumb.js b/app/components/Breadcrumb.js index 79466dc40e..73a2347860 100644 --- a/app/components/Breadcrumb.js +++ b/app/components/Breadcrumb.js @@ -1,4 +1,5 @@ // @flow +import i18n from "i18next"; import { observer, inject } from "mobx-react"; import { ArchiveIcon, @@ -21,6 +22,8 @@ import Flex from "components/Flex"; import BreadcrumbMenu from "./BreadcrumbMenu"; import { collectionUrl } from "utils/routeHelpers"; +const t = (k) => i18n.t(k); + type Props = { document: Document, collections: CollectionsStore, @@ -34,7 +37,7 @@ function Icon({ document }) {   - Trash + {t("Trash")} @@ -46,7 +49,7 @@ function Icon({ document }) {   - Archive + {t("Archive")} @@ -58,7 +61,7 @@ function Icon({ document }) {   - Drafts + {t("Drafts")} @@ -70,7 +73,7 @@ function Icon({ document }) {   - Templates + {t("Templates")} diff --git a/app/components/DocumentPreview/DocumentPreview.js b/app/components/DocumentPreview/DocumentPreview.js index 6f41db5fd0..8f7cac18b4 100644 --- a/app/components/DocumentPreview/DocumentPreview.js +++ b/app/components/DocumentPreview/DocumentPreview.js @@ -3,6 +3,7 @@ import { observable } from "mobx"; import { observer } from "mobx-react"; import { StarredIcon, PlusIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Link, Redirect } from "react-router-dom"; import styled, { withTheme } from "styled-components"; import Document from "models/Document"; @@ -29,6 +30,7 @@ type Props = { const SEARCH_RESULT_REGEX = /]*>(.*?)<\/b>/gi; +@withTranslation() @observer class DocumentPreview extends React.Component { @observable redirectTo: ?string; @@ -72,6 +74,7 @@ class DocumentPreview extends React.Component { showTemplate, highlight, context, + t, } = this.props; if (this.redirectTo) { @@ -91,7 +94,7 @@ class DocumentPreview extends React.Component { > - {document.isNew && <Badge yellow>New</Badge>} + {document.isNew && <Badge yellow>{t("New")}</Badge>} {!document.isDraft && !document.isArchived && !document.isTemplate && ( @@ -104,12 +107,16 @@ class DocumentPreview extends React.Component<Props> { </Actions> )} {document.isDraft && showDraft && ( - <Tooltip tooltip="Only visible to you" delay={500} placement="top"> - <Badge>Draft</Badge> + <Tooltip + tooltip={t("Only visible to you")} + delay={500} + placement="top" + > + <Badge>{t("Draft")}</Badge> </Tooltip> )} {document.isTemplate && showTemplate && ( - <Badge primary>Template</Badge> + <Badge primary>{t("Template")}</Badge> )} <SecondaryActions> {document.isTemplate && @@ -120,7 +127,7 @@ class DocumentPreview extends React.Component<Props> { icon={<PlusIcon />} neutral > - New doc + {t("New doc")} </Button> )}   diff --git a/app/components/DropdownMenu/DropdownMenu.js b/app/components/DropdownMenu/DropdownMenu.js index f18ab02168..08e54043f0 100644 --- a/app/components/DropdownMenu/DropdownMenu.js +++ b/app/components/DropdownMenu/DropdownMenu.js @@ -5,6 +5,7 @@ import { observer } from "mobx-react"; import { MoreIcon } from "outline-icons"; import { rgba } from "polished"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { PortalWithState } from "react-portal"; import styled from "styled-components"; import { fadeAndScaleIn } from "shared/styles/animations"; @@ -29,6 +30,7 @@ type Props = { position?: "left" | "right" | "center", }; +@withTranslation() @observer class DropdownMenu extends React.Component<Props> { id: string = `menu${counter++}`; @@ -51,8 +53,9 @@ class DropdownMenu extends React.Component<Props> { ) => { return (ev: SyntheticMouseEvent<HTMLElement>) => { ev.preventDefault(); + const { t } = this.props; const currentTarget = ev.currentTarget; - invariant(document.body, "why you not here"); + invariant(document.body, t("why you not here")); if (currentTarget instanceof HTMLDivElement) { this.bodyRect = document.body.getBoundingClientRect(); @@ -150,7 +153,7 @@ class DropdownMenu extends React.Component<Props> { }; render() { - const { className, hover, label, children } = this.props; + const { className, hover, label, children, t } = this.props; return ( <div className={className}> @@ -177,7 +180,7 @@ class DropdownMenu extends React.Component<Props> { {label || ( <NudeButton id={`${this.id}button`} - aria-label="More options" + aria-label={t("More options")} aria-haspopup="true" aria-expanded={isOpen ? "true" : "false"} aria-controls={this.id} diff --git a/app/components/InputSearch.js b/app/components/InputSearch.js index 1ab086e1e9..92a16c8669 100644 --- a/app/components/InputSearch.js +++ b/app/components/InputSearch.js @@ -3,6 +3,7 @@ import { observable } from "mobx"; import { observer } from "mobx-react"; import { SearchIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import keydown from "react-keydown"; import { withRouter, type RouterHistory } from "react-router-dom"; import styled, { withTheme } from "styled-components"; @@ -18,6 +19,7 @@ type Props = { collectionId?: string, }; +@withTranslation() @observer class InputSearch extends React.Component<Props> { input: ?Input; @@ -51,13 +53,13 @@ class InputSearch extends React.Component<Props> { }; render() { - const { theme, placeholder = "Search…" } = this.props; + const { theme, t } = this.props; return ( <InputMaxWidth ref={(ref) => (this.input = ref)} type="search" - placeholder={placeholder} + placeholder={t("Search...")} onInput={this.handleSearchInput} icon={ <SearchIcon diff --git a/app/components/Sidebar/Main.js b/app/components/Sidebar/Main.js index 13e86d8791..23f394c817 100644 --- a/app/components/Sidebar/Main.js +++ b/app/components/Sidebar/Main.js @@ -12,6 +12,7 @@ import { PlusIcon, } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import styled from "styled-components"; import AuthStore from "stores/AuthStore"; @@ -36,6 +37,7 @@ type Props = { policies: PoliciesStore, }; +@withTranslation() @observer class MainSidebar extends React.Component<Props> { @observable inviteModalOpen = false; @@ -65,7 +67,7 @@ class MainSidebar extends React.Component<Props> { }; render() { - const { auth, documents, policies } = this.props; + const { auth, documents, policies, t } = this.props; const { user, team } = auth; if (!user || !team) return null; @@ -90,7 +92,7 @@ class MainSidebar extends React.Component<Props> { to="/home" icon={<HomeIcon color="currentColor" />} exact={false} - label="Home" + label={t("Home")} /> <SidebarLink to={{ @@ -98,20 +100,20 @@ class MainSidebar extends React.Component<Props> { state: { fromMenu: true }, }} icon={<SearchIcon color="currentColor" />} - label="Search" + label={t("Search")} exact={false} /> <SidebarLink to="/starred" icon={<StarredIcon color="currentColor" />} exact={false} - label="Starred" + label={t("Starred")} /> <SidebarLink to="/templates" icon={<ShapesIcon color="currentColor" />} exact={false} - label="Templates" + label={t("Templates")} active={ documents.active ? documents.active.template : undefined } @@ -121,7 +123,7 @@ class MainSidebar extends React.Component<Props> { icon={<EditIcon color="currentColor" />} label={ <Drafts align="center"> - Drafts + {t("Drafts")} {documents.totalDrafts > 0 && ( <Bubble count={documents.totalDrafts} /> )} @@ -146,7 +148,7 @@ class MainSidebar extends React.Component<Props> { to="/archive" icon={<ArchiveIcon color="currentColor" />} exact={false} - label="Archive" + label={t("Archive")} active={ documents.active ? documents.active.isArchived && !documents.active.isDeleted @@ -157,7 +159,7 @@ class MainSidebar extends React.Component<Props> { to="/trash" icon={<TrashIcon color="currentColor" />} exact={false} - label="Trash" + label={t("Trash")} active={ documents.active ? documents.active.isDeleted : undefined } @@ -167,21 +169,21 @@ class MainSidebar extends React.Component<Props> { to="/settings/people" onClick={this.handleInviteModalOpen} icon={<PlusIcon color="currentColor" />} - label="Invite people…" + label={t("Invite people…")} /> )} </Section> </Scrollable> </Flex> <Modal - title="Invite people" + title={t("Invite people")} onRequestClose={this.handleInviteModalClose} isOpen={this.inviteModalOpen} > <Invite onSubmit={this.handleInviteModalClose} /> </Modal> <Modal - title="Create a collection" + title={t("Create a collection")} onRequestClose={this.handleCreateCollectionModalClose} isOpen={this.createCollectionModalOpen} > diff --git a/app/components/Sidebar/components/Collections.js b/app/components/Sidebar/components/Collections.js index 10af481026..1caa7a202d 100644 --- a/app/components/Sidebar/components/Collections.js +++ b/app/components/Sidebar/components/Collections.js @@ -2,6 +2,7 @@ import { observer, inject } from "mobx-react"; import { PlusIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import keydown from "react-keydown"; import { withRouter, type RouterHistory } from "react-router-dom"; @@ -26,6 +27,7 @@ type Props = { ui: UiStore, }; +@withTranslation() @observer class Collections extends React.Component<Props> { isPreloaded: boolean = !!this.props.collections.orderedData.length; @@ -52,7 +54,7 @@ class Collections extends React.Component<Props> { } render() { - const { collections, ui, policies, documents } = this.props; + const { collections, ui, policies, documents, t } = this.props; const content = ( <> @@ -71,7 +73,7 @@ class Collections extends React.Component<Props> { to="/collections" onClick={this.props.onCreateCollection} icon={<PlusIcon color="currentColor" />} - label="New collection…" + label={t("New collection…")} exact /> </> @@ -79,7 +81,7 @@ class Collections extends React.Component<Props> { return ( <Flex column> - <Header>Collections</Header> + <Header>{t("Collections")}</Header> {collections.isLoaded ? ( this.isPreloaded ? ( content diff --git a/app/components/Sidebar/components/DocumentLink.js b/app/components/Sidebar/components/DocumentLink.js index 023c8afff3..91be986bec 100644 --- a/app/components/Sidebar/components/DocumentLink.js +++ b/app/components/Sidebar/components/DocumentLink.js @@ -2,6 +2,7 @@ import { observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import styled from "styled-components"; import DocumentsStore from "stores/DocumentsStore"; import Collection from "models/Collection"; @@ -25,6 +26,7 @@ type Props = {| depth: number, |}; +@withTranslation() @observer class DocumentLink extends React.Component<Props> { @observable menuOpen = false; @@ -84,6 +86,7 @@ class DocumentLink extends React.Component<Props> { prefetchDocument, depth, canUpdate, + t, } = this.props; const showChildren = !!( @@ -96,7 +99,7 @@ class DocumentLink extends React.Component<Props> { this.isActiveDocument()) ); const document = documents.get(node.id); - const title = node.title || "Untitled"; + const title = node.title || t("Untitled"); return ( <Flex diff --git a/app/index.js b/app/index.js index 55d9b8388f..67140439b0 100644 --- a/app/index.js +++ b/app/index.js @@ -6,6 +6,7 @@ import * as React from "react"; import { render } from "react-dom"; import { BrowserRouter as Router } from "react-router-dom"; +import { outlineTranslation } from "shared/translations/i18n"; import stores from "stores"; import ErrorBoundary from "components/ErrorBoundary"; import ScrollToTop from "components/ScrollToTop"; @@ -14,8 +15,6 @@ import Toasts from "components/Toasts"; import Routes from "./routes"; import env from "env"; -import { outlineTranslation } from "shared/translations/i18n"; - outlineTranslation.init(); const element = document.getElementById("root"); diff --git a/app/menus/AccountMenu.js b/app/menus/AccountMenu.js index 5e4a58809d..0406c78d14 100644 --- a/app/menus/AccountMenu.js +++ b/app/menus/AccountMenu.js @@ -3,6 +3,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import { SunIcon, MoonIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Link } from "react-router-dom"; import styled from "styled-components"; import AuthStore from "stores/AuthStore"; @@ -25,6 +26,7 @@ type Props = { auth: AuthStore, }; +@withTranslation() @observer class AccountMenu extends React.Component<Props> { @observable keyboardShortcutsOpen: boolean = false; @@ -42,14 +44,14 @@ class AccountMenu extends React.Component<Props> { }; render() { - const { ui } = this.props; + const { ui, t } = this.props; return ( <> <Modal isOpen={this.keyboardShortcutsOpen} onRequestClose={this.handleCloseKeyboardShortcuts} - title="Keyboard shortcuts" + title={t("Keyboard shortcuts")} > <KeyboardShortcuts /> </Modal> @@ -58,23 +60,23 @@ class AccountMenu extends React.Component<Props> { label={this.props.label} > <DropdownMenuItem as={Link} to={settings()}> - Settings + {t("Settings")} </DropdownMenuItem> <DropdownMenuItem onClick={this.handleOpenKeyboardShortcuts}> - Keyboard shortcuts + {t("Keyboard shortcuts")} </DropdownMenuItem> <DropdownMenuItem href={developers()} target="_blank"> - API documentation + {t("API documentation")} </DropdownMenuItem> <hr /> <DropdownMenuItem href={changelog()} target="_blank"> - Changelog + {t("Changelog")} </DropdownMenuItem> <DropdownMenuItem href={mailToUrl()} target="_blank"> - Send us feedback + {t("Send us feedback")} </DropdownMenuItem> <DropdownMenuItem href={githubIssuesUrl()} target="_blank"> - Report a bug + {t("Report a bug")} </DropdownMenuItem> <hr /> <DropdownMenu @@ -87,7 +89,7 @@ class AccountMenu extends React.Component<Props> { label={ <DropdownMenuItem> <ChangeTheme justify="space-between"> - Appearance + {t("Appearance")} {ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />} </ChangeTheme> </DropdownMenuItem> @@ -98,24 +100,24 @@ class AccountMenu extends React.Component<Props> { onClick={() => ui.setTheme("system")} selected={ui.theme === "system"} > - System + {t("System")} </DropdownMenuItem> <DropdownMenuItem onClick={() => ui.setTheme("light")} selected={ui.theme === "light"} > - Light + {t("Light")} </DropdownMenuItem> <DropdownMenuItem onClick={() => ui.setTheme("dark")} selected={ui.theme === "dark"} > - Dark + {t("Dark")} </DropdownMenuItem> </DropdownMenu> <hr /> <DropdownMenuItem onClick={this.handleLogout}> - Log out + {t("Log out")} </DropdownMenuItem> </DropdownMenu> </> diff --git a/app/menus/CollectionMenu.js b/app/menus/CollectionMenu.js index c7ab1d90c6..dbdab6e591 100644 --- a/app/menus/CollectionMenu.js +++ b/app/menus/CollectionMenu.js @@ -2,6 +2,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { withRouter, type RouterHistory } from "react-router-dom"; import DocumentsStore from "stores/DocumentsStore"; import PoliciesStore from "stores/PoliciesStore"; @@ -28,6 +29,7 @@ type Props = { onClose?: () => void, }; +@withTranslation() @observer class CollectionMenu extends React.Component<Props> { file: ?HTMLInputElement; @@ -111,6 +113,7 @@ class CollectionMenu extends React.Component<Props> { position, onOpen, onClose, + t, } = this.props; const can = policies.abilities(collection.id); @@ -127,7 +130,7 @@ class CollectionMenu extends React.Component<Props> { </VisuallyHidden> <Modal - title="Collection permissions" + title={t("Collection permissions")} onRequestClose={this.handleMembersModalClose} isOpen={this.showCollectionMembers} > @@ -143,40 +146,40 @@ class CollectionMenu extends React.Component<Props> { <> {can.update && ( <DropdownMenuItem onClick={this.onNewDocument}> - New document + {t("New document")} </DropdownMenuItem> )} {can.update && ( <DropdownMenuItem onClick={this.onImportDocument}> - Import document + {t("Import document")} </DropdownMenuItem> )} {can.update && <hr />} {can.update && ( <DropdownMenuItem onClick={this.handleEditCollectionOpen}> - Edit… + {t("Edit…")} </DropdownMenuItem> )} {can.update && ( <DropdownMenuItem onClick={this.handleMembersModalOpen}> - Permissions… + {t("Permissions…")} </DropdownMenuItem> )} {can.export && ( <DropdownMenuItem onClick={this.handleExportCollectionOpen}> - Export… + {t("Export…")} </DropdownMenuItem> )} </> )} {can.delete && ( <DropdownMenuItem onClick={this.handleDeleteCollectionOpen}> - Delete… + {t("Delete…")} </DropdownMenuItem> )} </DropdownMenu> <Modal - title="Edit collection" + title={t("Edit collection")} isOpen={this.showCollectionEdit} onRequestClose={this.handleEditCollectionClose} > @@ -186,7 +189,7 @@ class CollectionMenu extends React.Component<Props> { /> </Modal> <Modal - title="Delete collection" + title={t("Delete collection")} isOpen={this.showCollectionDelete} onRequestClose={this.handleDeleteCollectionClose} > @@ -196,7 +199,7 @@ class CollectionMenu extends React.Component<Props> { /> </Modal> <Modal - title="Export collection" + title={t("Export collection")} isOpen={this.showCollectionExport} onRequestClose={this.handleExportCollectionClose} > diff --git a/app/menus/DocumentMenu.js b/app/menus/DocumentMenu.js index 55a45015f7..a48ed80be0 100644 --- a/app/menus/DocumentMenu.js +++ b/app/menus/DocumentMenu.js @@ -2,6 +2,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Redirect } from "react-router-dom"; import AuthStore from "stores/AuthStore"; import CollectionStore from "stores/CollectionsStore"; @@ -43,6 +44,7 @@ type Props = { onClose?: () => void, }; +@withTranslation() @observer class DocumentMenu extends React.Component<Props> { @observable redirectTo: ?string; @@ -86,7 +88,8 @@ class DocumentMenu extends React.Component<Props> { // when duplicating, go straight to the duplicated document content this.redirectTo = duped.url; - this.props.ui.showToast("Document duplicated"); + const { t } = this.props; + this.props.ui.showToast(t("Document duplicated")); }; handleOpenTemplateModal = () => { @@ -103,7 +106,8 @@ class DocumentMenu extends React.Component<Props> { handleArchive = async (ev: SyntheticEvent<>) => { await this.props.document.archive(); - this.props.ui.showToast("Document archived"); + const { t } = this.props; + this.props.ui.showToast(t("Document archived")); }; handleRestore = async ( @@ -111,12 +115,14 @@ class DocumentMenu extends React.Component<Props> { options?: { collectionId: string } ) => { await this.props.document.restore(options); - this.props.ui.showToast("Document restored"); + const { t } = this.props; + this.props.ui.showToast(t("Document restored")); }; handleUnpublish = async (ev: SyntheticEvent<>) => { await this.props.document.unpublish(); - this.props.ui.showToast("Document unpublished"); + const { t } = this.props; + this.props.ui.showToast(t("Document unpublished")); }; handlePin = (ev: SyntheticEvent<>) => { @@ -167,6 +173,7 @@ class DocumentMenu extends React.Component<Props> { label, onOpen, onClose, + t, } = this.props; const can = policies.abilities(document.id); @@ -185,17 +192,17 @@ class DocumentMenu extends React.Component<Props> { > {can.unarchive && ( <DropdownMenuItem onClick={this.handleRestore}> - Restore + {t("Restore")} </DropdownMenuItem> )} {can.restore && (collection ? ( <DropdownMenuItem onClick={this.handleRestore}> - Restore + {t("Restore")} </DropdownMenuItem> ) : ( <DropdownMenu - label={<DropdownMenuItem>Restore…</DropdownMenuItem>} + label={<DropdownMenuItem>{t("Restore…")}</DropdownMenuItem>} style={{ left: -170, position: "relative", @@ -203,7 +210,7 @@ class DocumentMenu extends React.Component<Props> { }} hover > - <Header>Choose a collection</Header> + <Header>{t("Choose a collection")}</Header> {collections.orderedData.map((collection) => { const can = policies.abilities(collection.id); @@ -226,42 +233,42 @@ class DocumentMenu extends React.Component<Props> { (document.pinned ? can.unpin && ( <DropdownMenuItem onClick={this.handleUnpin}> - Unpin + {t("Unpin")} </DropdownMenuItem> ) : can.pin && ( <DropdownMenuItem onClick={this.handlePin}> - Pin to collection + {t("Pin to collection")} </DropdownMenuItem> ))} {document.isStarred ? can.unstar && ( <DropdownMenuItem onClick={this.handleUnstar}> - Unstar + {t("Unstar")} </DropdownMenuItem> ) : can.star && ( <DropdownMenuItem onClick={this.handleStar}> - Star + {t("Star")} </DropdownMenuItem> )} {canShareDocuments && ( <DropdownMenuItem onClick={this.handleShareLink} - title="Create a public share link" + title={t("Create a public share link")} > - Share link… + {t("Share link…")} </DropdownMenuItem> )} {showToggleEmbeds && ( <> {document.embedsDisabled ? ( <DropdownMenuItem onClick={document.enableEmbeds}> - Enable embeds + {t("Enable embeds")} </DropdownMenuItem> ) : ( <DropdownMenuItem onClick={document.disableEmbeds}> - Disable embeds + {t("Disable embeds")} </DropdownMenuItem> )} </> @@ -271,61 +278,69 @@ class DocumentMenu extends React.Component<Props> { {can.createChildDocument && ( <DropdownMenuItem onClick={this.handleNewChild} - title="Create a nested document inside the current document" + title={t("Create a nested document inside the current document")} > - New nested document + {t("New nested document")} </DropdownMenuItem> )} {can.update && !document.isTemplate && ( <DropdownMenuItem onClick={this.handleOpenTemplateModal}> - Create template… + {t("Create template…")} </DropdownMenuItem> )} {can.unpublish && ( <DropdownMenuItem onClick={this.handleUnpublish}> - Unpublish + {t("Unpublish")} </DropdownMenuItem> )} {can.update && ( - <DropdownMenuItem onClick={this.handleEdit}>Edit</DropdownMenuItem> + <DropdownMenuItem onClick={this.handleEdit}> + {t("Edit")} + </DropdownMenuItem> )} {can.update && ( <DropdownMenuItem onClick={this.handleDuplicate}> - Duplicate + {t("Duplicate")} </DropdownMenuItem> )} {can.archive && ( <DropdownMenuItem onClick={this.handleArchive}> - Archive + {t("Archive")} </DropdownMenuItem> )} {can.delete && ( <DropdownMenuItem onClick={this.handleDelete}> - Delete… + {t("Delete…")} </DropdownMenuItem> )} {can.move && ( - <DropdownMenuItem onClick={this.handleMove}>Move…</DropdownMenuItem> + <DropdownMenuItem onClick={this.handleMove}> + {t("Move…")} + </DropdownMenuItem> )} <hr /> {canViewHistory && ( <> <DropdownMenuItem onClick={this.handleDocumentHistory}> - History + {t("History")} </DropdownMenuItem> </> )} {can.download && ( <DropdownMenuItem onClick={this.handleExport}> - Download + {t("Download")} </DropdownMenuItem> )} {showPrint && ( - <DropdownMenuItem onClick={window.print}>Print</DropdownMenuItem> + <DropdownMenuItem onClick={window.print}> + {t("Print")} + </DropdownMenuItem> )} </DropdownMenu> <Modal - title={`Delete ${this.props.document.noun}`} + title={t("Delete {{ documentName }}", { + documentName: this.props.document.noun, + })} onRequestClose={this.handleCloseDeleteModal} isOpen={this.showDeleteModal} > @@ -335,7 +350,7 @@ class DocumentMenu extends React.Component<Props> { /> </Modal> <Modal - title="Create template" + title={t("Create template")} onRequestClose={this.handleCloseTemplateModal} isOpen={this.showTemplateModal} > @@ -345,7 +360,7 @@ class DocumentMenu extends React.Component<Props> { /> </Modal> <Modal - title="Share document" + title={t("Share document")} onRequestClose={this.handleCloseShareModal} isOpen={this.showShareModal} > diff --git a/app/menus/GroupMenu.js b/app/menus/GroupMenu.js index f37d56dcba..620b0c01eb 100644 --- a/app/menus/GroupMenu.js +++ b/app/menus/GroupMenu.js @@ -2,6 +2,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { withRouter, type RouterHistory } from "react-router-dom"; import PoliciesStore from "stores/PoliciesStore"; import UiStore from "stores/UiStore"; @@ -22,6 +23,7 @@ type Props = { onClose?: () => void, }; +@withTranslation() @observer class GroupMenu extends React.Component<Props> { @observable editModalOpen: boolean = false; @@ -46,13 +48,13 @@ class GroupMenu extends React.Component<Props> { }; render() { - const { policies, group, onOpen, onClose } = this.props; + const { policies, group, onOpen, onClose, t } = this.props; const can = policies.abilities(group.id); return ( <> <Modal - title="Edit group" + title={t("Edit group")} onRequestClose={this.handleEditModalClose} isOpen={this.editModalOpen} > @@ -63,7 +65,7 @@ class GroupMenu extends React.Component<Props> { </Modal> <Modal - title="Delete group" + title={t("Delete group")} onRequestClose={this.handleDeleteModalClose} isOpen={this.deleteModalOpen} > @@ -77,18 +79,20 @@ class GroupMenu extends React.Component<Props> { {group && ( <> <DropdownMenuItem onClick={this.props.onMembers}> - Members… + {t("Members…")} </DropdownMenuItem> {(can.update || can.delete) && <hr />} {can.update && ( - <DropdownMenuItem onClick={this.onEdit}>Edit…</DropdownMenuItem> + <DropdownMenuItem onClick={this.onEdit}> + {t("Edit…")} + </DropdownMenuItem> )} {can.delete && ( <DropdownMenuItem onClick={this.onDelete}> - Delete… + {t("Delete…")} </DropdownMenuItem> )} </> diff --git a/app/menus/NewChildDocumentMenu.js b/app/menus/NewChildDocumentMenu.js index f6e25d5535..54665f467e 100644 --- a/app/menus/NewChildDocumentMenu.js +++ b/app/menus/NewChildDocumentMenu.js @@ -3,6 +3,7 @@ import { observable } from "mobx"; import { observer, inject } from "mobx-react"; import { MoreIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Redirect } from "react-router-dom"; import CollectionsStore from "stores/CollectionsStore"; @@ -16,6 +17,7 @@ type Props = { collections: CollectionsStore, }; +@withTranslation() @observer class NewChildDocumentMenu extends React.Component<Props> { @observable redirectTo: ?string; @@ -39,19 +41,19 @@ class NewChildDocumentMenu extends React.Component<Props> { render() { if (this.redirectTo) return <Redirect to={this.redirectTo} push />; - const { label, document, collections, ...rest } = this.props; + const { label, document, collections, t, ...rest } = this.props; const collection = collections.get(document.collectionId); return ( <DropdownMenu label={label || <MoreIcon />} {...rest}> <DropdownMenuItem onClick={this.handleNewDocument}> <span> - New document in{" "} - <strong>{collection ? collection.name : "collection"}</strong> + {t("New document in")}{" "} + <strong>{collection ? collection.name : t("collection")}</strong> </span> </DropdownMenuItem> <DropdownMenuItem onClick={this.handleNewChild}> - New nested document + {t("New nested document")} </DropdownMenuItem> </DropdownMenu> ); diff --git a/app/menus/NewDocumentMenu.js b/app/menus/NewDocumentMenu.js index a85d2520c4..38995357e7 100644 --- a/app/menus/NewDocumentMenu.js +++ b/app/menus/NewDocumentMenu.js @@ -3,6 +3,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import { PlusIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Redirect } from "react-router-dom"; import CollectionsStore from "stores/CollectionsStore"; @@ -24,6 +25,7 @@ type Props = { policies: PoliciesStore, }; +@withTranslation() @observer class NewDocumentMenu extends React.Component<Props> { @observable redirectTo: ?string; @@ -47,7 +49,7 @@ class NewDocumentMenu extends React.Component<Props> { render() { if (this.redirectTo) return <Redirect to={this.redirectTo} push />; - const { collections, documents, policies, label, ...rest } = this.props; + const { collections, documents, policies, label, t, ...rest } = this.props; const singleCollection = collections.orderedData.length === 1; return ( @@ -55,14 +57,15 @@ class NewDocumentMenu extends React.Component<Props> { label={ label || ( <Button icon={<PlusIcon />} small> - New doc{singleCollection ? "" : "…"} + {t("New doc")} + {singleCollection ? "" : "…"} </Button> ) } onOpen={this.onOpen} {...rest} > - <Header>Choose a collection</Header> + <Header>{t("Choose a collection")}</Header> {collections.orderedData.map((collection) => { const can = policies.abilities(collection.id); diff --git a/app/menus/NewTemplateMenu.js b/app/menus/NewTemplateMenu.js index ed3d94a825..86d0cb339b 100644 --- a/app/menus/NewTemplateMenu.js +++ b/app/menus/NewTemplateMenu.js @@ -3,6 +3,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import { PlusIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Redirect } from "react-router-dom"; import CollectionsStore from "stores/CollectionsStore"; @@ -22,6 +23,7 @@ type Props = { policies: PoliciesStore, }; +@withTranslation() @observer class NewTemplateMenu extends React.Component<Props> { @observable redirectTo: ?string; @@ -39,20 +41,20 @@ class NewTemplateMenu extends React.Component<Props> { render() { if (this.redirectTo) return <Redirect to={this.redirectTo} push />; - const { collections, policies, label, ...rest } = this.props; + const { collections, policies, label, t, ...rest } = this.props; return ( <DropdownMenu label={ label || ( <Button icon={<PlusIcon />} small> - New template… + {t("New template…")} </Button> ) } {...rest} > - <Header>Choose a collection</Header> + <Header>{t("Choose a collection")}</Header> {collections.orderedData.map((collection) => { const can = policies.abilities(collection.id); diff --git a/app/menus/RevisionMenu.js b/app/menus/RevisionMenu.js index 888ee0ebd0..bfcabf7b37 100644 --- a/app/menus/RevisionMenu.js +++ b/app/menus/RevisionMenu.js @@ -1,6 +1,7 @@ // @flow import { inject } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { withRouter, type RouterHistory } from "react-router-dom"; import UiStore from "stores/UiStore"; @@ -21,20 +22,23 @@ type Props = { ui: UiStore, }; +@withTranslation() class RevisionMenu extends React.Component<Props> { handleRestore = async (ev: SyntheticEvent<>) => { ev.preventDefault(); await this.props.document.restore({ revisionId: this.props.revision.id }); - this.props.ui.showToast("Document restored"); + const { t } = this.props; + this.props.ui.showToast(t("Document restored")); this.props.history.push(this.props.document.url); }; handleCopy = () => { - this.props.ui.showToast("Link copied"); + const { t } = this.props; + this.props.ui.showToast(t("Link copied")); }; render() { - const { className, label, onOpen, onClose } = this.props; + const { className, label, onOpen, onClose, t } = this.props; const url = `${window.location.origin}${documentHistoryUrl( this.props.document, this.props.revision.id @@ -48,11 +52,11 @@ class RevisionMenu extends React.Component<Props> { label={label} > <DropdownMenuItem onClick={this.handleRestore}> - Restore version + {t("Restore version")} </DropdownMenuItem> <hr /> <CopyToClipboard text={url} onCopy={this.handleCopy}> - <DropdownMenuItem>Copy link</DropdownMenuItem> + <DropdownMenuItem>{t("Copy link")}</DropdownMenuItem> </CopyToClipboard> </DropdownMenu> ); diff --git a/app/menus/ShareMenu.js b/app/menus/ShareMenu.js index d950a5d331..f73746a638 100644 --- a/app/menus/ShareMenu.js +++ b/app/menus/ShareMenu.js @@ -2,6 +2,7 @@ import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Redirect } from "react-router-dom"; import SharesStore from "stores/SharesStore"; @@ -18,6 +19,7 @@ type Props = { share: Share, }; +@withTranslation() @observer class ShareMenu extends React.Component<Props> { @observable redirectTo: ?string; @@ -36,32 +38,34 @@ class ShareMenu extends React.Component<Props> { try { await this.props.shares.revoke(this.props.share); - this.props.ui.showToast("Share link revoked"); + const { t } = this.props; + this.props.ui.showToast(t("Share link revoked")); } catch (err) { this.props.ui.showToast(err.message); } }; handleCopy = () => { - this.props.ui.showToast("Share link copied"); + const { t } = this.props; + this.props.ui.showToast(t("Share link copied")); }; render() { if (this.redirectTo) return <Redirect to={this.redirectTo} push />; - const { share, onOpen, onClose } = this.props; + const { share, onOpen, onClose, t } = this.props; return ( <DropdownMenu onOpen={onOpen} onClose={onClose}> <CopyToClipboard text={share.url} onCopy={this.handleCopy}> - <DropdownMenuItem>Copy link</DropdownMenuItem> + <DropdownMenuItem>{t("Copy link")}</DropdownMenuItem> </CopyToClipboard> <DropdownMenuItem onClick={this.handleGoToDocument}> - Go to document + {t("Go to document")} </DropdownMenuItem> <hr /> <DropdownMenuItem onClick={this.handleRevoke}> - Revoke link + {t("Revoke link")} </DropdownMenuItem> </DropdownMenu> ); diff --git a/app/menus/TemplatesMenu.js b/app/menus/TemplatesMenu.js index 653ba27919..1a112a32ee 100644 --- a/app/menus/TemplatesMenu.js +++ b/app/menus/TemplatesMenu.js @@ -2,6 +2,7 @@ import { observer, inject } from "mobx-react"; import { DocumentIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import styled from "styled-components"; import DocumentsStore from "stores/DocumentsStore"; import Document from "models/Document"; @@ -13,10 +14,11 @@ type Props = { documents: DocumentsStore, }; +@withTranslation() @observer class TemplatesMenu extends React.Component<Props> { render() { - const { documents, document, ...rest } = this.props; + const { documents, document, t, ...rest } = this.props; const templates = documents.templatesInCollection(document.collectionId); if (!templates.length) { @@ -28,7 +30,7 @@ class TemplatesMenu extends React.Component<Props> { position="left" label={ <Button disclosure neutral> - Templates + {t("Templates")} </Button> } {...rest} @@ -42,7 +44,10 @@ class TemplatesMenu extends React.Component<Props> { <div> <strong>{template.titleWithDefault}</strong> <br /> - <Author>By {template.createdBy.name}</Author> + <Author> + {" "} + {t("By {{ author }}", { author: template.createdBy.name })} + </Author> </div> </DropdownMenuItem> ))} diff --git a/app/menus/UserMenu.js b/app/menus/UserMenu.js index 603eae0d16..a0da1adb2f 100644 --- a/app/menus/UserMenu.js +++ b/app/menus/UserMenu.js @@ -2,6 +2,7 @@ import { inject, observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import UsersStore from "stores/UsersStore"; import User from "models/User"; import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; @@ -11,14 +12,18 @@ type Props = { users: UsersStore, }; +@withTranslation() @observer class UserMenu extends React.Component<Props> { handlePromote = (ev: SyntheticEvent<>) => { ev.preventDefault(); - const { user, users } = this.props; + const { user, users, t } = this.props; if ( !window.confirm( - `Are you want to make ${user.name} an admin? Admins can modify team and billing information.` + t( + "Are you want to make {{ userName }} an admin? Admins can modify team and billing information.", + { userName: user.name } + ) ) ) { return; @@ -28,8 +33,14 @@ class UserMenu extends React.Component<Props> { handleDemote = (ev: SyntheticEvent<>) => { ev.preventDefault(); - const { user, users } = this.props; - if (!window.confirm(`Are you want to make ${user.name} a member?`)) { + const { user, users, t } = this.props; + if ( + !window.confirm( + t("Are you want to make {{ userName }} a member?", { + userName: user.name, + }) + ) + ) { return; } users.demote(user); @@ -37,10 +48,12 @@ class UserMenu extends React.Component<Props> { handleSuspend = (ev: SyntheticEvent<>) => { ev.preventDefault(); - const { user, users } = this.props; + const { user, users, t } = this.props; if ( !window.confirm( - "Are you want to suspend this account? Suspended users will be prevented from logging in." + t( + "Are you want to suspend this account? Suspended users will be prevented from logging in." + ) ) ) { return; @@ -61,33 +74,33 @@ class UserMenu extends React.Component<Props> { }; render() { - const { user } = this.props; + const { user, t } = this.props; return ( <DropdownMenu> {user.isAdmin && ( <DropdownMenuItem onClick={this.handleDemote}> - Make {user.name} a member… + {t("Make {{ userName }} a member…", { userName: user.name })} </DropdownMenuItem> )} {!user.isAdmin && !user.isSuspended && ( <DropdownMenuItem onClick={this.handlePromote}> - Make {user.name} an admin… + {t("Make {{ userName }} an admin…", { userName: user.name })} </DropdownMenuItem> )} {!user.lastActiveAt && ( <DropdownMenuItem onClick={this.handleRevoke}> - Revoke invite… + {t("Revoke invite…")} </DropdownMenuItem> )} {user.lastActiveAt && (user.isSuspended ? ( <DropdownMenuItem onClick={this.handleActivate}> - Activate account + {t("Activate account")} </DropdownMenuItem> ) : ( <DropdownMenuItem onClick={this.handleSuspend}> - Suspend account… + {t("Suspend account…")} </DropdownMenuItem> ))} </DropdownMenu> diff --git a/app/scenes/Collection.js b/app/scenes/Collection.js index fa8e763279..390b603c68 100644 --- a/app/scenes/Collection.js +++ b/app/scenes/Collection.js @@ -4,6 +4,7 @@ import { observer, inject } from "mobx-react"; import { NewDocumentIcon, PlusIcon, PinIcon } from "outline-icons"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import { Redirect, Link, Switch, Route, type Match } from "react-router-dom"; import styled, { withTheme } from "styled-components"; @@ -49,6 +50,7 @@ type Props = { theme: Theme, }; +@withTranslation() @observer class CollectionScene extends React.Component<Props> { @observable collection: ?Collection; @@ -132,7 +134,7 @@ class CollectionScene extends React.Component<Props> { }; renderActions() { - const { match, policies } = this.props; + const { match, policies, t } = this.props; const can = policies.abilities(match.params.id || ""); return ( @@ -142,19 +144,19 @@ class CollectionScene extends React.Component<Props> { <Action> <InputSearch source="collection" - placeholder="Search in collection…" + placeholder={t("Search in collection…")} collectionId={match.params.id} /> </Action> <Action> <Tooltip - tooltip="New document" + tooltip={t("New document")} shortcut="n" delay={500} placement="bottom" > <Button onClick={this.onNewDocument} icon={<PlusIcon />}> - New doc + {t("New doc")} </Button> </Tooltip> </Action> @@ -169,7 +171,7 @@ class CollectionScene extends React.Component<Props> { } render() { - const { documents, theme } = this.props; + const { documents, theme, t } = this.props; if (this.redirectTo) return <Redirect to={this.redirectTo} push />; if (!this.isFetching && !this.collection) return <Search notFound />; @@ -188,26 +190,31 @@ class CollectionScene extends React.Component<Props> { {collection.isEmpty ? ( <Centered column> <HelpText> - <strong>{collection.name}</strong> doesn’t contain any - documents yet. - <br /> - Get started by creating a new one! + {t( + ` + <strong>{{ collectionName }}</strong> doesn’t contain any + documents yet. + <br /> + Get started by creating a new one! + `, + { collectionName: collection.name } + )} </HelpText> <Wrapper> <Link to={newDocumentUrl(collection.id)}> <Button icon={<NewDocumentIcon color={theme.buttonText} />}> - Create a document + {t("Create a document")} </Button> </Link>    {collection.private && ( <Button onClick={this.onPermissions} neutral> - Manage members… + {t("Manage members…")} </Button> )} </Wrapper> <Modal - title="Collection permissions" + title={t("Collection permissions")} onRequestClose={this.handlePermissionsModalClose} isOpen={this.permissionsModalOpen} > @@ -218,7 +225,7 @@ class CollectionScene extends React.Component<Props> { /> </Modal> <Modal - title="Edit collection" + title={t("Edit collection")} onRequestClose={this.handleEditModalClose} isOpen={this.editModalOpen} > @@ -249,7 +256,7 @@ class CollectionScene extends React.Component<Props> { {hasPinnedDocuments && ( <> <Subheading> - <TinyPinIcon size={18} /> Pinned + <TinyPinIcon size={18} /> {t("Pinned")} </Subheading> <DocumentList documents={pinnedDocuments} showPin /> </> @@ -257,16 +264,16 @@ class CollectionScene extends React.Component<Props> { <Tabs> <Tab to={collectionUrl(collection.id)} exact> - Recently updated + {t("Recently updated")} </Tab> <Tab to={collectionUrl(collection.id, "recent")} exact> - Recently published + {t("Recently published")} </Tab> <Tab to={collectionUrl(collection.id, "old")} exact> - Least recently updated + {t("Least recently updated")} </Tab> <Tab to={collectionUrl(collection.id, "alphabetical")} exact> - A–Z + {t("A–Z")} </Tab> </Tabs> <Switch> diff --git a/app/scenes/CollectionMembers/AddGroupsToCollection.js b/app/scenes/CollectionMembers/AddGroupsToCollection.js index 604fc103d0..02b35bd4a2 100644 --- a/app/scenes/CollectionMembers/AddGroupsToCollection.js +++ b/app/scenes/CollectionMembers/AddGroupsToCollection.js @@ -3,6 +3,7 @@ import { debounce } from "lodash"; import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import styled from "styled-components"; import AuthStore from "stores/AuthStore"; import CollectionGroupMembershipsStore from "stores/CollectionGroupMembershipsStore"; @@ -28,6 +29,7 @@ type Props = { onSubmit: () => void, }; +@withTranslation() @observer class AddGroupsToCollection extends React.Component<Props> { @observable newGroupModalOpen: boolean = false; @@ -53,49 +55,54 @@ class AddGroupsToCollection extends React.Component<Props> { }, 250); handleAddGroup = (group) => { + const { t } = this.props; try { this.props.collectionGroupMemberships.create({ collectionId: this.props.collection.id, groupId: group.id, permission: "read_write", }); - this.props.ui.showToast(`${group.name} was added to the collection`); + this.props.ui.showToast( + t("{{ groupName }} was added to the collection", { + groupName: group.name, + }) + ); } catch (err) { - this.props.ui.showToast("Could not add user"); + this.props.ui.showToast(t("Could not add user")); console.error(err); } }; render() { - const { groups, collection, auth } = this.props; + const { groups, collection, auth, t } = this.props; const { user, team } = auth; if (!user || !team) return null; return ( <Flex column> <HelpText> - Can’t find the group you’re looking for?{" "} + {t("Can’t find the group you’re looking for?")}{" "} <a role="button" onClick={this.handleNewGroupModalOpen}> - Create a group + {t("Create a group")} </a> . </HelpText> <Input type="search" - placeholder="Search by group name…" + placeholder={t("Search by group name…")} value={this.query} onChange={this.handleFilter} - label="Search groups" + label={t("Search groups")} labelHidden flex /> <PaginatedList empty={ this.query ? ( - <Empty>No groups matching your search</Empty> + <Empty>{t("No groups matching your search")}</Empty> ) : ( - <Empty>No groups left to add</Empty> + <Empty>{t("No groups left to add")}</Empty> ) } items={groups.notInCollection(collection.id, this.query)} @@ -108,7 +115,7 @@ class AddGroupsToCollection extends React.Component<Props> { renderActions={() => ( <ButtonWrap> <Button onClick={() => this.handleAddGroup(item)} neutral> - Add + {t("Add")} </Button> </ButtonWrap> )} @@ -116,7 +123,7 @@ class AddGroupsToCollection extends React.Component<Props> { )} /> <Modal - title="Create a group" + title={t("Create a group")} onRequestClose={this.handleNewGroupModalClose} isOpen={this.newGroupModalOpen} > diff --git a/app/scenes/CollectionMembers/AddPeopleToCollection.js b/app/scenes/CollectionMembers/AddPeopleToCollection.js index 50afbe6f6b..9f97a7de11 100644 --- a/app/scenes/CollectionMembers/AddPeopleToCollection.js +++ b/app/scenes/CollectionMembers/AddPeopleToCollection.js @@ -3,6 +3,7 @@ import { debounce } from "lodash"; import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import * as React from "react"; +import { withTranslation } from "react-i18next"; import AuthStore from "stores/AuthStore"; import MembershipsStore from "stores/MembershipsStore"; import UiStore from "stores/UiStore"; @@ -26,6 +27,7 @@ type Props = { onSubmit: () => void, }; +@withTranslation() @observer class AddPeopleToCollection extends React.Component<Props> { @observable inviteModalOpen: boolean = false; @@ -51,39 +53,42 @@ class AddPeopleToCollection extends React.Component<Props> { }, 250); handleAddUser = (user) => { + const { t } = this.props; try { this.props.memberships.create({ collectionId: this.props.collection.id, userId: user.id, permission: "read_write", }); - this.props.ui.showToast(`${user.name} was added to the collection`); + this.props.ui.showToast( + t("{{ userName }} was added to the collection", { userName: user.name }) + ); } catch (err) { - this.props.ui.showToast("Could not add user"); + this.props.ui.showToast(t("Could not add user")); } }; render() { - const { users, collection, auth } = this.props; + const { users, collection, auth, t } = this.props; const { user, team } = auth; if (!user || !team) return null; return ( <Flex column> <HelpText> - Need to add someone who’s not yet on the team yet?{" "} + {t("Need to add someone who’s not yet on the team yet?")}{" "} <a role="button" onClick={this.handleInviteModalOpen}> - Invite people to {team.name} + {t("Invite people to {{ teamName }}", { teamName: team.name })} </a> . </HelpText> <Input type="search" - placeholder="Search by name…" + placeholder={t("Search by name…")} value={this.query} onChange={this.handleFilter} - label="Search people" + label={t("Search people")} autoFocus labelHidden flex @@ -91,9 +96,9 @@ class AddPeopleToCollection extends React.Component<Props> { <PaginatedList empty={ this.query ? ( - <Empty>No people matching your search</Empty> + <Empty>{t("No people matching your search")}</Empty> ) : ( - <Empty>No people left to add</Empty> + <Empty>{t("No people left to add")}</Empty> ) } items={users.notInCollection(collection.id, this.query)} @@ -108,7 +113,7 @@ class AddPeopleToCollection extends React.Component<Props> { )} /> <Modal - title="Invite people" + title={t("Invite people")} onRequestClose={this.handleInviteModalClose} isOpen={this.inviteModalOpen} > diff --git a/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js b/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js index 404b6c08ac..7b05445197 100644 --- a/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js +++ b/app/scenes/CollectionMembers/components/CollectionGroupMemberListItem.js @@ -1,4 +1,5 @@ // @flow +import i18n from "i18next"; import * as React from "react"; import styled from "styled-components"; import CollectionGroupMembership from "models/CollectionGroupMembership"; @@ -7,9 +8,11 @@ import { DropdownMenu, DropdownMenuItem } from "components/DropdownMenu"; import GroupListItem from "components/GroupListItem"; import InputSelect from "components/InputSelect"; +const t = (k) => i18n.t(k); + const PERMISSIONS = [ - { label: "Read only", value: "read" }, - { label: "Read & Edit", value: "read_write" }, + { label: t("Read only"), value: "read" }, + { label: t("Read & Edit"), value: "read_write" }, ]; type Props = { group: Group, @@ -32,7 +35,7 @@ const MemberListItem = ({ renderActions={({ openMembersModal }) => ( <> <Select - label="Permissions" + label={t("Permissions")} options={PERMISSIONS} value={ collectionGroupMembership @@ -45,10 +48,12 @@ const MemberListItem = ({ <ButtonWrap> <DropdownMenu> <DropdownMenuItem onClick={openMembersModal}> - Members… + {t("Members…")} </DropdownMenuItem> <hr /> - <DropdownMenuItem onClick={onRemove}>Remove</DropdownMenuItem> + <DropdownMenuItem onClick={onRemove}> + {t("Remove")} + </DropdownMenuItem> </DropdownMenu> </ButtonWrap> </> diff --git a/app/scenes/CollectionMembers/components/MemberListItem.js b/app/scenes/CollectionMembers/components/MemberListItem.js index dd9cd4f547..edd8c2179b 100644 --- a/app/scenes/CollectionMembers/components/MemberListItem.js +++ b/app/scenes/CollectionMembers/components/MemberListItem.js @@ -1,4 +1,5 @@ // @flow +import i18n from "i18next"; import * as React from "react"; import styled from "styled-components"; import Membership from "models/Membership"; @@ -12,9 +13,11 @@ import InputSelect from "components/InputSelect"; import ListItem from "components/List/Item"; import Time from "components/Time"; +const t = (k) => i18n.t(k); + const PERMISSIONS = [ - { label: "Read only", value: "read" }, - { label: "Read & Edit", value: "read_write" }, + { label: t("Read only"), value: "read" }, + { label: t("Read & Edit"), value: "read_write" }, ]; type Props = { user: User, @@ -40,13 +43,15 @@ const MemberListItem = ({ <> {user.lastActiveAt ? ( <> - Active <Time dateTime={user.lastActiveAt} /> ago + {t("Active {{ lastActiveAt }} ago", { + lastActiveAt: <Time dateTime={user.lastActiveAt} />, + })} </> ) : ( - "Never signed in" + t("Never signed in") )} - {!user.lastActiveAt && <Badge>Invited</Badge>} - {user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>} + {!user.lastActiveAt && <Badge>{t("Invited")}</Badge>} + {user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>} </> } image={<Avatar src={user.avatarUrl} size={40} />} @@ -54,7 +59,7 @@ const MemberListItem = ({ <Flex align="center"> {canEdit && onUpdate && ( <Select - label="Permissions" + label={t("Permissions")} options={PERMISSIONS} value={membership ? membership.permission : undefined} onChange={(ev) => onUpdate(ev.target.value)} @@ -64,12 +69,14 @@ const MemberListItem = ({    {canEdit && onRemove && ( <DropdownMenu> - <DropdownMenuItem onClick={onRemove}>Remove</DropdownMenuItem> + <DropdownMenuItem onClick={onRemove}> + {t("Remove")} + </DropdownMenuItem> </DropdownMenu> )} {canEdit && onAdd && ( <Button onClick={onAdd} neutral> - Add + {t("Add")} </Button> )} </Flex> diff --git a/app/scenes/CollectionMembers/components/UserListItem.js b/app/scenes/CollectionMembers/components/UserListItem.js index 94d0d8730f..03f8445057 100644 --- a/app/scenes/CollectionMembers/components/UserListItem.js +++ b/app/scenes/CollectionMembers/components/UserListItem.js @@ -1,4 +1,5 @@ // @flow +import i18n from "i18next"; import { PlusIcon } from "outline-icons"; import * as React from "react"; import User from "models/User"; @@ -8,6 +9,8 @@ import Button from "components/Button"; import ListItem from "components/List/Item"; import Time from "components/Time"; +const t = (k) => i18n.t(k); + type Props = { user: User, canEdit: boolean, @@ -23,19 +26,21 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => { <> {user.lastActiveAt ? ( <> - Active <Time dateTime={user.lastActiveAt} /> ago + {t("Active {{ lastActiveAt }} ago", { + lastActiveAt: <Time dateTime={user.lastActiveAt} />, + })} </> ) : ( - "Never signed in" + t("Never signed in") )} - {!user.lastActiveAt && <Badge>Invited</Badge>} - {user.isAdmin && <Badge primary={user.isAdmin}>Admin</Badge>} + {!user.lastActiveAt && <Badge>{t("Invited")}</Badge>} + {user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>} </> } actions={ canEdit ? ( <Button type="button" onClick={onAdd} icon={<PlusIcon />} neutral> - Add + {t("Add")} </Button> ) : undefined } diff --git a/app/scenes/Settings/Profile.js b/app/scenes/Settings/Profile.js index d33e15cb46..883cfea441 100644 --- a/app/scenes/Settings/Profile.js +++ b/app/scenes/Settings/Profile.js @@ -125,6 +125,7 @@ class Profile extends React.Component<Props> { options={[ { label: "English (US)", value: "en_US" }, { label: "Deutsch (Deutschland)", value: "de_DE" }, + { label: "Português (Portugal)", value: "pt_PT" }, ]} value={this.language} onChange={this.handleLanguageChange} diff --git a/shared/translations/de_DE.json b/shared/translations/de_DE.json index 8a3fe16fde..480d15d805 100644 --- a/shared/translations/de_DE.json +++ b/shared/translations/de_DE.json @@ -1,19 +1,143 @@ { - "Home": "Startseite", - "Recently updated": "Zuletzt geändert", - "Recently viewed": "Zuletzt gesehen", - "Created by me": "Von mir erstellt", - "Profile saved": "Profil gespeichert", - "Profile picture updated": "Profilfoto wurde aktualisiert", - "Unable to upload new profile picture": "Neuer Avatar kann nicht hochgeladen werden", - "Profile": "Profil", - "Photo": "Foto", - "Upload": "Hochladen", - "Full name": "Vorname", - "Language": "Sprache", - "Saving…": "Am speichern…", - "Save": "Speichern", - "Delete Account": "Konto löschen", - "You may delete your account at any time, note that this is unrecoverable": "Sie können Ihr Konto jederzeit löschen. Beachten Sie, dass dies nicht wiederherstellbar ist", - "Delete account": "Konto löschen" - } \ No newline at end of file + "<strong>{{ userName }} {{ you }} <br /> {{ action }}</strong>": "<strong>{{ userName }} {{ you }} <br /> {{ action }}</strong>", + "Trash": "Papierkorb", + "Archive": "Archiv", + "Drafts": "Entwürfe", + "Templates": "Vorlagen", + "New": "Neu", + "Only visible to you": "Nur für Sie sichtbar", + "Draft": "Entwurf", + "Template": "Vorlag", + "New doc": "Neues Dok", + "why you not here": "warum sind sie nicht hier", + "More options": "Mehr Optionen", + "Search...": "Suche...", + "New collection…": "Neue Kollektion…", + "Collections": "Kollektionen", + "Untitled": "Ohne Titel", + "Home": "Startseite", + "Search": "Suche", + "Starred": "Favoriten", + "Invite people…": "Personen einladen…", + "Invite people": "Personen einalden", + "Create a collection": "Kollektion erstellen", + "Keyboard shortcuts": "Tastarturkürzel", + "Settings": "Einstellungen", + "API documentation": "API Dokumentation", + "Changelog": "Changelog", + "Send us feedback": "Schicken Sie uns Feedback", + "Report a bug": "Ein technischen Fehler melden", + "Appearance": "Darstellung", + "System": "System", + "Light": "Hell", + "Dark": "Dunkel", + "Log out": "Ausloggen", + "Collection permissions": "Kollektionsberechtigungen", + "New document": "Neues Dokument", + "Import document": "Dokument importieren", + "Edit…": "Bearbeiten…", + "Permissions…": "Berechtigungen…", + "Export…": "Export…", + "Delete…": "Löschen…", + "Edit collection": "Kollektion bearbeiten", + "Delete collection": "Kollektion löschen", + "Export collection": "Kollektion exportieren", + "Document duplicated": "Kollektion wurde dupliziert", + "Document archived": "Kollektion wurde archiviert", + "Document restored": "Kollektion wurde wiederhergestellt", + "Document unpublished": "Kollektion wurde unveröffentlicht", + "Restore": "Wiederherstellen", + "Restore…": "Wiederherstellen…", + "Choose a collection": "Kollektion auswählen", + "Unpin": "Unpin", + "Pin to collection": "Zur Kollektion anpinnen", + "Unstar": "Stern entfernen", + "Star": "Stern", + "Create a public share link": "Einen öffentlichen Freigabelink erstellen", + "Share link…": "Link teilen…", + "Enable embeds": "Embeds aktivieren", + "Disable embeds": "Embeds deaktivieren", + "Create a nested document inside the current document": "Erstellen Sie ein verschachteltes Dokument im aktuellen Dokument", + "New nested document": "Neues verschachteltes Dokument", + "Create template…": "Vorlage erstellen…", + "Unpublish": "Unveröffentlichen", + "Edit": "Bearbeiten", + "Duplicate": "Duplizieren", + "Move…": "Verschieben…", + "History": "Verlauf", + "Download": "Download", + "Print": "Drucken", + "Delete {{ documentName }}": "{{ documentName }} löschen", + "Create template": "Vorlage erstellen", + "Share document": "Dokument teilen", + "Edit group": "Gruppe bearbeiten", + "Delete group": "Gruppe löschen", + "Members…": "Mitglieder…", + "New document in": "Neues Dokument in der ", + "collection": "kollektion", + "New template…": "Neue Vorlage…", + "Link copied": "Link wurde kopiert", + "Restore version": "Version wiederherstellen", + "Copy link": "Link kopieren", + "Share link revoked": "Freigabelink wurde gelöscht", + "Share link copied": "Freigabelink wurde kopiert", + "Go to document": "Zum Dokument gehen", + "Revoke link": "Link löschen", + "By {{ author }}": "Von {{ author }}", + "Are you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Möchten Sie {{userName}} zum Administrator machen? Administratoren können Team- und Rechnungsinformationen ändern.", + "Are you want to make {{ userName }} a member?": "Möchten Sie {{userName}} zu einem Mitglied machen?", + "Are you want to suspend this account? Suspended users will be prevented from logging in.": "Möchten Sie dieses Konto sperren? Gesperrte Benutzer können sich nicht anmelden.", + "Make {{ userName }} a member…": "Machen Sie {{userName}} zu einem Mitglied…", + "Make {{ userName }} an admin…": "Machen Sie {{userName}} zu einem Administrator…", + "Revoke invite…": "Einladung löschen…", + "Activate account": "Konto aktivieren", + "Suspend account…": "Konto suspendieren…", + "Search in collection…": "Innerhalb der Kollektion suchen…", + "\n <strong>{{ collectionName }}</strong> doesn’t contain any\n documents yet.\n <br />\n Get started by creating a new one!\n ": "\n <strong>{{ collectionName }}</strong> enthält noch keine\n Dokumente.\n <br />\n Erstellen Sie zunächst ein neues!\n ", + "Create a document": "Dokument erstellen", + "Manage members…": "Mitglieder verwalten…", + "Pinned": "Gepinned", + "Recently updated": "Vor Kurzem aktualisiert", + "Recently published": "Vor Kurzem veröffentlicht", + "Least recently updated": "Vor kurzem aktualisiert", + "A–Z": "A–Z", + "{{ groupName }} was added to the collection": "{{ groupName }} wurde zur Kollektion hinzugefügt", + "Could not add user": "Benutzer kann nicht hinzugefügt werden", + "Can’t find the group you’re looking for?": "Können Sie die Gruppe nicht finden?", + "Create a group": "Gruppe erstellen", + "Search by group name…": "Nach Name der Gruppe suchen…", + "Search groups": "Gruppen suchen", + "No groups matching your search": "Keine Gruppen, die Ihrer Suche entsprechen", + "No groups left to add": "Keine Gruppen mehr zum Hinzufügen", + "Add": "Hinzufügen", + "{{ userName }} was added to the collection": "{{ userName }} wurde zur Kollektion hinzugefügt", + "Need to add someone who’s not yet on the team yet?": "Müssen Sie jemanden hinzufügen, der noch nicht im Team ist?", + "Invite people to {{ teamName }}": "Personen zu {{ teamName }} einladen", + "Search by name…": "Nach Name suchen…", + "Search people": "Personen suchen", + "No people matching your search": "Keine Personen, die Ihrer Suche entsprechen", + "No people left to add": "Keine Personen mehr zum Hinzufügen", + "Read only": "Schreibgeschützt", + "Read & Edit": "Lesen & Schreiben", + "Permissions": "Berechtigungen", + "Remove": "Entfernen", + "Active {{ lastActiveAt }} ago": "Vor {{ lastActiveAt }} aktiv", + "Never signed in": "Nie angemeldet", + "Invited": "Eingeladet", + "Admin": "Admin", + "Recently viewed": "Vor Kurzem angesehen", + "Created by me": "Von mir erstellt", + "Profile saved": "Profil gespeichert", + "Profile picture updated": "Profilbild wurde aktualisiert", + "Unable to upload new profile picture": "Neues Profilbild kann nicht hochgeladen werden", + "Profile": "Profil", + "Photo": "Foto", + "Upload": "Upload", + "Full name": "Vorname", + "Language": "Sprache", + "Saving…": "Am speichern…", + "Save": "Speichern", + "Delete Account": "Konto löschen", + "You may delete your account at any time, note that this is unrecoverable": "Sie können Ihr Konto jederzeit löschen. Beachten Sie, dass dies nicht wiederherstellbar ist", + "Delete account": "Konto löschen" +} diff --git a/shared/translations/default.json b/shared/translations/default.json index c5f0343cdd..85618b2be6 100644 --- a/shared/translations/default.json +++ b/shared/translations/default.json @@ -1,6 +1,130 @@ { + "<strong>{{ userName }} {{ you }} <br /> {{ action }}</strong>": "<strong>{{ userName }} {{ you }} <br /> {{ action }}</strong>", + "Trash": "Trash", + "Archive": "Archive", + "Drafts": "Drafts", + "Templates": "Templates", + "New": "New", + "Only visible to you": "Only visible to you", + "Draft": "Draft", + "Template": "Template", + "New doc": "New doc", + "why you not here": "why you not here", + "More options": "More options", + "Search...": "Search...", + "New collection…": "New collection…", + "Collections": "Collections", + "Untitled": "Untitled", "Home": "Home", + "Search": "Search", + "Starred": "Starred", + "Invite people…": "Invite people…", + "Invite people": "Invite people", + "Create a collection": "Create a collection", + "Keyboard shortcuts": "Keyboard shortcuts", + "Settings": "Settings", + "API documentation": "API documentation", + "Changelog": "Changelog", + "Send us feedback": "Send us feedback", + "Report a bug": "Report a bug", + "Appearance": "Appearance", + "System": "System", + "Light": "Light", + "Dark": "Dark", + "Log out": "Log out", + "Collection permissions": "Collection permissions", + "New document": "New document", + "Import document": "Import document", + "Edit…": "Edit…", + "Permissions…": "Permissions…", + "Export…": "Export…", + "Delete…": "Delete…", + "Edit collection": "Edit collection", + "Delete collection": "Delete collection", + "Export collection": "Export collection", + "Document duplicated": "Document duplicated", + "Document archived": "Document archived", + "Document restored": "Document restored", + "Document unpublished": "Document unpublished", + "Restore": "Restore", + "Restore…": "Restore…", + "Choose a collection": "Choose a collection", + "Unpin": "Unpin", + "Pin to collection": "Pin to collection", + "Unstar": "Unstar", + "Star": "Star", + "Create a public share link": "Create a public share link", + "Share link…": "Share link…", + "Enable embeds": "Enable embeds", + "Disable embeds": "Disable embeds", + "Create a nested document inside the current document": "Create a nested document inside the current document", + "New nested document": "New nested document", + "Create template…": "Create template…", + "Unpublish": "Unpublish", + "Edit": "Edit", + "Duplicate": "Duplicate", + "Move…": "Move…", + "History": "History", + "Download": "Download", + "Print": "Print", + "Delete {{ documentName }}": "Delete {{ documentName }}", + "Create template": "Create template", + "Share document": "Share document", + "Edit group": "Edit group", + "Delete group": "Delete group", + "Members…": "Members…", + "New document in": "New document in", + "collection": "collection", + "New template…": "New template…", + "Link copied": "Link copied", + "Restore version": "Restore version", + "Copy link": "Copy link", + "Share link revoked": "Share link revoked", + "Share link copied": "Share link copied", + "Go to document": "Go to document", + "Revoke link": "Revoke link", + "By {{ author }}": "By {{ author }}", + "Are you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you want to make {{ userName }} an admin? Admins can modify team and billing information.", + "Are you want to make {{ userName }} a member?": "Are you want to make {{ userName }} a member?", + "Are you want to suspend this account? Suspended users will be prevented from logging in.": "Are you want to suspend this account? Suspended users will be prevented from logging in.", + "Make {{ userName }} a member…": "Make {{ userName }} a member…", + "Make {{ userName }} an admin…": "Make {{ userName }} an admin…", + "Revoke invite…": "Revoke invite…", + "Activate account": "Activate account", + "Suspend account…": "Suspend account…", + "Search in collection…": "Search in collection…", + "\n <strong>{{ collectionName }}</strong> doesn’t contain any\n documents yet.\n <br />\n Get started by creating a new one!\n ": "\n <strong>{{ collectionName }}</strong> doesn’t contain any\n documents yet.\n <br />\n Get started by creating a new one!\n ", + "Create a document": "Create a document", + "Manage members…": "Manage members…", + "Pinned": "Pinned", "Recently updated": "Recently updated", + "Recently published": "Recently published", + "Least recently updated": "Least recently updated", + "A–Z": "A–Z", + "{{ groupName }} was added to the collection": "{{ groupName }} was added to the collection", + "Could not add user": "Could not add user", + "Can’t find the group you’re looking for?": "Can’t find the group you’re looking for?", + "Create a group": "Create a group", + "Search by group name…": "Search by group name…", + "Search groups": "Search groups", + "No groups matching your search": "No groups matching your search", + "No groups left to add": "No groups left to add", + "Add": "Add", + "{{ userName }} was added to the collection": "{{ userName }} was added to the collection", + "Need to add someone who’s not yet on the team yet?": "Need to add someone who’s not yet on the team yet?", + "Invite people to {{ teamName }}": "Invite people to {{ teamName }}", + "Search by name…": "Search by name…", + "Search people": "Search people", + "No people matching your search": "No people matching your search", + "No people left to add": "No people left to add", + "Read only": "Read only", + "Read & Edit": "Read & Edit", + "Permissions": "Permissions", + "Remove": "Remove", + "Active {{ lastActiveAt }} ago": "Active {{ lastActiveAt }} ago", + "Never signed in": "Never signed in", + "Invited": "Invited", + "Admin": "Admin", "Recently viewed": "Recently viewed", "Created by me": "Created by me", "Profile saved": "Profile saved", diff --git a/shared/translations/i18n.js b/shared/translations/i18n.js index 4c49924418..a9de0c5e33 100644 --- a/shared/translations/i18n.js +++ b/shared/translations/i18n.js @@ -2,8 +2,8 @@ import i18n from "i18next"; import { initReactI18next } from "react-i18next"; -import en_US from "./default.json"; import de_DE from "./de_DE.json"; +import en_US from "./default.json"; import pt_PT from "./pt_PT.json"; const outlineTranslation = { @@ -15,7 +15,10 @@ const outlineTranslation = { react: { useSuspense: false, }, - lng: "DEFAULT_LANGUAGE" in process.env ? process.env.DEFAULT_LANGUAGE : "en_US", + lng: + "DEFAULT_LANGUAGE" in process.env + ? process.env.DEFAULT_LANGUAGE + : "en_US", debug: process.env.NODE_ENV !== "production", resources: { en_US: { @@ -25,11 +28,11 @@ const outlineTranslation = { translation: de_DE, }, pt_PT: { - translation: pt_PT - } + translation: pt_PT, + }, }, }); - } -} + }, +}; export { outlineTranslation, i18n, en_US, de_DE, pt_PT }; diff --git a/shared/translations/i18n.test.js b/shared/translations/i18n.test.js index 079e586214..47a33e1a32 100644 --- a/shared/translations/i18n.test.js +++ b/shared/translations/i18n.test.js @@ -1,97 +1,97 @@ /* eslint-disable flowtype/require-valid-file-annotation */ -import { outlineTranslation, i18n, en_US, de_DE, pt_PT } from "./i18n"; import { isEqual } from "lodash"; +import { outlineTranslation, i18n, en_US, de_DE, pt_PT } from "./i18n"; describe("i18n configuration", () => { - beforeEach(() => { - outlineTranslation.init(); - }); - it("all languages should have same keys", () => { - const en_US_Keys = Object.keys(en_US); - const de_DE_Keys = Object.keys(de_DE); - const pt_PT_Keys = Object.keys(pt_PT); + beforeEach(() => { + outlineTranslation.init(); + }); + it("all languages should have same keys", () => { + const en_US_Keys = Object.keys(en_US); + const de_DE_Keys = Object.keys(de_DE); + const pt_PT_Keys = Object.keys(pt_PT); - expect(isEqual(en_US_Keys, de_DE_Keys)).toBe(true); - expect(isEqual(en_US_Keys, pt_PT_Keys)).toBe(true); - }); + expect(isEqual(en_US_Keys, de_DE_Keys)).toBe(true); + expect(isEqual(en_US_Keys, pt_PT_Keys)).toBe(true); + }); }); describe("i18n process.env is unset", () => { - beforeEach(() => { - delete process.env.DEFAULT_LANGUAGE; - outlineTranslation.init(); - }); + beforeEach(() => { + delete process.env.DEFAULT_LANGUAGE; + outlineTranslation.init(); + }); - it("translation of key should match", () => expect(i18n.t("Saving…")).toBe("Saving…")); + it("translation of key should match", () => + expect(i18n.t("Saving…")).toBe("Saving…")); - it ("translation if changed to de_DE", () => { - i18n.changeLanguage("de_DE"); - expect(i18n.t("Saving…")).toBe("Am speichern…"); - }); - - it ("translation if changed to pt_PT", () => { - i18n.changeLanguage("pt_PT"); - expect(i18n.t("Saving…")).toBe("A guardar…"); - }); + it("translation if changed to de_DE", () => { + i18n.changeLanguage("de_DE"); + expect(i18n.t("Saving…")).toBe("Am speichern…"); + }); + it("translation if changed to pt_PT", () => { + i18n.changeLanguage("pt_PT"); + expect(i18n.t("Saving…")).toBe("A guardar…"); + }); }); describe("i18n process.env is en_US", () => { - beforeEach(() => { - process.env.DEFAULT_LANGUAGE = "en_US"; - outlineTranslation.init(); - }); + beforeEach(() => { + process.env.DEFAULT_LANGUAGE = "en_US"; + outlineTranslation.init(); + }); - it("translation of key should match", () => expect(i18n.t("Saving…")).toBe("Saving…")); + it("translation of key should match", () => + expect(i18n.t("Saving…")).toBe("Saving…")); - it ("translation if changed to de_DE", () => { - i18n.changeLanguage("de_DE"); - expect(i18n.t("Saving…")).toBe("Am speichern…"); - }); - - it ("translation if changed to pt_PT", () => { - i18n.changeLanguage("pt_PT"); - expect(i18n.t("Saving…")).toBe("A guardar…"); - }); + it("translation if changed to de_DE", () => { + i18n.changeLanguage("de_DE"); + expect(i18n.t("Saving…")).toBe("Am speichern…"); + }); + it("translation if changed to pt_PT", () => { + i18n.changeLanguage("pt_PT"); + expect(i18n.t("Saving…")).toBe("A guardar…"); + }); }); describe("i18n process.env is de_DE", () => { - beforeEach(() => { - process.env.DEFAULT_LANGUAGE = "de_DE"; - outlineTranslation.init(); - }); + beforeEach(() => { + process.env.DEFAULT_LANGUAGE = "de_DE"; + outlineTranslation.init(); + }); - it("translation of key should match", () => expect(i18n.t("Saving…")).toBe("Am speichern…")); + it("translation of key should match", () => + expect(i18n.t("Saving…")).toBe("Am speichern…")); - it ("translation if changed to en_US", () => { - i18n.changeLanguage("en_US"); - expect(i18n.t("Saving…")).toBe("Saving…"); - }); - - it ("translation if changed to pt_PT", () => { - i18n.changeLanguage("pt_PT"); - expect(i18n.t("Saving…")).toBe("A guardar…"); - }); + it("translation if changed to en_US", () => { + i18n.changeLanguage("en_US"); + expect(i18n.t("Saving…")).toBe("Saving…"); + }); + it("translation if changed to pt_PT", () => { + i18n.changeLanguage("pt_PT"); + expect(i18n.t("Saving…")).toBe("A guardar…"); + }); }); describe("i18n process.env is pt_PT", () => { - beforeEach(() => { - process.env.DEFAULT_LANGUAGE = "pt_PT"; - outlineTranslation.init(); - }); + beforeEach(() => { + process.env.DEFAULT_LANGUAGE = "pt_PT"; + outlineTranslation.init(); + }); - it("translation of key should match", () => expect(i18n.t("Saving…")).toBe("A guardar…")); + it("translation of key should match", () => + expect(i18n.t("Saving…")).toBe("A guardar…")); - it ("translation if changed to en_US", () => { - i18n.changeLanguage("en_US"); - expect(i18n.t("Saving…")).toBe("Saving…"); - }); + it("translation if changed to en_US", () => { + i18n.changeLanguage("en_US"); + expect(i18n.t("Saving…")).toBe("Saving…"); + }); - it ("translation if changed to de_DE", () => { - i18n.changeLanguage("de_DE"); - expect(i18n.t("Saving…")).toBe("Am speichern…"); - }); - -}); \ No newline at end of file + it("translation if changed to de_DE", () => { + i18n.changeLanguage("de_DE"); + expect(i18n.t("Saving…")).toBe("Am speichern…"); + }); +}); diff --git a/shared/translations/pt_PT.json b/shared/translations/pt_PT.json index d02f47d337..6a70b51214 100644 --- a/shared/translations/pt_PT.json +++ b/shared/translations/pt_PT.json @@ -1,20 +1,143 @@ { - "Home": "Home", - "Recently updated": "Recently updated", - "Recently viewed": "Recently viewed", - "Created by me": "Created by me", - "Profile saved": "Profile saved", - "Profile picture updated": "Profile picture updated", - "Unable to upload new profile picture": "Unable to upload new avatar", - "Profile": "Profile", - "Photo": "Photo", - "Upload": "Upload", - "Full name": "Full name", - "Language": "Language", - "Saving…": "A guardar…", - "Save": "Save", - "Delete Account": "Delete Account", - "You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable", - "Delete account": "Delete account" - } - \ No newline at end of file + "<strong>{{ userName }} {{ you }} <br /> {{ action }}</strong>": "<strong>{{ userName }} {{ you }} <br /> {{ action }}</strong>", + "Trash": "Reciclagem", + "Archive": "Arquivo", + "Drafts": "Rascunhos", + "Templates": "Templates", + "New": "Novo", + "Only visible to you": "Apenas visível para ti", + "Draft": "Rascunho", + "Template": "Template", + "New doc": "Novo doc", + "why you not here": "porquê é que não estás aquí", + "More options": "Mais opções", + "Search...": "Pesquisa...", + "New collection…": "Nova coleção…", + "Collections": "Coleções", + "Untitled": "Sem título", + "Home": "Página inicial", + "Search": "Pesquisa", + "Starred": "Favoritos", + "Invite people…": "Convidar pessoas…", + "Invite people": "Convidar pessoas", + "Create a collection": "Criar coleção", + "Keyboard shortcuts": "Atalhos do teclado", + "Settings": "Configurações", + "API documentation": "Documentação da API", + "Changelog": "Changelog", + "Send us feedback": "Envia-nos Feedback", + "Report a bug": "Reportar um erro técnico", + "Appearance": "Aparência", + "System": "Sistema", + "Light": "Claro", + "Dark": "Escuro", + "Log out": "Deslogar", + "Collection permissions": "Permissões da coleção", + "New document": "Novo documento", + "Import document": "Importar documento", + "Edit…": "Editar…", + "Permissions…": "Permissões…", + "Export…": "Exportar…", + "Delete…": "Apagar…", + "Edit collection": "Editar coleção", + "Delete collection": "Apagar coleção", + "Export collection": "Exportar coleção", + "Document duplicated": "Documento duplicado", + "Document archived": "Documento arquivado", + "Document restored": "Documento restaurado", + "Document unpublished": "Documento não publicado", + "Restore": "Restaurar", + "Restore…": "Restaurar…", + "Choose a collection": "Escolher coleção", + "Unpin": "Tirar pino", + "Pin to collection": "Pin coleção", + "Unstar": "Tirar estrela", + "Star": "Estrela", + "Create a public share link": "Criar um link de partilha público", + "Share link…": "Partilhar link…", + "Enable embeds": "Ativar embeds", + "Disable embeds": "Desativar embeds", + "Create a nested document inside the current document": "Criar um documento aninhado dentro do documento atual", + "New nested document": "Novo documento aninhado", + "Create template…": "Criar template…", + "Unpublish": "Despublicar", + "Edit": "Editar", + "Duplicate": "Duplicar", + "Move…": "Mover…", + "History": "Historia", + "Download": "Download", + "Print": "Imprimir", + "Delete {{ documentName }}": "Apagar {{ documentName }}", + "Create template": "Criar template", + "Share document": "Partilhar documento", + "Edit group": "Editar grupo", + "Delete group": "Apagar grupo", + "Members…": "Membros…", + "New document in": "Novo documento em", + "collection": "coleção", + "New template…": "Novo template…", + "Link copied": "Link copiado", + "Restore version": "Restaurar versão", + "Copy link": "Copiar link", + "Share link revoked": "Link de partilha foi revogado", + "Share link copied": "Link de partilha foi copiado", + "Go to document": "Ir para documento", + "Revoke link": "Revogar link", + "By {{ author }}": "De {{ author }}", + "Are you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Deseja tornar {{userName}} um administrador? Os administradores podem modificar as informações da equipe e do faturamento.", + "Are you want to make {{ userName }} a member?": "Deseja tornar {{userName}} um membro?", + "Are you want to suspend this account? Suspended users will be prevented from logging in.": "Deseja suspender esta conta? Os usuários suspensos serão impedidos de fazer login.", + "Make {{ userName }} a member…": "Tornar {{userName}} um membro…", + "Make {{ userName }} an admin…": "Tornar {{userName}} um administrador…", + "Revoke invite…": "Revogar convite…", + "Activate account": "Ativar conta", + "Suspend account…": "Suspender conta…", + "Search in collection…": "Procurar na coleção…", + "\n <strong>{{ collectionName }}</strong> doesn’t contain any\n documents yet.\n <br />\n Get started by creating a new one!\n ": "\n <strong>{{ collectionName }}</strong> não contém nenhum documento\n ainda.\n <br />\n Para começar crie um documento novo!\n ", + "Create a document": "Criar documento", + "Manage members…": "Gerenciar membros…", + "Pinned": "Marcado", + "Recently updated": "Atualizado recentemente", + "Recently published": "Publicado recentemente", + "Least recently updated": "Menos atualizado recentemente", + "A–Z": "A–Z", + "{{ groupName }} was added to the collection": "{{ groupName }} foi adicionado à coleção", + "Could not add user": "Não foi possível adicionar usuário", + "Can’t find the group you’re looking for?": "Não consegue encontrar o grupo que procura?", + "Create a group": "Criar grupo", + "Search by group name…": "Procurar grupo pelo nome…", + "Search groups": "Procurar grupos", + "No groups matching your search": "Nenhum grupo corresponde à sua pesquisa", + "No groups left to add": "Nenhum grupo restante para adicionar", + "Add": "Adicionar", + "{{ userName }} was added to the collection": "{{userName}} foi adicionado à coleção", + "Need to add someone who’s not yet on the team yet?": "Precisa adicionar alguém que ainda não faz parte da equipe?", + "Invite people to {{ teamName }}": "Convide pessoas para {{teamName}}", + "Search by name…": "Procurar pelo nome…", + "Search people": "Procurar pessoas", + "No people matching your search": "Nenhuma pessoa corresponde à sua pesquisa", + "No people left to add": "Não sobrou ninguém para adicionar", + "Read only": "Somente leitura", + "Read & Edit": "Leitura & Escrita", + "Permissions": "Permissões", + "Remove": "Remover", + "Active {{ lastActiveAt }} ago": "Ativo há {{ lastActiveAt }}", + "Never signed in": "Nunca conectado", + "Invited": "Convidado", + "Admin": "Admin", + "Recently viewed": "Visto recentemente", + "Created by me": "Criado por mim", + "Profile saved": "Profil foi guardado", + "Profile picture updated": "Foto de perfil foi atualizada", + "Unable to upload new profile picture": "Não é possível carregar uma nova foto de perfil", + "Profile": "Profil", + "Photo": "Foto", + "Upload": "Upload", + "Full name": "Nome completo", + "Language": "Língua", + "Saving…": "A guardar…", + "Save": "Guardar", + "Delete Account": "Apagar conta", + "You may delete your account at any time, note that this is unrecoverable": "Pode apagar a sua conta a qualquer momento, atenção que isso é irrecuperável", + "Delete account": "Apagar conta" +}