diff --git a/ghost/portal/package.json b/ghost/portal/package.json index 434e6d6440..55a8c42720 100644 --- a/ghost/portal/package.json +++ b/ghost/portal/package.json @@ -33,10 +33,12 @@ "eslintConfig": { "extends": [ "react-app", - "plugin:ghost/browser" + "plugin:ghost/browser", + "plugin:i18next/recommended" ], "plugins": [ - "ghost" + "ghost", + "i18next" ] }, "browserslist": { @@ -60,7 +62,7 @@ }, "devDependencies": { "@babel/eslint-parser": "7.21.8", - "@doist/react-interpolate": "0.4.0", + "@doist/react-interpolate": "0.4.1", "@sentry/react": "7.52.1", "@sentry/tracing": "7.53.0", "@testing-library/jest-dom": "5.16.5", @@ -73,6 +75,7 @@ "cross-fetch": "3.1.6", "eslint": "8.37.0", "eslint-config-react-app": "7.0.1", + "eslint-plugin-i18next": "^6.0.1", "jsdom": "22.0.0", "react": "17.0.2", "react-dom": "17.0.2", diff --git a/ghost/portal/src/components/Global.styles.js b/ghost/portal/src/components/Global.styles.js index 77c6cbec9a..ffa0009663 100644 --- a/ghost/portal/src/components/Global.styles.js +++ b/ghost/portal/src/components/Global.styles.js @@ -219,10 +219,16 @@ export const GlobalStyles = ` padding: 10vmin 28px; } - .gh-mobile-shortener { + .gh-desktop-only { + display: none; + } + } + + @media (min-width: 481px) { + .gh-mobile-only { display: none; } } `; -export default GlobalStyles; \ No newline at end of file +export default GlobalStyles; diff --git a/ghost/portal/src/components/Notification.js b/ghost/portal/src/components/Notification.js index 0ba6a3f440..4d77c54de6 100644 --- a/ghost/portal/src/components/Notification.js +++ b/ghost/portal/src/components/Notification.js @@ -26,6 +26,7 @@ const Styles = () => { }; const NotificationText = ({type, status, context}) => { + const t = context.t; const signinPortalLink = getPortalLink({page: 'signin', siteUrl: context.site.url}); const singupPortalLink = getPortalLink({page: 'signup', siteUrl: context.site.url}); @@ -33,16 +34,18 @@ const NotificationText = ({type, status, context}) => { const firstname = context.member.firstname || ''; return (
- Welcome back{(firstname ? ', ' + firstname : '')}!
You've successfully signed in.
+ {firstname ? t('Welcome back, {{name}}!', firstname) : t('Welcome back!')}
{t('You\'ve successfully signed in.')}
- Could not sign in. Login link expired. Click here to retry + {t('Could not sign in. Login link expired.')} {t('Click here to retry')}
); } else if (type === 'signup' && status === 'success') { + // TODO: Wrap these strings with translation function + /* eslint-disable i18next/no-literal-string */ return (
You've successfully subscribed to
{context.site.title}
@@ -54,41 +57,42 @@ const NotificationText = ({type, status, context}) => {
You've successfully subscribed to
{context.site.title}
- Success! Your email is updated. + {t('Success! Your email is updated.')}
); } else if (type === 'updateEmail' && status === 'error') { return (- Could not update email! Invalid link. + {t('Could not update email! Invalid link.')}
); } else if (type === 'signup' && status === 'error') { return (
- Signup error: Invalid link
Click here to retry
+ {t('Signup error: Invalid link')}
{t('Click here to retry')}
- Signup error: Invalid link
Click here to retry
+ {t('Signup error: Invalid link')}
{t('Click here to retry')}
- Success! Your account is fully activated, you now have access to all content. + {t('Success! Your account is fully activated, you now have access to all content.')}
); } return (- Success! Check your email for magic link to sign-in. + {t('Success! Check your email for magic link to sign-in.')}
); } else if (type === 'stripe:checkout' && status === 'warning') { @@ -96,19 +100,19 @@ const NotificationText = ({type, status, context}) => { if (context.member) { return (- Plan upgrade was cancelled. + {t('Plan upgrade was cancelled.')}
); } return (- Plan checkout was cancelled. + {t('Plan checkout was cancelled.')}
); } return (- {status === 'success' ? 'Success' : 'Error'} + {status === 'success' ? t('Success') : t('Error')}
); }; diff --git a/ghost/portal/src/components/common/NewsletterManagement.js b/ghost/portal/src/components/common/NewsletterManagement.js index b1245e9b31..f3380b9f67 100644 --- a/ghost/portal/src/components/common/NewsletterManagement.js +++ b/ghost/portal/src/components/common/NewsletterManagement.js @@ -207,6 +207,7 @@ export default function NewsletterManagement({ className="gh-portal-btn-text gh-email-faq-page-button" onClick={() => onAction('switchPage', {page: 'emailReceivingFAQ'})} > + {/* eslint-disable-next-line i18next/no-literal-string */} {t('Get help')} → diff --git a/ghost/portal/src/components/common/PopupNotification.js b/ghost/portal/src/components/common/PopupNotification.js index 957dff5b4c..ca4f72ef73 100644 --- a/ghost/portal/src/components/common/PopupNotification.js +++ b/ghost/portal/src/components/common/PopupNotification.js @@ -5,6 +5,8 @@ import {ReactComponent as CheckmarkIcon} from '../../images/icons/checkmark-fill import {ReactComponent as WarningIcon} from '../../images/icons/warning-fill.svg'; import {getSupportAddress} from '../../utils/helpers'; import {clearURLParams} from '../../utils/notifications'; +import Interpolate from '@doist/react-interpolate'; +import {SYNTAX_I18NEXT} from '@doist/react-interpolate'; export const PopupNotificationStyles = ` .gh-portal-popupnotification { @@ -77,24 +79,24 @@ export const PopupNotificationStyles = ` } @keyframes popupnotification-slidein { - 0% { - transform: translateY(-10px); + 0% { + transform: translateY(-10px); opacity: 0; } 60% { transform: translateY(2px); } - 100% { - transform: translateY(0); + 100% { + transform: translateY(0); opacity: 1.0; } } @keyframes popupnotification-slideout { - 0% { + 0% { transform: translateY(0); opacity: 1.0; } 40% { transform: translateY(2px); } - 100% { + 100% { transform: translateY(-10px); opacity: 0; } @@ -110,7 +112,7 @@ const CloseButton = ({hide = false, onClose}) => { ); }; -const NotificationText = ({message, site}) => { +const NotificationText = ({message, site, t}) => { const supportAddress = getSupportAddress({site}); const supportAddressMail = `mailto:${supportAddress}`; if (message) { @@ -119,9 +121,18 @@ const NotificationText = ({message, site}) => { ); } return ( -An unexpected error occured. Please try again or { - supportAddressMail && window.open(supportAddressMail); - }}>contact support if the error persists.
+
+
{t('Update your preferences')}
diff --git a/ghost/portal/src/components/pages/OfferPage.js b/ghost/portal/src/components/pages/OfferPage.js index d84620906e..427f4697f5 100644 --- a/ghost/portal/src/components/pages/OfferPage.js +++ b/ghost/portal/src/components/pages/OfferPage.js @@ -431,7 +431,7 @@ export default class OfferPage extends React.Component { } renderOfferTag() { - const {pageData: offer} = this.context; + const {pageData: offer, t} = this.context; if (offer.amount <= 0) { return ( @@ -441,18 +441,20 @@ export default class OfferPage extends React.Component { if (offer.type === 'fixed') { return ( -Try free for {offer.amount} days, then {originalPrice}. Cancel anytime.
+{t('Try free for {{amount}} days, then {{originalPrice}}.', { + amount: offer.amount, + originalPrice: originalPrice + })} {t('Cancel anytime.')}
); } return ( -{this.getOffAmount({offer})} off {durationLabel}. {renewsLabel}
+{offerLabel} {useRenewsLabel ? renewsLabel : ''}
); } @@ -611,7 +632,7 @@ export default class OfferPage extends React.Component { {(benefits.length ? this.renderBenefits({product}) : '')} - +{offer.display_description}
: '')} diff --git a/yarn.lock b/yarn.lock index 3fdf88ff57..6536535f08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2034,10 +2034,10 @@ resolved "https://registry.yarnpkg.com/@ebay/nice-modal-react/-/nice-modal-react-1.2.10.tgz#6b2406bfce4a5daffc43f5b85f5f238311cdfe93" integrity sha512-qNp8vQo5kPRwB9bHlkh8lcwH/0KFWpp58X/b9KaLB/gNlJ3W24nCT2l/qBBSnWgV7NEIq25uLowaPS2mbfpZiw== -"@doist/react-interpolate@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@doist/react-interpolate/-/react-interpolate-0.4.0.tgz#b22a408ec78374213e0148473f082dd4c9a72285" - integrity sha512-hGSaTKnY5U6F3/MvuoZfGvtN4nbpL2NhL7c8atQqm0Yn4E2I4ChQQiMxm9/3NEOzd6VfyFxuK9Eo85ogTIO/PA== +"@doist/react-interpolate@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@doist/react-interpolate/-/react-interpolate-0.4.1.tgz#696d14e90ffc849e94a4e3e94578c5eade1590b8" + integrity sha512-JxBJMpDlXByMrA7T6Z+tRdCqXlbnM1ikfasLaqMrWvV4TvvrPuD7dngU/X01iP9tpAycfxasNvbNKFs/QehViQ== "@elastic/elasticsearch@8.6.0": version "8.6.0" @@ -16801,6 +16801,14 @@ eslint-plugin-ghost@3.0.0: eslint-plugin-sort-imports-es6-autofix "0.6.0" eslint-plugin-unicorn "42.0.0" +eslint-plugin-i18next@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-i18next/-/eslint-plugin-i18next-6.0.1.tgz#b0a289179598b247a73881abccb4b73ea8a5e835" + integrity sha512-aJ0UlZLVqBK7mdXsl3yFQNz72OPUjSmBfcrMjCNxfMp8YVBAlKE83ZfzzTLCMQvFlXZs0TWYTtoteO/ZiZdDdw== + dependencies: + lodash "^4.17.21" + requireindex "~1.1.0" + eslint-plugin-import@^2.25.3: version "2.27.5" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" @@ -29227,6 +29235,11 @@ requireindex@^1.2.0: resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== +requireindex@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162" + integrity sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"