From 3f030540b309a7b0825ded15c7b30b3f3119ce40 Mon Sep 17 00:00:00 2001 From: Saumya Pandey Date: Thu, 15 Jul 2021 14:50:36 +0530 Subject: [PATCH] fix: Add translation hooks on settings screen (#2298) Co-authored-by: Tom Moor --- app/scenes/Settings/Details.js | 238 ++++++++++----------- app/scenes/Settings/Notifications.js | 213 +++++++++--------- app/scenes/Settings/Security.js | 157 +++++++------- app/scenes/Settings/Zapier.js | 16 +- shared/i18n/locales/en_US/translation.json | 32 ++- 5 files changed, 340 insertions(+), 316 deletions(-) diff --git a/app/scenes/Settings/Details.js b/app/scenes/Settings/Details.js index 6d21df949..8b5de8a4b 100644 --- a/app/scenes/Settings/Details.js +++ b/app/scenes/Settings/Details.js @@ -1,11 +1,10 @@ // @flow -import { observable } from "mobx"; -import { observer, inject } from "mobx-react"; +import { observer } from "mobx-react"; import { TeamIcon } from "outline-icons"; import * as React from "react"; +import { useRef, useState } from "react"; +import { useTranslation, Trans } from "react-i18next"; import styled from "styled-components"; -import AuthStore from "stores/AuthStore"; -import UiStore from "stores/UiStore"; import Button from "components/Button"; import Flex from "components/Flex"; import Heading from "components/Heading"; @@ -14,137 +13,128 @@ import Input, { LabelText } from "components/Input"; import Scene from "components/Scene"; import ImageUpload from "./components/ImageUpload"; import env from "env"; +import useCurrentTeam from "hooks/useCurrentTeam"; +import useStores from "hooks/useStores"; -type Props = { - auth: AuthStore, - ui: UiStore, -}; +function Details() { + const { auth, ui } = useStores(); + const team = useCurrentTeam(); + const { t } = useTranslation(); + const form = useRef(); + const [name, setName] = useState(team.name); + const [subdomain, setSubdomain] = useState(team.subdomain); + const [avatarUrl, setAvatarUrl] = useState(); -@observer -class Details extends React.Component { - timeout: TimeoutID; - form: ?HTMLFormElement; + const handleSubmit = React.useCallback( + async (event: ?SyntheticEvent<>) => { + if (event) { + event.preventDefault(); + } - @observable name: string; - @observable subdomain: ?string; - @observable avatarUrl: ?string; + try { + await auth.updateTeam({ + name, + avatarUrl, + subdomain, + }); + ui.showToast(t("Settings saved"), { type: "success" }); + } catch (err) { + ui.showToast(err.message, { type: "error" }); + } + }, + [auth, ui, name, avatarUrl, subdomain, t] + ); - componentDidMount() { - const { team } = this.props.auth; - if (team) { - this.name = team.name; - this.subdomain = team.subdomain; - } - } + const handleNameChange = React.useCallback((ev: SyntheticInputEvent<*>) => { + setName(ev.target.value); + }, []); - componentWillUnmount() { - clearTimeout(this.timeout); - } + const handleSubdomainChange = React.useCallback( + (ev: SyntheticInputEvent<*>) => { + setSubdomain(ev.target.value.toLowerCase()); + }, + [] + ); - handleSubmit = async (event: ?SyntheticEvent<>) => { - if (event) { - event.preventDefault(); - } + const handleAvatarUpload = React.useCallback( + (avatarUrl: string) => { + setAvatarUrl(avatarUrl); + handleSubmit(); + }, + [handleSubmit] + ); - try { - await this.props.auth.updateTeam({ - name: this.name, - avatarUrl: this.avatarUrl, - subdomain: this.subdomain, - }); - this.props.ui.showToast("Settings saved", { type: "success" }); - } catch (err) { - this.props.ui.showToast(err.message, { type: "error" }); - } - }; + const handleAvatarError = React.useCallback( + (error: ?string) => { + ui.showToast(error || t("Unable to upload new logo")); + }, + [ui, t] + ); - handleNameChange = (ev: SyntheticInputEvent<*>) => { - this.name = ev.target.value; - }; + const isValid = form.current && form.current.checkValidity(); - handleSubdomainChange = (ev: SyntheticInputEvent<*>) => { - this.subdomain = ev.target.value.toLowerCase(); - }; - - handleAvatarUpload = (avatarUrl: string) => { - this.avatarUrl = avatarUrl; - this.handleSubmit(); - }; - - handleAvatarError = (error: ?string) => { - this.props.ui.showToast(error || "Unable to upload new logo"); - }; - - get isValid() { - return this.form && this.form.checkValidity(); - } - - render() { - const { team, isSaving } = this.props.auth; - if (!team) return null; - const avatarUrl = this.avatarUrl || team.avatarUrl; - - return ( - }> - Details - + return ( + }> + {t("Details")} + + These details affect the way that your Outline appears to everyone on the team. - + + - - Logo - - - - - Upload - - - - -
(this.form = ref)}> - - {env.SUBDOMAINS_ENABLED && ( - <> - - {this.subdomain && ( - - Your knowledge base will be accessible at{" "} - {this.subdomain}.getoutline.com - - )} - - )} - -
-
- ); - } + + {t("Logo")} + + + + + Upload + + + + +
+ + {env.SUBDOMAINS_ENABLED && ( + <> + + {subdomain && ( + + Your knowledge base will be accessible at{" "} + {subdomain}.getoutline.com + + )} + + )} + +
+ + ); } const ProfilePicture = styled(Flex)` @@ -186,4 +176,4 @@ const Avatar = styled.img` ${avatarStyles}; `; -export default inject("auth", "ui")(Details); +export default observer(Details); diff --git a/app/scenes/Settings/Notifications.js b/app/scenes/Settings/Notifications.js index d9d490af6..52729f6fb 100644 --- a/app/scenes/Settings/Notifications.js +++ b/app/scenes/Settings/Notifications.js @@ -1,137 +1,140 @@ // @flow import { debounce } from "lodash"; -import { observer, inject } from "mobx-react"; +import { observer } from "mobx-react"; import { EmailIcon } from "outline-icons"; import * as React from "react"; +import { useTranslation, Trans } from "react-i18next"; import styled from "styled-components"; -import AuthStore from "stores/AuthStore"; -import NotificationSettingsStore from "stores/NotificationSettingsStore"; -import UiStore from "stores/UiStore"; import Heading from "components/Heading"; import HelpText from "components/HelpText"; import Input from "components/Input"; import Notice from "components/Notice"; import Scene from "components/Scene"; import Subheading from "components/Subheading"; - import NotificationListItem from "./components/NotificationListItem"; +import useCurrentUser from "hooks/useCurrentUser"; +import useStores from "hooks/useStores"; -type Props = { - ui: UiStore, - auth: AuthStore, - notificationSettings: NotificationSettingsStore, -}; +function Notifications() { + const { notificationSettings, ui } = useStores(); + const user = useCurrentUser(); + const { t } = useTranslation(); -const options = [ - { - event: "documents.publish", - title: "Document published", - description: "Receive a notification whenever a new document is published", - }, - { - event: "documents.update", - title: "Document updated", - description: "Receive a notification when a document you created is edited", - }, - { - event: "collections.create", - title: "Collection created", - description: "Receive a notification whenever a new collection is created", - }, - { - separator: true, - }, - { - event: "emails.onboarding", - title: "Getting started", - description: - "Tips on getting started with Outline`s features and functionality", - }, - { - event: "emails.features", - title: "New features", - description: "Receive an email when new features of note are added", - }, -]; + const options = [ + { + event: "documents.publish", + title: t("Document published"), + description: t( + "Receive a notification whenever a new document is published" + ), + }, + { + event: "documents.update", + title: t("Document updated"), + description: t( + "Receive a notification when a document you created is edited" + ), + }, + { + event: "collections.create", + title: t("Collection created"), + description: t( + "Receive a notification whenever a new collection is created" + ), + }, + { + separator: true, + }, + { + event: "emails.onboarding", + title: t("Getting started"), + description: t( + "Tips on getting started with Outline`s features and functionality" + ), + }, + { + event: "emails.features", + title: t("New features"), + description: t("Receive an email when new features of note are added"), + }, + ]; -@observer -class Notifications extends React.Component { - componentDidMount() { - this.props.notificationSettings.fetchPage(); - } + React.useEffect(() => { + notificationSettings.fetchPage(); + }, [notificationSettings]); - handleChange = async (ev: SyntheticInputEvent<>) => { - const { notificationSettings } = this.props; - const setting = notificationSettings.getByEvent(ev.target.name); - - if (ev.target.checked) { - await notificationSettings.save({ - event: ev.target.name, - }); - } else if (setting) { - await notificationSettings.delete(setting); - } - - this.showSuccessMessage(); - }; - - showSuccessMessage = debounce(() => { - this.props.ui.showToast("Notifications saved", { type: "success" }); + const showSuccessMessage = debounce(() => { + ui.showToast(t("Notifications saved"), { type: "success" }); }, 500); - render() { - const { notificationSettings, auth } = this.props; - const showSuccessNotice = window.location.search === "?success"; - const { user, team } = auth; - if (!team || !user) return null; + const handleChange = React.useCallback( + async (ev: SyntheticInputEvent<>) => { + const setting = notificationSettings.getByEvent(ev.target.name); - return ( - }> - {showSuccessNotice && ( - + if (ev.target.checked) { + await notificationSettings.save({ + event: ev.target.name, + }); + } else if (setting) { + await notificationSettings.delete(setting); + } + + showSuccessMessage(); + }, + [notificationSettings, showSuccessMessage] + ); + + const showSuccessNotice = window.location.search === "?success"; + + return ( + }> + {showSuccessNotice && ( + + Unsubscription successful. Your notification settings were updated - - )} - Notifications - + + + )} + {t("Notifications")} + + Manage when and where you receive email notifications from Outline. Your email address can be updated in your SSO provider. - + + + - + {t("Notifications")} - Notifications + {options.map((option, index) => { + if (option.separator) return ; - {options.map((option, index) => { - if (option.separator) return ; + const setting = notificationSettings.getByEvent(option.event); - const setting = notificationSettings.getByEvent(option.event); - - return ( - - ); - })} - - ); - } + return ( + + ); + })} + + ); } const Separator = styled.hr` padding-bottom: 12px; `; -export default inject("notificationSettings", "auth", "ui")(Notifications); +export default observer(Notifications); diff --git a/app/scenes/Settings/Security.js b/app/scenes/Settings/Security.js index 5f19bfcea..9b99454a1 100644 --- a/app/scenes/Settings/Security.js +++ b/app/scenes/Settings/Security.js @@ -1,97 +1,94 @@ // @flow import { debounce } from "lodash"; -import { observable } from "mobx"; -import { observer, inject } from "mobx-react"; +import { observer } from "mobx-react"; import { PadlockIcon } from "outline-icons"; import * as React from "react"; -import AuthStore from "stores/AuthStore"; -import UiStore from "stores/UiStore"; +import { useState } from "react"; +import { useTranslation, Trans } from "react-i18next"; import Checkbox from "components/Checkbox"; import Heading from "components/Heading"; import HelpText from "components/HelpText"; import Scene from "components/Scene"; +import useCurrentTeam from "hooks/useCurrentTeam"; +import useStores from "hooks/useStores"; -type Props = { - auth: AuthStore, - ui: UiStore, -}; +function Security() { + const { auth, ui } = useStores(); + const team = useCurrentTeam(); + const { t } = useTranslation(); + const [sharing, setSharing] = useState(team.documentEmbeds); + const [documentEmbeds, setDocumentEmbeds] = useState(team.guestSignin); + const [guestSignin, setGuestSignin] = useState(team.sharing); -@observer -class Security extends React.Component { - form: ?HTMLFormElement; - - @observable sharing: boolean; - @observable documentEmbeds: boolean; - @observable guestSignin: boolean; - - componentDidMount() { - const { auth } = this.props; - if (auth.team) { - this.documentEmbeds = auth.team.documentEmbeds; - this.guestSignin = auth.team.guestSignin; - this.sharing = auth.team.sharing; - } - } - - handleChange = async (ev: SyntheticInputEvent<*>) => { - switch (ev.target.name) { - case "sharing": - this.sharing = ev.target.checked; - break; - case "documentEmbeds": - this.documentEmbeds = ev.target.checked; - break; - case "guestSignin": - this.guestSignin = ev.target.checked; - break; - default: - } - - await this.props.auth.updateTeam({ - sharing: this.sharing, - documentEmbeds: this.documentEmbeds, - guestSignin: this.guestSignin, - }); - this.showSuccessMessage(); - }; - - showSuccessMessage = debounce(() => { - this.props.ui.showToast("Settings saved", { type: "success" }); + const showSuccessMessage = debounce(() => { + ui.showToast(t("Settings saved"), { type: "success" }); }, 500); - render() { - return ( - }> - Security - + const handleChange = React.useCallback( + async (ev: SyntheticInputEvent<*>) => { + switch (ev.target.name) { + case "sharing": + setSharing(ev.target.checked); + break; + case "documentEmbeds": + setDocumentEmbeds(ev.target.checked); + break; + case "guestSignin": + setGuestSignin(ev.target.checked); + break; + default: + } + + await auth.updateTeam({ + sharing, + documentEmbeds, + guestSignin, + }); + + showSuccessMessage(); + }, + [auth, sharing, documentEmbeds, guestSignin, showSuccessMessage] + ); + + return ( + }> + + Security + + + Settings that impact the access, security, and content of your knowledge base. - + + - - - - - ); - } + + + + + ); } -export default inject("auth", "ui")(Security); +export default observer(Security); diff --git a/app/scenes/Settings/Zapier.js b/app/scenes/Settings/Zapier.js index 6899375ca..a79f7237c 100644 --- a/app/scenes/Settings/Zapier.js +++ b/app/scenes/Settings/Zapier.js @@ -1,5 +1,6 @@ // @flow import * as React from "react"; +import { useTranslation, Trans } from "react-i18next"; import Button from "components/Button"; import Heading from "components/Heading"; import HelpText from "components/HelpText"; @@ -7,13 +8,16 @@ import Scene from "components/Scene"; import ZapierIcon from "components/ZapierIcon"; function Zapier() { + const { t } = useTranslation(); return ( - }> - Zapier + }> + {t("Zapier")} - Zapier is a platform that allows Outline to easily integrate with - thousands of other business tools. Head over to Zapier to setup a "Zap" - and start programmatically interacting with Outline. + + Zapier is a platform that allows Outline to easily integrate with + thousands of other business tools. Head over to Zapier to setup a + "Zap" and start programmatically interacting with Outline.' +

diff --git a/shared/i18n/locales/en_US/translation.json b/shared/i18n/locales/en_US/translation.json index 22574f025..d474c8274 100644 --- a/shared/i18n/locales/en_US/translation.json +++ b/shared/i18n/locales/en_US/translation.json @@ -424,6 +424,14 @@ "Active": "Active", "Everyone": "Everyone", "Admins": "Admins", + "Settings saved": "Settings saved", + "Unable to upload new logo": "Unable to upload new logo", + "These details affect the way that your Outline appears to everyone on the team.": "These details affect the way that your Outline appears to everyone on the team.", + "Logo": "Logo", + "Crop logo": "Crop logo", + "Upload": "Upload", + "Subdomain": "Subdomain", + "Your knowledge base will be accessible at": "Your knowledge base will be accessible at", "New group": "New group", "Groups can be used to organize and manage the people on your team.": "Groups can be used to organize and manage the people on your team.", "All groups": "All groups", @@ -441,18 +449,37 @@ "Export Requested": "Export Requested", "Requesting Export": "Requesting Export", "Export Data": "Export Data", + "Document published": "Document published", + "Receive a notification whenever a new document is published": "Receive a notification whenever a new document is published", + "Document updated": "Document updated", + "Receive a notification when a document you created is edited": "Receive a notification when a document you created is edited", + "Collection created": "Collection created", + "Receive a notification whenever a new collection is created": "Receive a notification whenever a new collection is created", + "Getting started": "Getting started", + "Tips on getting started with Outline`s features and functionality": "Tips on getting started with Outline`s features and functionality", + "New features": "New features", + "Receive an email when new features of note are added": "Receive an email when new features of note are added", + "Notifications saved": "Notifications saved", + "Unsubscription successful. Your notification settings were updated": "Unsubscription successful. Your notification settings were updated", + "Manage when and where you receive email notifications from Outline. Your email address can be updated in your SSO provider.": "Manage when and where you receive email notifications from Outline. Your email address can be updated in your SSO provider.", + "Email address": "Email address", "Everyone that has signed into Outline appears here. It’s possible that there are other users who have access through {team.signinMethods} but haven’t signed in yet.": "Everyone that has signed into Outline appears here. It’s possible that there are other users who have access through {team.signinMethods} but haven’t signed in yet.", "Filter": "Filter", "Profile saved": "Profile saved", "Profile picture updated": "Profile picture updated", "Unable to upload new profile picture": "Unable to upload new profile picture", "Photo": "Photo", - "Upload": "Upload", "Language": "Language", "Please note that translations are currently in early access.<1>Community contributions are accepted though our <4>translation portal": "Please note that translations are currently in early access.<1>Community contributions are accepted though our <4>translation portal", "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", + "Settings that impact the access, security, and content of your knowledge base.": "Settings that impact the access, security, and content of your knowledge base.", + "Allow email authentication": "Allow email authentication", + "When enabled, users can sign-in using their email address": "When enabled, users can sign-in using their email address", + "When enabled, documents can be shared publicly on the internet by any team member": "When enabled, documents can be shared publicly on the internet by any team member", + "Rich service embeds": "Rich service embeds", + "Links to supported services are shown as rich embeds within your documents": "Links to supported services are shown as rich embeds within your documents", "Documents that have been shared are listed below. Anyone that has the public link can access a read-only version of the document until the link has been revoked.": "Documents that have been shared are listed below. Anyone that has the public link can access a read-only version of the document until the link has been revoked.", "Sharing is currently disabled.": "Sharing is currently disabled.", "You can globally enable and disable public document sharing in the security settings.": "You can globally enable and disable public document sharing in the security settings.", @@ -469,6 +496,9 @@ "You can create an unlimited amount of personal tokens to authenticate\n with the API. Tokens have the same permissions as your user account.\n For more details see the developer documentation.": "You can create an unlimited amount of personal tokens to authenticate\n with the API. Tokens have the same permissions as your user account.\n For more details see the developer documentation.", "Tokens": "Tokens", "Create a token": "Create a token", + "Zapier": "Zapier", + "Zapier is a platform that allows Outline to easily integrate with thousands of other business tools. Head over to Zapier to setup a \"Zap\" and start programmatically interacting with Outline.'": "Zapier is a platform that allows Outline to easily integrate with thousands of other business tools. Head over to Zapier to setup a \"Zap\" and start programmatically interacting with Outline.'", + "Open Zapier": "Open Zapier", "You’ve not starred any documents yet.": "You’ve not starred any documents yet.", "There are no templates just yet.": "There are no templates just yet.", "You can create templates to help your team create consistent and accurate documentation.": "You can create templates to help your team create consistent and accurate documentation.",