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`
This commit is contained in:
Daniel Lockyer 2023-02-27 10:39:38 +01:00 committed by Daniel Lockyer
parent f007094d4b
commit acf2ab2d22
19 changed files with 259 additions and 114 deletions

View File

@ -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.": ""
}

View File

@ -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.": ""
}

View File

@ -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 (
<header className='gh-portal-detail-header'>
<BackButton brandColor={brandColor} hidden={!lastPage} onClick={(e) => {
onAction('back');
}} />
<h3 className='gh-portal-main-title'>Email preferences</h3>
<h3 className='gh-portal-main-title'>{t('Email preferences')}</h3>
</header>
);
}
@ -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 (
<section className='gh-portal-list-toggle-wrapper' data-test-toggle-wrapper>
<div className='gh-portal-list-detail'>
<h3>Comments</h3>
<p>Get notified when someone replies to your comment</p>
<h3>{t('Comments')}</h3>
<p>{t('Get notified when someone replies to your comment')}</p>
</div>
<div style={{display: 'flex', alignItems: 'center'}}>
<SuccessIcon show={showUpdated} checked={isChecked} />
@ -133,9 +134,11 @@ function NewsletterPrefs({subscribedNewsletters, setSubscribedNewsletters}) {
}
function ShowPaidMemberMessage({site, isPaid}) {
const {t} = useContext(AppContext);
if (isPaid) {
return (
<p style={{textAlign: 'center', marginTop: '12px', marginBottom: '0', color: 'var(--grey6)'}}>Unsubscribing from emails will not cancel your paid subscription to {site?.title}</p>
<p style={{textAlign: 'center', marginTop: '12px', marginBottom: '0', color: 'var(--grey6)'}}>{t('Unsubscribing from emails will not cancel your paid subscription to {{title}}', {title: site?.title})}</p>
);
}
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({
</div>
{hasMemberGotEmailSuppression({member}) && !isDisabled &&
<div className="gh-portal-footer-secondary">
<span className="gh-portal-footer-secondary-light">Not receiving emails?</span>
<span className="gh-portal-footer-secondary-light">{t('Not receiving emails?')}</span>
<button
className="gh-portal-btn-text gh-email-faq-page-button"
onClick={() => onAction('switchPage', {page: 'emailReceivingFAQ'})}
>
Get help &rarr;
{t('Get help')} &rarr;
</button>
</div>
}

View File

@ -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 (
<a href='https://ghost.org' target='_blank' rel='noopener noreferrer' onClick={() => {
window.open('https://ghost.org', '_blank');
}}>
<GhostLogo />
Powered by Ghost
{t('Powered by Ghost')}
</a>
);
}
}
}

View File

@ -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 (
<span className="gh-portal-discount-label">{trialDays} days free</span>
<span className="gh-portal-discount-label">{t('{{trialDays}} days free', {trialDays})}</span>
);
} else {
return null;
@ -561,7 +561,7 @@ function ProductCardTrialDays({trialDays, discount, selectedInterval}) {
if (selectedInterval === 'year') {
return (
<span className="gh-portal-discount-label">{discount}% discount</span>
<span className="gh-portal-discount-label">{t('{{discount}}% discount', {discount})}</span>
);
}
@ -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')}
</button>
<button
data-test-button='switch-yearly'
@ -834,7 +834,7 @@ function ProductPriceSwitch({products, selectedInterval, setSelectedInterval}) {
setSelectedInterval('year');
}}
>
Yearly
{t('Yearly')}
</button>
</div>
</div>

View File

@ -5,7 +5,7 @@ export default class SiteTitleBackButton extends React.Component {
static contextType = AppContext;
render() {
// const {site} = this.context;
const {t} = this.context;
return (
<>
<button
@ -17,7 +17,7 @@ export default class SiteTitleBackButton extends React.Component {
this.context.onAction('closePopup');
}
}}>
<span>&larr; </span> Back
<span>&larr; </span> {t('Back')}
</button>
</>
);

View File

@ -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) {

View File

@ -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 (
<section>
<div className='gh-portal-list-detail'>
<h3>Emails</h3>
<h3>{t('Emails')}</h3>
{
emailSuppressed
? (
@ -20,7 +20,7 @@ function EmailPreferencesAction() {
<span>You're <span className="gh-mobile-shortener">currently </span>not receiving emails</span>
</p>
)
: <p>Update your preferences</p>
: <p>{t('Update your preferences')}</p>
}
</div>
<button className='gh-portal-btn gh-portal-btn-list' onClick={(e) => {
@ -29,7 +29,7 @@ function EmailPreferencesAction() {
lastPage: 'accountHome'
});
}} data-test-button='manage-newsletters'>
Manage
{t('Manage')}
</button>
</section>
);

View File

@ -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 (
<header className='gh-portal-account-header'>
<MemberAvatar gravatar={avatar} style={{userIcon: {color: brandColor, width: '56px', height: '56px', padding: '2px'}}} />
<h2 className="gh-portal-main-title">Your account</h2>
<h2 className="gh-portal-main-title">{t('Your account')}</h2>
</header>
);
};

View File

@ -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 (
<div className='gh-portal-logged-out-form-container'>
<div className='gh-portal-list mb6'>
<section>
<div className='gh-portal-list-detail'>
<h3>Account</h3>
<h3>{t('Account')}</h3>
<p>{member.email}</p>
</div>
</section>
@ -161,7 +161,7 @@ const PlanConfirmationSection = ({plan, type, onConfirm}) => {
<p>If you cancel your subscription now, you will continue to have access until <strong>{getDateString(subscription.current_period_end)}</strong>.</p>
<section className='gh-portal-input-section'>
<div className='gh-portal-input-labelcontainer'>
<label className='gh-portal-input-label'>Cancellation reason</label>
<label className='gh-portal-input-label'>{t('Cancellation reason')}</label>
</div>
<textarea
data-test-input='cancellation-reason'

View File

@ -55,10 +55,12 @@ export default class AccountProfilePage extends React.Component {
}
renderSaveButton() {
const {t} = this.context;
const isRunning = (this.context.action === 'updateProfile:running');
let label = 'Save';
let label = t('Save');
if (this.context.action === 'updateProfile:failed') {
label = 'Retry';
label = t('Retry');
}
const disabled = isRunning ? true : false;
return (
@ -75,8 +77,10 @@ export default class AccountProfilePage extends React.Component {
}
renderDeleteAccountButton() {
const {t} = this.context;
return (
<div style={{cursor: 'pointer', color: 'red'}} role='button'>Delete account</div>
<div style={{cursor: 'pointer', color: 'red'}} role='button'>{t('Delete account')}</div>
);
}
@ -89,10 +93,12 @@ export default class AccountProfilePage extends React.Component {
}
renderHeader() {
const {t} = this.context;
return (
<header className='gh-portal-detail-header'>
<BackButton brandColor={this.context.brandColor} hidden={!this.context.lastPage} onClick={e => this.onBack(e)} />
<h3 className='gh-portal-main-title'>Account settings</h3>
<h3 className='gh-portal-main-title'>{t('Account settings')}</h3>
</header>
);
}
@ -129,13 +135,15 @@ export default class AccountProfilePage extends React.Component {
}
getInputFields({state, fieldNames}) {
const {t} = this.context;
const errors = state.errors || {};
const fields = [
{
type: 'text',
value: state.name,
placeholder: 'Jamie Larson',
label: 'Name',
label: t('Name'),
name: 'name',
required: true,
errorMessage: errors.name || ''
@ -144,7 +152,7 @@ export default class AccountProfilePage extends React.Component {
type: 'email',
value: state.email,
placeholder: 'jamie@example.com',
label: 'Email',
label: t('Email'),
name: 'email',
required: true,
errorMessage: errors.email || ''

View File

@ -7,7 +7,7 @@ import ActionButton from 'components/common/ActionButton';
import {ReactComponent as EmailDeliveryFailedIcon} from 'images/icons/email-delivery-failed.svg';
export default function EmailSuppressedPage() {
const {brandColor, lastPage, onAction, action, site} = useContext(AppContext);
const {brandColor, lastPage, onAction, action, site, t} = useContext(AppContext);
useEffect(() => {
if (['removeEmailFromSuppressionList:success'].includes(action)) {
@ -26,13 +26,13 @@ export default function EmailSuppressedPage() {
lastPage: 'accountHome'
});
onAction('showPopupNotification', {
message: 'You have been successfully resubscribed'
message: t('You have been successfully resubscribed')
});
} else {
onAction('back');
}
}
}, [action, onAction, site]);
}, [action, onAction, site, t]);
const isRunning = ['removeEmailFromSuppressionList:running', 'refreshMemberData:running'].includes(action);
@ -52,9 +52,9 @@ export default function EmailSuppressedPage() {
<EmailDeliveryFailedIcon className="gh-email-suppressed-page-icon" />
<div className="gh-email-suppressed-page-text">
<h3 className="gh-portal-main-title gh-email-suppressed-page-title">Emails disabled</h3>
<h3 className="gh-portal-main-title gh-email-suppressed-page-title">{t('Emails disabled')}</h3>
<p>
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.
{t('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.')}
</p>
</div>
@ -64,7 +64,7 @@ export default function EmailSuppressedPage() {
onClick={handleSubmit}
disabled={isRunning}
brandColor={brandColor}
label="Re-enable emails"
label={t('Re-enable emails')}
isRunning={isRunning}
/>
</div>

View File

@ -153,14 +153,14 @@ export const FeedbackPageStyles = `
}
@keyframes mobile-tray-from-bottom {
0% {
0% {
opacity: 0;
transform: translateY(300px);
}
20% {
opacity: 1.0;
}
100% {
100% {
transform: translateY(0);
}
}
@ -196,7 +196,7 @@ function ErrorPage({error}) {
}
const ConfirmDialog = ({onConfirm, loading, initialScore}) => {
const {onAction, brandColor} = useContext(AppContext);
const {onAction, brandColor, t} = useContext(AppContext);
const [score, setScore] = useState(initialScore);
const stopPropagation = (event) => {
@ -223,7 +223,7 @@ const ConfirmDialog = ({onConfirm, loading, initialScore}) => {
return (
<div className="gh-portal-confirm-dialog" onMouseDown={stopPropagation}>
<h1 className="gh-portal-confirm-title">Give feedback on this post</h1>
<h1 className="gh-portal-confirm-title">{t('Give feedback on this post')}</h1>
<div className="gh-feedback-buttons-group">
<button
@ -232,7 +232,7 @@ const ConfirmDialog = ({onConfirm, loading, initialScore}) => {
onClick={() => setScore(1)}
>
<ThumbUpIcon />
More like this
{t('More like this')}
</button>
<button
@ -241,7 +241,7 @@ const ConfirmDialog = ({onConfirm, loading, initialScore}) => {
onClick={() => setScore(0)}
>
<ThumbDownIcon />
Less like this
{t('Less like this')}
</button>
</div>
@ -251,7 +251,7 @@ const ConfirmDialog = ({onConfirm, loading, initialScore}) => {
onClick={submit}
disabled={false}
brandColor={brandColor}
label="Submit feedback"
label={t('Submit feedback')}
isRunning={loading}
tabindex="3"
/>
@ -274,7 +274,7 @@ const LoadingFeedbackView = ({action, score}) => {
};
const ConfirmFeedback = ({positive}) => {
const {onAction, brandColor} = useContext(AppContext);
const {onAction, brandColor, t} = useContext(AppContext);
const icon = positive ? <ThumbUpIcon /> : <ThumbDownIcon />;
@ -285,15 +285,15 @@ const ConfirmFeedback = ({positive}) => {
<div className="gh-feedback-icon">
{icon}
</div>
<h1 className="gh-portal-main-title">Thanks for the feedback!</h1>
<p className="gh-portal-text-center">Your input helps shape what gets published.</p>
<h1 className="gh-portal-main-title">{t('Thanks for the feedback!')}</h1>
<p className="gh-portal-text-center">{t('Your input helps shape what gets published.')}</p>
<ActionButton
style={{width: '100%'}}
retry={false}
onClick = {() => onAction('closePopup')}
disabled={false}
brandColor={brandColor}
label={'Close'}
label={t('Close')}
isRunning={false}
tabindex='3'
classes={'sticky bottom'}

View File

@ -27,12 +27,14 @@ export default class MagicLinkPage extends React.Component {
static contextType = AppContext;
renderFormHeader() {
let popupTitle = `Now check your email!`;
let popupDescription = `A login link has been sent to your inbox. If it doesn't arrive in 3 minutes, be sure to check your spam folder.`;
const {t} = this.context;
let popupTitle = t(`Now check your email!`);
let popupDescription = t(`A login link has been sent to your inbox. If it doesn't arrive in 3 minutes, be sure to check your spam folder.`);
if (this.context.lastPage === 'signup') {
popupTitle = `Now check your email!`;
popupDescription = `To complete signup, click the confirmation link in your inbox. If it doesn't arrive within 3 minutes, check your spam folder!`;
popupTitle = t(`Now check your email!`);
popupDescription = t(`To complete signup, click the confirmation link in your inbox. If it doesn't arrive within 3 minutes, check your spam folder!`);
}
return (
@ -47,13 +49,15 @@ export default class MagicLinkPage extends React.Component {
}
renderLoginMessage() {
const {t} = this.context;
return (
<>
<div
style={{color: '#1d1d1d', fontWeight: 'bold', cursor: 'pointer'}}
onClick={() => this.context.onAction('switchPage', {page: 'signin'})}
>
Back to Log in
{t('Back to Log in')}
</div>
</>
);
@ -64,7 +68,9 @@ export default class MagicLinkPage extends React.Component {
}
renderCloseButton() {
const label = 'Close';
const {t} = this.context;
const label = t('Close');
return (
<ActionButton
style={{width: '100%'}}

View File

@ -65,7 +65,7 @@ function NewsletterPrefs({subscribedNewsletters, setSubscribedNewsletters}) {
}
export default function NewsletterSelectionPage({pageData, onBack}) {
const {brandColor, site, onAction, action} = useContext(AppContext);
const {brandColor, site, onAction, action, t} = useContext(AppContext);
const siteNewsletters = getSiteNewsletters({site});
const defaultNewsletters = siteNewsletters.filter((d) => {
return d.subscribe_on_signup;
@ -76,10 +76,10 @@ export default function NewsletterSelectionPage({pageData, onBack}) {
if (action === 'signup:running') {
isRunning = true;
}
let label = 'Continue';
let label = t('Continue');
let retry = false;
if (action === 'signup:failed') {
label = 'Retry';
label = t('Retry');
retry = true;
}
@ -88,7 +88,7 @@ export default function NewsletterSelectionPage({pageData, onBack}) {
const [subscribedNewsletters, setSubscribedNewsletters] = useState(defaultNewsletters);
return (
<div className='gh-portal-content with-footer gh-portal-newsletter-selection'>
<p className="gh-portal-text-center gh-portal-text-large">Choose your newsletters</p>
<p className="gh-portal-text-center gh-portal-text-large">{t('Choose your newsletters')}</p>
<div className='gh-portal-section'>
<div className='gh-portal-list'>
<NewsletterPrefs
@ -124,7 +124,7 @@ export default function NewsletterSelectionPage({pageData, onBack}) {
onClick = {() => {
onBack();
}}>
<span>Choose a different plan</span>
<span>{t('Choose a different plan')}</span>
</button>
</div>
</div>

View File

@ -152,14 +152,14 @@ export default class OfferPage extends React.Component {
getInputFields({state, fieldNames}) {
const {portal_name: portalName} = this.context.site;
const {member} = this.context;
const {member, t} = this.context;
const errors = state.errors || {};
const fields = [
{
type: 'email',
value: member?.email || state.email,
placeholder: 'jamie@example.com',
label: 'Email',
label: t('Email'),
name: 'email',
disabled: !!member,
required: true,
@ -181,7 +181,7 @@ export default class OfferPage extends React.Component {
type: 'text',
value: member?.name || state.name,
placeholder: 'Jamie Larson',
label: 'Name',
label: t('Name'),
name: 'name',
disabled: !!member,
required: true,
@ -307,22 +307,22 @@ export default class OfferPage extends React.Component {
}
renderSubmitButton() {
const {action, brandColor} = this.context;
const {action, brandColor, t} = this.context;
const {pageData: offer} = this.context;
let label = 'Continue';
let label = t('Continue');
if (offer.type === 'trial') {
label = 'Start ' + offer.amount + '-day free trial';
label = t('Start {{amount}}-day free trial', {amount: offer.amount});
}
let isRunning = false;
if (action === 'signup:running') {
label = 'Sending...';
label = t('Sending...');
isRunning = true;
}
let retry = false;
if (action === 'signup:failed') {
label = 'Retry';
label = t('Retry');
retry = true;
}
@ -347,16 +347,16 @@ export default class OfferPage extends React.Component {
if (member) {
return null;
}
const {brandColor, onAction} = this.context;
const {brandColor, onAction, t} = this.context;
return (
<div className='gh-portal-signup-message'>
<div>Already a member?</div>
<div>{t('Already a member?')}</div>
<button
className='gh-portal-btn gh-portal-btn-link'
style={{color: brandColor}}
onClick={() => onAction('switchPage', {page: 'signin'})}
>
<span>Sign in</span>
<span>{t('Sign in')}</span>
</button>
</div>
);
@ -477,15 +477,15 @@ export default class OfferPage extends React.Component {
}
renderProductLabel({product, offer}) {
const {site} = this.context;
const {site, t} = this.context;
if (hasMultipleProductsFeature({site})) {
return (
<h4 className="gh-portal-plan-name">{product.name} - {(offer.cadence === 'month' ? 'Monthly' : 'Yearly')}</h4>
<h4 className="gh-portal-plan-name">{product.name} - {(offer.cadence === 'month' ? t('Monthly') : t('Yearly'))}</h4>
);
}
return (
<h4 className="gh-portal-plan-name">{(offer.cadence === 'month' ? 'Monthly' : 'Yearly')}</h4>
<h4 className="gh-portal-plan-name">{(offer.cadence === 'month' ? t('Monthly') : t('Yearly'))}</h4>
);
}
@ -520,6 +520,8 @@ export default class OfferPage extends React.Component {
}
renderProductCard({product, offer, currencyClass, updatedPrice, price, benefits}) {
const {t} = this.context;
if (this.state.showNewsletterSelection) {
return null;
}
@ -527,7 +529,7 @@ export default class OfferPage extends React.Component {
<>
<div className='gh-portal-product-card top'>
<div className='gh-portal-product-card-header'>
<h4 className="gh-portal-product-name">{product.name} - {(offer.cadence === 'month' ? 'Monthly' : 'Yearly')}</h4>
<h4 className="gh-portal-product-name">{product.name} - {(offer.cadence === 'month' ? t('Monthly') : t('Yearly'))}</h4>
{this.renderOldTierPrice({offer, price})}
{this.renderUpdatedTierPrice({offer, currencyClass, updatedPrice, price})}
{this.renderOfferMessage({offer, product, price})}

View File

@ -56,13 +56,15 @@ export default class SigninPage extends React.Component {
}
getInputFields({state}) {
const {t} = this.context;
const errors = state.errors || {};
const fields = [
{
type: 'email',
value: state.email,
placeholder: 'jamie@example.com',
label: 'Email',
label: t('Email'),
name: 'email',
required: true,
errorMessage: errors.email || '',
@ -73,13 +75,13 @@ export default class SigninPage extends React.Component {
}
renderSubmitButton() {
const {action} = this.context;
const {action, t} = this.context;
let retry = false;
const isRunning = (action === 'signin:running');
let label = isRunning ? 'Sending login link...' : 'Continue';
let label = isRunning ? t('Sending login link...') : t('Continue');
const disabled = isRunning ? true : false;
if (action === 'signin:failed') {
label = 'Retry';
label = t('Retry');
retry = true;
}
return (
@ -97,17 +99,17 @@ export default class SigninPage extends React.Component {
}
renderSignupMessage() {
const brandColor = this.context.brandColor;
const {brandColor, t} = this.context;
return (
<div className='gh-portal-signup-message'>
<div>Don't have an account?</div>
<div>{t('Don\'t have an account?')}</div>
<button
data-test-button='signup-switch'
className='gh-portal-btn gh-portal-btn-link'
style={{color: brandColor}}
onClick={() => this.context.onAction('switchPage', {page: 'signup'})}
>
<span>Sign up</span>
<span>{t('Sign up')}</span>
</button>
</div>
);
@ -143,11 +145,12 @@ export default class SigninPage extends React.Component {
renderFormHeader() {
// const siteTitle = this.context.site.title || 'Site Title';
const {t} = this.context;
return (
<header className='gh-portal-signin-header'>
{this.renderSiteLogo()}
<h1 className="gh-portal-main-title">Sign in</h1>
<h1 className="gh-portal-main-title">{t('Sign in')}</h1>
</header>
);
}

View File

@ -361,7 +361,7 @@ class SignupPage extends React.Component {
}
getInputFields({state, fieldNames}) {
const {portal_name: portalName} = this.context.site;
const {site: {portal_name: portalName}, t} = this.context;
const errors = state.errors || {};
const fields = [
@ -369,7 +369,7 @@ class SignupPage extends React.Component {
type: 'email',
value: state.email,
placeholder: 'jamie@example.com',
label: 'Email',
label: t('Email'),
name: 'email',
required: true,
tabindex: 2,
@ -383,7 +383,7 @@ class SignupPage extends React.Component {
type: 'text',
value: state.name,
placeholder: 'Jamie Larson',
label: 'Name',
label: t('Name'),
name: 'name',
required: true,
tabindex: 1,
@ -400,29 +400,29 @@ class SignupPage extends React.Component {
}
renderSubmitButton() {
const {action, site, brandColor, pageQuery} = this.context;
const {action, site, brandColor, pageQuery, t} = this.context;
if (isInviteOnlySite({site, pageQuery})) {
return null;
}
let label = 'Continue';
let label = t('Continue');
const showOnlyFree = pageQuery === 'free' && hasFreeProductPrice({site});
if (hasOnlyFreePlan({site}) || showOnlyFree) {
label = 'Sign up';
label = t('Sign up');
} else {
return null;
}
let isRunning = false;
if (action === 'signup:running') {
label = 'Sending...';
label = t('Sending...');
isRunning = true;
}
let retry = false;
if (action === 'signup:failed') {
label = 'Retry';
label = t('Retry');
retry = true;
}
@ -456,11 +456,11 @@ class SignupPage extends React.Component {
}
renderFreeTrialMessage() {
const {site} = this.context;
const {site, t} = this.context;
if (hasFreeTrialTier({site}) && !isInviteOnlySite({site})) {
return (
<p className='gh-portal-free-trial-notification' data-testid="free-trial-notification-text">
After a free trial ends, you will be charged the regular price for the tier youve chosen. You can always cancel before then.
{t('After a free trial ends, you will be charged the regular price for the tier you\'ve chosen. You can always cancel before then.')}
</p>
);
}
@ -468,19 +468,19 @@ class SignupPage extends React.Component {
}
renderLoginMessage() {
const {brandColor, onAction} = this.context;
const {brandColor, onAction, t} = this.context;
return (
<div>
{this.renderFreeTrialMessage()}
<div className='gh-portal-signup-message'>
<div>Already a member?</div>
<div>{t('Already a member?')}</div>
<button
data-test-button='signin-switch'
className='gh-portal-btn gh-portal-btn-link'
style={{color: brandColor}}
onClick={() => onAction('switchPage', {page: 'signin'})}
>
<span>Sign in</span>
<span>{t('Sign in')}</span>
</button>
</div>
</div>
@ -489,7 +489,7 @@ class SignupPage extends React.Component {
renderForm() {
const fields = this.getInputFields({state: this.state});
const {site, pageQuery} = this.context;
const {site, pageQuery, t} = this.context;
if (this.state.showNewsletterSelection) {
return (
@ -512,7 +512,7 @@ class SignupPage extends React.Component {
className='gh-portal-invite-only-notification'
data-testid="invite-only-notification-text"
>
This site is invite-only, contact the owner for access.
{t('This site is invite-only, contact the owner for access.')}
</p>
{this.renderLoginMessage()}
</div>

View File

@ -41,7 +41,7 @@ async function updateMemberNewsletters({api, memberUuid, newsletters, enableComm
}
export default function UnsubscribePage() {
const {site, pageData, onAction} = useContext(AppContext);
const {site, pageData, onAction, t} = useContext(AppContext);
const api = setupGhostApi({siteUrl: site.url});
const [member, setMember] = useState();
const siteNewsletters = getSiteNewsletters({site});
@ -101,9 +101,9 @@ export default function UnsubscribePage() {
<div class="gh-feedback-icon gh-feedback-icon-error">
<WarningIcon />
</div>
<h1 className="gh-portal-main-title">That didn't go to plan</h1>
<h1 className="gh-portal-main-title">{t('That didn\'t go to plan')}</h1>
<div>
<p className="gh-portal-text-center">We couldn't unsubscribe you as the email address was not found. Please contact the site owner.</p>
<p className="gh-portal-text-center">{t('We couldn\'t unsubscribe you as the email address was not found. Please contact the site owner.')}</p>
</div>
<ActionButton
style={{width: '100%'}}
@ -111,7 +111,7 @@ export default function UnsubscribePage() {
onClick = {() => onAction('closePopup')}
disabled={false}
brandColor='#000000'
label={'Close'}
label={t('Close')}
isRunning={false}
tabindex='3'
classes={'sticky bottom'}
@ -126,7 +126,7 @@ export default function UnsubscribePage() {
<div className='gh-portal-content gh-portal-unsubscribe with-footer'>
<CloseButton />
<AccountHeader />
<h1 className="gh-portal-main-title">Successfully unsubscribed</h1>
<h1 className="gh-portal-main-title">{t('Successfully unsubscribed')}</h1>
<div>
<p className='gh-portal-text-center'><strong>{member?.email}</strong> will no longer receive this newsletter.</p>
<p className='gh-portal-text-center'>Didn't mean to do this? Manage your preferences
@ -182,7 +182,7 @@ export default function UnsubscribePage() {
setSubscribedNewsletters([]);
onAction('showPopupNotification', {
action: 'updated:success',
message: `Email preference updated.`
message: t(`Email preference updated.`)
});
const updatedMember = await api.member.updateNewsletters({uuid: pageData.uuid, newsletters: [], enableCommentNotifications: false});
setMember(updatedMember);