From acf2ab2d22350f439d7bd9c0dc91186ec96eb13b Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Mon, 27 Feb 2023 10:39:38 +0100 Subject: [PATCH] Added translation wrapper to public-facing strings in Portal refs https://github.com/TryGhost/Ghost/issues/15502 - in order to use the translations, strings must be wrapped in the `t` function, which is passed through AppContext - whilst I haven't instrumented all public strings, the vast majority are done here and the strings have been brought into the JSON locale files using `yarn translate:generate` --- ghost/i18n/locales/en.json | 61 ++++++++++++++++++- ghost/i18n/locales/nl.json | 61 ++++++++++++++++++- .../components/common/NewsletterManagement.js | 21 ++++--- .../portal/src/components/common/PoweredBy.js | 9 ++- .../src/components/common/ProductsSection.js | 12 ++-- .../components/common/SiteTitleBackButton.js | 4 +- .../src/components/pages/AccountEmailPage.js | 4 +- .../components/EmailPreferencesAction.js | 8 +-- .../AccountHomePage/components/UserHeader.js | 4 +- .../src/components/pages/AccountPlanPage.js | 14 ++--- .../components/pages/AccountProfilePage.js | 20 ++++-- .../components/pages/EmailSuppressedPage.js | 12 ++-- .../src/components/pages/FeedbackPage.js | 22 +++---- .../src/components/pages/MagicLinkPage.js | 18 ++++-- .../pages/NewsletterSelectionPage.js | 10 +-- .../portal/src/components/pages/OfferPage.js | 32 +++++----- .../portal/src/components/pages/SigninPage.js | 19 +++--- .../portal/src/components/pages/SignupPage.js | 30 ++++----- .../src/components/pages/UnsubscribePage.js | 12 ++-- 19 files changed, 259 insertions(+), 114 deletions(-) diff --git a/ghost/i18n/locales/en.json b/ghost/i18n/locales/en.json index 7ce7d40f3c5..91f7e2dd3ae 100644 --- a/ghost/i18n/locales/en.json +++ b/ghost/i18n/locales/en.json @@ -1,3 +1,62 @@ { - "Hello": "Hello" + "{{discount}}% discount": "", + "{{trialDays}} days free": "", + "A login link has been sent to your inbox. If it doesn't arrive in 3 minutes, be sure to check your spam folder.": "", + "Account": "", + "Account settings": "", + "After a free trial ends, you will be charged the regular price for the tier you've chosen. You can always cancel before then.": "", + "Already a member?": "", + "Back": "", + "Back to Log in": "", + "Cancel subscription": "", + "Cancellation reason": "", + "Choose a different plan": "", + "Choose your newsletters": "", + "Close": "", + "Comments": "", + "Confirm": "", + "Continue": "", + "Delete account": "", + "Don't have an account?": "", + "Email": "", + "Email preference updated.": "", + "Email preferences": "", + "Emails": "", + "Emails disabled": "", + "Get help": "", + "Get notified when someone replies to your comment": "", + "Give feedback on this post": "", + "Hello": "", + "Less like this": "", + "Manage": "", + "Monthly": "", + "More like this": "", + "Name": "", + "Not receiving emails?": "", + "Now check your email!": "", + "Powered by Ghost": "", + "Price": "", + "Re-enable emails": "", + "Retry": "", + "Save": "", + "Sending login link...": "", + "Sending...": "", + "Sign in": "", + "Sign up": "", + "Start {{amount}}-day free trial": "", + "Submit feedback": "", + "Successfully unsubscribed": "", + "Thanks for the feedback!": "", + "That didn't go to plan": "", + "This site is invite-only, contact the owner for access.": "", + "To complete signup, click the confirmation link in your inbox. If it doesn't arrive within 3 minutes, check your spam folder!": "", + "Unsubscribe from all emails": "", + "Unsubscribing from emails will not cancel your paid subscription to {{title}}": "", + "Update your preferences": "", + "We couldn't unsubscribe you as the email address was not found. Please contact the site owner.": "", + "Yearly": "", + "You have been successfully resubscribed": "", + "You're not receiving emails because you either marked a recent message as spam, or because messages could not be delivered to your provided email address.": "", + "Your account": "", + "Your input helps shape what gets published.": "" } diff --git a/ghost/i18n/locales/nl.json b/ghost/i18n/locales/nl.json index 904fe1b030c..499c8f6821c 100644 --- a/ghost/i18n/locales/nl.json +++ b/ghost/i18n/locales/nl.json @@ -1,3 +1,62 @@ { - "Hello": "Hallo" + "{{discount}}% discount": "", + "{{trialDays}} days free": "", + "A login link has been sent to your inbox. If it doesn't arrive in 3 minutes, be sure to check your spam folder.": "", + "Account": "", + "Account settings": "", + "After a free trial ends, you will be charged the regular price for the tier you've chosen. You can always cancel before then.": "", + "Already a member?": "", + "Back": "", + "Back to Log in": "", + "Cancel subscription": "", + "Cancellation reason": "", + "Choose a different plan": "", + "Choose your newsletters": "", + "Close": "", + "Comments": "", + "Confirm": "", + "Continue": "", + "Delete account": "", + "Don't have an account?": "", + "Email": "", + "Email preference updated.": "", + "Email preferences": "", + "Emails": "", + "Emails disabled": "", + "Get help": "", + "Get notified when someone replies to your comment": "", + "Give feedback on this post": "", + "Hello": "Hallo", + "Less like this": "", + "Manage": "", + "Monthly": "", + "More like this": "", + "Name": "", + "Not receiving emails?": "", + "Now check your email!": "", + "Powered by Ghost": "", + "Price": "", + "Re-enable emails": "", + "Retry": "", + "Save": "", + "Sending login link...": "", + "Sending...": "", + "Sign in": "", + "Sign up": "", + "Start {{amount}}-day free trial": "", + "Submit feedback": "", + "Successfully unsubscribed": "", + "Thanks for the feedback!": "", + "That didn't go to plan": "", + "This site is invite-only, contact the owner for access.": "", + "To complete signup, click the confirmation link in your inbox. If it doesn't arrive within 3 minutes, check your spam folder!": "", + "Unsubscribe from all emails": "", + "Unsubscribing from emails will not cancel your paid subscription to {{title}}": "", + "Update your preferences": "", + "We couldn't unsubscribe you as the email address was not found. Please contact the site owner.": "", + "Yearly": "", + "You have been successfully resubscribed": "", + "You're not receiving emails because you either marked a recent message as spam, or because messages could not be delivered to your provided email address.": "", + "Your account": "", + "Your input helps shape what gets published.": "" } diff --git a/ghost/portal/src/components/common/NewsletterManagement.js b/ghost/portal/src/components/common/NewsletterManagement.js index 2146c3ed191..5278f806bd8 100644 --- a/ghost/portal/src/components/common/NewsletterManagement.js +++ b/ghost/portal/src/components/common/NewsletterManagement.js @@ -10,13 +10,13 @@ import {ReactComponent as CheckmarkIcon} from '../../images/icons/check-circle.s const React = require('react'); function AccountHeader() { - const {brandColor, lastPage, onAction} = useContext(AppContext); + const {brandColor, lastPage, onAction, t} = useContext(AppContext); return (
); } @@ -86,6 +86,7 @@ function NewsletterPrefSection({newsletter, subscribedNewsletters, setSubscribed } function CommentsSection({updateCommentNotifications, isCommentsEnabled, enableCommentNotifications}) { + const {t} = useContext(AppContext); const isChecked = !!enableCommentNotifications; const [showUpdated, setShowUpdated] = useState(false); @@ -98,8 +99,8 @@ function CommentsSection({updateCommentNotifications, isCommentsEnabled, enableC return (
-

Comments

-

Get notified when someone replies to your comment

+

{t('Comments')}

+

{t('Get notified when someone replies to your comment')}

@@ -133,9 +134,11 @@ function NewsletterPrefs({subscribedNewsletters, setSubscribedNewsletters}) { } function ShowPaidMemberMessage({site, isPaid}) { + const {t} = useContext(AppContext); + if (isPaid) { return ( -

Unsubscribing from emails will not cancel your paid subscription to {site?.title}

+

{t('Unsubscribing from emails will not cancel your paid subscription to {{title}}', {title: site?.title})}

); } return null; @@ -151,7 +154,7 @@ export default function NewsletterManagement({ isCommentsEnabled, enableCommentNotifications }) { - const {brandColor, onAction, member, site} = useContext(AppContext); + const {brandColor, onAction, member, site, t} = useContext(AppContext); const isDisabled = !subscribedNewsletters?.length && ((isCommentsEnabled && !enableCommentNotifications) || !isCommentsEnabled); const EmptyNotification = () => { return null; @@ -192,7 +195,7 @@ export default function NewsletterManagement({ disabled={isDisabled} brandColor={brandColor} isPrimary={false} - label='Unsubscribe from all emails' + label={t('Unsubscribe from all emails')} isDestructive={true} style={{width: '100%'}} dataTestId="unsubscribe-from-all-emails" @@ -201,12 +204,12 @@ export default function NewsletterManagement({
{hasMemberGotEmailSuppression({member}) && !isDisabled &&
- Not receiving emails? + {t('Not receiving emails?')}
} diff --git a/ghost/portal/src/components/common/PoweredBy.js b/ghost/portal/src/components/common/PoweredBy.js index 2ded25c76b6..fb67a030211 100644 --- a/ghost/portal/src/components/common/PoweredBy.js +++ b/ghost/portal/src/components/common/PoweredBy.js @@ -1,15 +1,20 @@ import React from 'react'; +import AppContext from '../../AppContext'; import {ReactComponent as GhostLogo} from '../../images/ghost-logo-small.svg'; export default class PoweredBy extends React.Component { + static contextType = AppContext; + render() { + const {t} = this.context; + return ( { window.open('https://ghost.org', '_blank'); }}> - Powered by Ghost + {t('Powered by Ghost')} ); } -} \ No newline at end of file +} diff --git a/ghost/portal/src/components/common/ProductsSection.js b/ghost/portal/src/components/common/ProductsSection.js index 4017d8394b1..084b456c63c 100644 --- a/ghost/portal/src/components/common/ProductsSection.js +++ b/ghost/portal/src/components/common/ProductsSection.js @@ -547,12 +547,12 @@ function ProductCardAlternatePrice({price}) { } function ProductCardTrialDays({trialDays, discount, selectedInterval}) { - const {site} = useContext(AppContext); + const {site, t} = useContext(AppContext); if (hasFreeTrialTier({site})) { if (trialDays) { return ( - {trialDays} days free + {t('{{trialDays}} days free', {trialDays})} ); } else { return null; @@ -561,7 +561,7 @@ function ProductCardTrialDays({trialDays, discount, selectedInterval}) { if (selectedInterval === 'year') { return ( - {discount}% discount + {t('{{discount}}% discount', {discount})} ); } @@ -809,7 +809,7 @@ function YearlyDiscount({discount, trialDays}) { } function ProductPriceSwitch({products, selectedInterval, setSelectedInterval}) { - const {site} = useContext(AppContext); + const {site, t} = useContext(AppContext); const {portal_plans: portalPlans} = site; if (!portalPlans.includes('monthly') || !portalPlans.includes('yearly')) { return null; @@ -825,7 +825,7 @@ function ProductPriceSwitch({products, selectedInterval, setSelectedInterval}) { setSelectedInterval('month'); }} > - Monthly + {t('Monthly')} diff --git a/ghost/portal/src/components/common/SiteTitleBackButton.js b/ghost/portal/src/components/common/SiteTitleBackButton.js index f97561a2ab9..b0aec53d0c7 100644 --- a/ghost/portal/src/components/common/SiteTitleBackButton.js +++ b/ghost/portal/src/components/common/SiteTitleBackButton.js @@ -5,7 +5,7 @@ export default class SiteTitleBackButton extends React.Component { static contextType = AppContext; render() { - // const {site} = this.context; + const {t} = this.context; return ( <> ); diff --git a/ghost/portal/src/components/pages/AccountEmailPage.js b/ghost/portal/src/components/pages/AccountEmailPage.js index 538d2e2358f..e5b482de5a9 100644 --- a/ghost/portal/src/components/pages/AccountEmailPage.js +++ b/ghost/portal/src/components/pages/AccountEmailPage.js @@ -6,7 +6,7 @@ import NewsletterManagement from '../common/NewsletterManagement'; const React = require('react'); export default function AccountEmailPage() { - const {member, onAction, site} = useContext(AppContext); + const {member, onAction, site, t} = useContext(AppContext); useEffect(() => { if (!member) { @@ -40,7 +40,7 @@ export default function AccountEmailPage() { setSubscribedNewsletters([]); onAction('showPopupNotification', { action: 'updated:success', - message: `Email preference updated.` + message: t(`Email preference updated.`) }); const data = {newsletters: []}; if (commentsEnabled) { diff --git a/ghost/portal/src/components/pages/AccountHomePage/components/EmailPreferencesAction.js b/ghost/portal/src/components/pages/AccountHomePage/components/EmailPreferencesAction.js index cf7bd92a44e..80d9390d49b 100644 --- a/ghost/portal/src/components/pages/AccountHomePage/components/EmailPreferencesAction.js +++ b/ghost/portal/src/components/pages/AccountHomePage/components/EmailPreferencesAction.js @@ -4,14 +4,14 @@ import {isEmailSuppressed} from 'utils/helpers'; import {ReactComponent as EmailDeliveryFailedIcon} from 'images/icons/email-delivery-failed.svg'; function EmailPreferencesAction() { - const {onAction, member} = useContext(AppContext); + const {onAction, member, t} = useContext(AppContext); const emailSuppressed = isEmailSuppressed({member}); const page = emailSuppressed ? 'emailSuppressed' : 'accountEmail'; return (
-

Emails

+

{t('Emails')}

{ emailSuppressed ? ( @@ -20,7 +20,7 @@ function EmailPreferencesAction() { You're currently not receiving emails

) - :

Update your preferences

+ :

{t('Update your preferences')}

}
); diff --git a/ghost/portal/src/components/pages/AccountHomePage/components/UserHeader.js b/ghost/portal/src/components/pages/AccountHomePage/components/UserHeader.js index a1101e55e55..ab35bd4a927 100644 --- a/ghost/portal/src/components/pages/AccountHomePage/components/UserHeader.js +++ b/ghost/portal/src/components/pages/AccountHomePage/components/UserHeader.js @@ -3,12 +3,12 @@ import MemberAvatar from 'components/common/MemberGravatar'; import React, {useContext} from 'react'; const UserHeader = () => { - const {member, brandColor} = useContext(AppContext); + const {member, brandColor, t} = useContext(AppContext); const avatar = member.avatar_image; return (
-

Your account

+

{t('Your account')}

); }; diff --git a/ghost/portal/src/components/pages/AccountPlanPage.js b/ghost/portal/src/components/pages/AccountPlanPage.js index 97f75ee2ecf..215b894504a 100644 --- a/ghost/portal/src/components/pages/AccountPlanPage.js +++ b/ghost/portal/src/components/pages/AccountPlanPage.js @@ -64,7 +64,7 @@ const Header = ({onBack, showConfirmation, confirmationType}) => { }; const CancelSubscriptionButton = ({member, onCancelSubscription, action, brandColor}) => { - const {site} = useContext(AppContext); + const {site, t} = useContext(AppContext); if (!member.paid) { return null; } @@ -77,7 +77,7 @@ const CancelSubscriptionButton = ({member, onCancelSubscription, action, brandCo if (subscription.cancel_at_period_end) { return null; } - const label = 'Cancel subscription'; + const label = t('Cancel subscription'); const isRunning = ['cancelSubscription:running'].includes(action); const disabled = (isRunning) ? true : false; const isPrimary = !!subscription.cancel_at_period_end; @@ -110,11 +110,11 @@ const CancelSubscriptionButton = ({member, onCancelSubscription, action, brandCo // For confirmation flows const PlanConfirmationSection = ({plan, type, onConfirm}) => { - const {site, action, member, brandColor} = useContext(AppContext); + const {site, action, member, brandColor, t} = useContext(AppContext); const [reason, setReason] = useState(''); const subscription = getMemberSubscription({member}); const isRunning = ['updateSubscription:running', 'checkoutPlan:running', 'cancelSubscription:running'].includes(action); - const label = 'Confirm'; + const label = t('Confirm'); let planStartDate = getDateString(subscription.current_period_end); const currentActivePlan = getMemberActivePrice({member}); if (currentActivePlan.id !== plan.id) { @@ -123,14 +123,14 @@ const PlanConfirmationSection = ({plan, type, onConfirm}) => { const priceString = formatNumber(plan.price); const planStartMessage = `${plan.currency_symbol}${priceString}/${plan.interval} – Starting ${planStartDate}`; const product = getProductFromPrice({site, priceId: plan?.id}); - const priceLabel = hasMultipleProductsFeature({site}) ? product?.name : 'Price'; + const priceLabel = hasMultipleProductsFeature({site}) ? product?.name : t('Price'); if (type === 'changePlan') { return (
-

Account

+

{t('Account')}

{member.email}

@@ -161,7 +161,7 @@ const PlanConfirmationSection = ({plan, type, onConfirm}) => {

If you cancel your subscription now, you will continue to have access until {getDateString(subscription.current_period_end)}.

- +