diff --git a/apps/portal/package.json b/apps/portal/package.json
index c646934366c..67ed91c8689 100644
--- a/apps/portal/package.json
+++ b/apps/portal/package.json
@@ -1,6 +1,6 @@
{
"name": "@tryghost/portal",
- "version": "2.67.11",
+ "version": "2.67.12",
"license": "MIT",
"repository": "https://github.com/TryGhost/Ghost",
"author": "Ghost Foundation",
diff --git a/apps/portal/src/actions.js b/apps/portal/src/actions.js
index 46d9e6b79a6..45d425b065d 100644
--- a/apps/portal/src/actions.js
+++ b/apps/portal/src/actions.js
@@ -1,6 +1,6 @@
import setupGhostApi from './utils/api';
import {chooseBestErrorMessage} from './utils/errors';
-import {createPopupNotification, getMemberEmail, getMemberName, getProductCadenceFromPrice, removePortalLinkFromUrl, getRefDomain} from './utils/helpers';
+import {createNotification, createPopupNotification, getMemberEmail, getMemberName, getProductCadenceFromPrice, removePortalLinkFromUrl, getRefDomain} from './utils/helpers';
import {t} from './utils/i18n';
function switchPage({data, state}) {
@@ -49,16 +49,35 @@ function closePopup({state}) {
};
}
-function openNotification({data}) {
+function openNotification({data, state}) {
+ const {
+ action = 'openNotification',
+ status = 'success',
+ autoHide = true,
+ closeable = true,
+ duration = 2600,
+ message = ''
+ } = data || {};
+
+ const notification = createNotification({
+ type: action,
+ status,
+ autoHide,
+ closeable,
+ duration,
+ state,
+ message
+ });
+
return {
- showNotification: true,
- ...data
+ notification,
+ notificationSequence: notification.count
};
}
function closeNotification() {
return {
- showNotification: false
+ notification: null
};
}
diff --git a/apps/portal/src/app.js b/apps/portal/src/app.js
index cf3212eba27..57984cbba1e 100644
--- a/apps/portal/src/app.js
+++ b/apps/portal/src/app.js
@@ -13,11 +13,24 @@ import {hasMode} from './utils/check-mode';
import {transformPortalAnchorToRelative} from './utils/transform-portal-anchor-to-relative';
import {getActivePage, isAccountPage, isOfferPage} from './pages';
import ActionHandler from './actions';
+import {getGiftRedemptionErrorMessage} from './utils/gift-redemption-notification';
import './app.css';
-import {hasRecommendations, hasGiftSubscriptions, createPopupNotification, hasAvailablePrices, getCurrencySymbol, getFirstpromoterId, getPriceIdFromPageQuery, getProductCadenceFromPrice, getProductFromId, getQueryPrice, getSiteDomain, isActiveOffer, isRetentionOffer, isComplimentaryMember, isInviteOnly, isPaidMember, isRecentMember, isSentryEventAllowed, removePortalLinkFromUrl} from './utils/helpers';
+import {hasRecommendations, hasGiftSubscriptions, createNotification, createPopupNotification, hasAvailablePrices, getCurrencySymbol, getFirstpromoterId, getPriceIdFromPageQuery, getProductCadenceFromPrice, getProductFromId, getQueryPrice, getSiteDomain, isActiveOffer, isRetentionOffer, isComplimentaryMember, isInviteOnly, isPaidMember, isRecentMember, isSentryEventAllowed, removePortalLinkFromUrl} from './utils/helpers';
import {validateHexColor} from './utils/sanitize-html';
import {handleDataAttributes} from './data-attributes';
+const safeDecodeURIComponent = (value) => {
+ try {
+ return decodeURIComponent(value);
+ } catch (error) {
+ return null;
+ }
+};
+
+const staleGiftRedemptionRequestResult = {
+ staleGiftRedemptionRequest: true
+};
+
const DEV_MODE_DATA = {
showPopup: true,
site: Fixtures.site,
@@ -59,10 +72,15 @@ export default class App extends React.Component {
actionErrorMessage: null,
initStatus: 'running',
lastPage: null,
+ notification: null,
+ notificationSequence: -1,
customSiteUrl: props.customSiteUrl,
locale: props.locale,
scrollbarWidth: 0
};
+
+ this._redemptionRequestId = 0;
+ this.currentRedemptionToken = null;
}
componentDidMount() {
@@ -161,17 +179,39 @@ export default class App extends React.Component {
/** Setup custom trigger buttons handling on page */
setupCustomTriggerButton() {
// Handler for custom buttons
- this.clickHandler = (event) => {
+ this.clickHandler = async (event) => {
event.preventDefault();
const target = event.currentTarget;
const pagePath = (target && target.dataset.portal);
- const {page, pageQuery, pageData} = this.getPageFromLinkPath(pagePath) || {};
+ const linkData = this.getPageFromLinkPath(pagePath);
+ if (!linkData) {
+ return;
+ }
+
+ const {page, pageQuery, pageData} = linkData;
if (this.state.initStatus === 'success') {
if (page === 'gift' && !hasGiftSubscriptions({site: this.state.site})) {
+ this.invalidateGiftRedemptionRequest();
removePortalLinkFromUrl();
return;
}
+ if (page === 'giftRedemption' && pageData?.token) {
+ const redemptionRequest = this.startGiftRedemptionRequest(pageData.token);
+ const giftLinkData = await this.fetchGiftRedemptionData({
+ site: this.state.site,
+ token: pageData.token
+ });
+
+ if (!this.isCurrentGiftRedemptionRequest(redemptionRequest)) {
+ return;
+ }
+
+ this.setState(giftLinkData);
+ return;
+ }
+
+ this.invalidateGiftRedemptionRequest();
if (pageQuery && pageQuery !== 'free') {
this.handleSignupQuery({site: this.state.site, pageQuery});
} else {
@@ -202,11 +242,30 @@ export default class App extends React.Component {
});
}
+ startGiftRedemptionRequest(token) {
+ this._redemptionRequestId += 1;
+ this.currentRedemptionToken = token;
+
+ return {
+ requestId: this._redemptionRequestId,
+ token
+ };
+ }
+
+ invalidateGiftRedemptionRequest() {
+ this._redemptionRequestId += 1;
+ this.currentRedemptionToken = null;
+ }
+
+ isCurrentGiftRedemptionRequest({requestId, token}) {
+ return this._redemptionRequestId === requestId && this.currentRedemptionToken === token;
+ }
+
/** Initialize portal setup on load, fetch data and setup state*/
async initSetup() {
try {
// Fetch data from API, links, preview, dev sources
- const {site, member, offers, page, showPopup, popupNotification, lastPage, pageQuery, pageData} = await this.fetchData();
+ const {site, member, offers, page, showPopup, popupNotification, notification, notificationSequence, lastPage, pageQuery, pageData} = await this.fetchData();
const i18nLanguage = this.props.siteI18nEnabled ? this.props.locale || site.locale || 'en' : 'en';
i18n.changeLanguage(i18nLanguage);
@@ -220,6 +279,8 @@ export default class App extends React.Component {
showPopup,
pageData,
popupNotification,
+ notification,
+ notificationSequence,
dir: i18n.dir() || 'ltr',
action: 'init:success',
initStatus: 'success',
@@ -266,7 +327,8 @@ export default class App extends React.Component {
async fetchData() {
const {site: apiSiteData, member, offers} = await this.fetchApiData();
const {site: devSiteData, ...restDevData} = this.fetchDevData();
- const {site: linkSiteData, ...restLinkData} = this.fetchLinkData(apiSiteData, member);
+ const linkData = await this.fetchLinkData(apiSiteData, member);
+ const {site: linkSiteData, ...restLinkData} = linkData?.staleGiftRedemptionRequest ? {} : linkData;
const {site: previewSiteData, ...restPreviewData} = this.fetchPreviewData();
const {site: notificationSiteData, ...restNotificationData} = this.fetchNotificationData();
let page = '';
@@ -492,7 +554,49 @@ export default class App extends React.Component {
}
/** Fetch state from Portal Links */
- fetchLinkData(site, member) {
+ async fetchGiftRedemptionData({site, token}) {
+ if (!hasGiftSubscriptions({site})) {
+ removePortalLinkFromUrl();
+
+ return {};
+ }
+
+ try {
+ const response = await this.GhostApi.gift.fetchRedemptionData({token});
+
+ return {
+ showPopup: true,
+ notification: null,
+ page: 'giftRedemption',
+ pageData: {
+ token,
+ gift: response?.gift || null
+ }
+ };
+ } catch (error) {
+ removePortalLinkFromUrl();
+
+ const notification = createNotification({
+ type: 'giftRedemption:failed',
+ status: 'error',
+ autoHide: false,
+ closeable: true,
+ state: this.state,
+ message: getGiftRedemptionErrorMessage(error)
+ });
+
+ return {
+ showPopup: false,
+ pageData: null,
+ notification,
+ notificationSequence: notification.count
+ };
+ }
+ }
+
+ async fetchLinkData(site, member) {
+ this.invalidateGiftRedemptionRequest();
+
const qParams = new URLSearchParams(window.location.search);
if (qParams.get('stripe') === 'gift-purchase-success') {
@@ -551,6 +655,7 @@ export default class App extends React.Component {
const productMonthlyPriceQueryRegex = /^(?:(\w+?))?\/monthly$/;
const productYearlyPriceQueryRegex = /^(?:(\w+?))?\/yearly$/;
const offersRegex = /^offers\/(\w+?)\/?$/;
+ const giftRedemptionRegex = /^\/portal\/gift\/redeem\/([^/?#]+)\/?$/;
const linkRegex = /^\/portal\/?(?:\/(\w+(?:\/\w+)*))?\/?$/;
const feedbackRegex = /^\/feedback\/(\w+?)\/(\w+?)\/?$/;
@@ -581,6 +686,25 @@ export default class App extends React.Component {
}
}
}
+ if (path && giftRedemptionRegex.test(path)) {
+ const [, token] = path.match(giftRedemptionRegex);
+ const decodedToken = safeDecodeURIComponent(token);
+ if (!decodedToken) {
+ return {};
+ }
+
+ const redemptionRequest = this.startGiftRedemptionRequest(decodedToken);
+ const giftLinkData = await this.fetchGiftRedemptionData({
+ site,
+ token: decodedToken
+ });
+
+ if (!this.isCurrentGiftRedemptionRequest(redemptionRequest)) {
+ return staleGiftRedemptionRequestResult;
+ }
+
+ return giftLinkData;
+ }
if (path && linkRegex.test(path)) {
const [,pagePath] = path.match(linkRegex);
const {page, pageQuery, pageData} = this.getPageFromLinkPath(pagePath, site) || {};
@@ -787,9 +911,14 @@ export default class App extends React.Component {
}
/**Handle state update for preview url and Portal Link changes */
- updateStateForPreviewLinks() {
+ async updateStateForPreviewLinks() {
const {site: previewSite, ...restPreviewData} = this.fetchPreviewData();
- const {site: linkSite, ...restLinkData} = this.fetchLinkData(this.state.site, this.state.member);
+ const linkData = await this.fetchLinkData(this.state.site, this.state.member);
+ if (linkData?.staleGiftRedemptionRequest) {
+ return;
+ }
+
+ const {site: linkSite, ...restLinkData} = linkData;
const updatedState = {
site: {
@@ -891,11 +1020,25 @@ export default class App extends React.Component {
const customMonthlyProductSignup = /^signup\/?(?:\/(\w+?))\/monthly\/?$/;
const customYearlyProductSignup = /^signup\/?(?:\/(\w+?))\/yearly\/?$/;
const customOfferRegex = /^offers\/(\w+?)\/?$/;
+ const giftRedemptionRegex = /^gift\/redeem\/([^/?#]+)\/?$/;
if (path === undefined || path === '') {
return {
page: 'default'
};
+ } else if (giftRedemptionRegex.test(path)) {
+ const [, token] = path.match(giftRedemptionRegex);
+ const decodedToken = safeDecodeURIComponent(token);
+ if (!decodedToken) {
+ return null;
+ }
+
+ return {
+ page: 'giftRedemption',
+ pageData: {
+ token: decodedToken
+ }
+ };
} else if (customOfferRegex.test(path)) {
return {
pageQuery: path
@@ -1082,7 +1225,7 @@ export default class App extends React.Component {
/**Get final App level context from App state*/
getContextFromState() {
- const {site, member, offers, action, actionErrorMessage, page, lastPage, showPopup, pageQuery, pageData, popupNotification, customSiteUrl, dir, scrollbarWidth, otcRef, inboxLinks} = this.state;
+ const {site, member, offers, action, actionErrorMessage, page, lastPage, showPopup, pageQuery, pageData, popupNotification, notification, customSiteUrl, dir, scrollbarWidth, otcRef, inboxLinks} = this.state;
const contextPage = this.getContextPage({site, page, member});
const contextMember = this.getContextMember({site, page: contextPage, member, offers, pageData, customSiteUrl});
return {
@@ -1099,6 +1242,7 @@ export default class App extends React.Component {
lastPage,
showPopup,
popupNotification,
+ notification,
customSiteUrl,
dir,
scrollbarWidth,
diff --git a/apps/portal/src/components/frame.styles.js b/apps/portal/src/components/frame.styles.js
index 3b0c658bad6..743422c0bfb 100644
--- a/apps/portal/src/components/frame.styles.js
+++ b/apps/portal/src/components/frame.styles.js
@@ -21,6 +21,7 @@ import EmailSuppressedPage from './pages/email-suppressed-page.css?inline';
import EmailSuppressionFAQ from './pages/email-suppression-faq.css?inline';
import EmailReceivingFAQ from './pages/email-receiving-faq.css?inline';
import {TipsAndDonationsSuccessStyle} from './pages/support-success';
+import {GiftRedemptionStyles} from './pages/gift-redemption-page';
import {GiftSuccessStyle} from './pages/gift-success-page';
import {TipsAndDonationsErrorStyle} from './pages/support-error';
import {RecommendationsPageStyles} from './pages/recommendations-page';
@@ -1313,6 +1314,7 @@ export function getFrameStyles({site}) {
EmailSuppressionFAQ +
EmailReceivingFAQ +
TipsAndDonationsSuccessStyle +
+ GiftRedemptionStyles +
TipsAndDonationsErrorStyle +
GiftSuccessStyle +
RecommendationsPageStyles +
diff --git a/apps/portal/src/components/notification.js b/apps/portal/src/components/notification.js
index 530e6868f44..226e8f17c4b 100644
--- a/apps/portal/src/components/notification.js
+++ b/apps/portal/src/components/notification.js
@@ -27,10 +27,26 @@ const Styles = () => {
};
};
-const NotificationText = ({type, status, context}) => {
+const NotificationText = ({type, status, message, context}) => {
const signinPortalLink = getPortalLink({page: 'signin', siteUrl: context.site.url});
const singupPortalLink = getPortalLink({page: 'signup', siteUrl: context.site.url});
+ if (message) {
+ if (typeof message === 'object') {
+ return (
+
+ {message.title ? {message.title} : null}
+ {message.title && message.subtitle ?
: null}
+ {message.subtitle || null}
+
+ );
+ }
+
+ return (
+ {message}
+ );
+ }
+
if (type === 'signin' && status === 'success' && context.member) {
const firstname = context.member.firstname || '';
return (
@@ -181,7 +197,7 @@ class NotificationContent extends React.Component {
}
render() {
- const {type, status} = this.props;
+ const {type, status, message} = this.props;
const {className = ''} = this.state;
const statusClass = status ? ` ${status}` : ' neutral';
const slideClass = className ? ` ${className}` : '';
@@ -189,7 +205,7 @@ class NotificationContent extends React.Component {
this.onAnimationEnd(e)}>
{(status === 'error' ? : )}
-
+
this.onNotificationClose(e)} />
@@ -207,15 +223,20 @@ export default class Notification extends React.Component {
active: true,
type,
status,
+ message: '',
autoHide,
duration,
- className: ''
+ className: '',
+ source: type && status ? 'url' : null,
+ notificationCount: null
};
}
componentDidMount() {
const {showPopup} = this.context;
- if (showPopup) {
+ if (this.context.notification) {
+ this.showNotification(this.context.notification, 'state');
+ } else if (showPopup) {
// Don't show a notification if there is a popup visible on page load
this.setState({
active: false
@@ -223,18 +244,49 @@ export default class Notification extends React.Component {
}
}
+ componentDidUpdate() {
+ const {notification} = this.context;
+
+ if (notification && notification.count !== this.state.notificationCount) {
+ this.showNotification(notification, 'state');
+ }
+ }
+
+ showNotification(notification, source) {
+ clearTimeout(this.timeoutId);
+
+ this.setState({
+ active: true,
+ className: '',
+ type: notification.type,
+ status: notification.status,
+ message: notification.message || '',
+ autoHide: notification.autoHide,
+ duration: notification.duration,
+ source,
+ notificationCount: notification.count || 0
+ });
+ }
+
onHideNotification() {
- const type = this.state.type;
- const deleteParams = [];
- if (['signin', 'signup'].includes(type)) {
- deleteParams.push('action', 'success');
- } else if (['stripe:checkout'].includes(type)) {
- deleteParams.push('stripe');
+ const {type, source} = this.state;
+
+ if (source === 'url') {
+ const deleteParams = [];
+ if (['signin', 'signup'].includes(type)) {
+ deleteParams.push('action', 'success');
+ } else if (['stripe:checkout'].includes(type)) {
+ deleteParams.push('stripe');
+ }
+ clearURLParams(deleteParams);
+ this.context.doAction('refreshMemberData');
+ } else if (source === 'state') {
+ this.context.doAction('closeNotification');
}
- clearURLParams(deleteParams);
- this.context.doAction('refreshMemberData');
+
this.setState({
- active: false
+ active: false,
+ source: null
});
}
@@ -256,11 +308,11 @@ export default class Notification extends React.Component {
if (!this.state.active) {
return null;
}
- const {type, status, autoHide, duration} = this.state;
+ const {type, status, message, autoHide, duration, notificationCount} = this.state;
if (type && status) {
return (
- this.onHideNotification(e)} />
+ this.onHideNotification(e)} />
);
}
diff --git a/apps/portal/src/components/pages/gift-redemption-page.js b/apps/portal/src/components/pages/gift-redemption-page.js
new file mode 100644
index 00000000000..6ba2858a3d0
--- /dev/null
+++ b/apps/portal/src/components/pages/gift-redemption-page.js
@@ -0,0 +1,323 @@
+import {useContext, useEffect, useState} from 'react';
+import AppContext from '../../app-context';
+import ActionButton from '../common/action-button';
+import CloseButton from '../common/close-button';
+import InputForm from '../common/input-form';
+import {ReactComponent as CheckmarkIcon} from '../../images/icons/checkmark.svg';
+import {ReactComponent as GiftIcon} from '../../images/icons/gift.svg';
+import {getGiftRedemptionErrorMessage} from '../../utils/gift-redemption-notification';
+import {t} from '../../utils/i18n';
+import {hasGiftSubscriptions, removePortalLinkFromUrl} from '../../utils/helpers';
+
+export const GiftRedemptionStyles = `
+ .gh-portal-popup-container.giftRedemption {
+ width: calc(100vw - 24px);
+ max-width: 452px;
+ padding: 0;
+ overflow: hidden;
+ }
+
+ .gh-portal-popup-container.giftRedemption .gh-portal-closeicon-container {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ z-index: 5;
+ }
+
+ html[dir="rtl"] .gh-portal-popup-container.giftRedemption .gh-portal-closeicon-container {
+ right: unset;
+ left: 18px;
+ }
+
+ .gh-portal-popup-container.giftRedemption .gh-portal-closeicon {
+ color: rgba(24, 32, 38, 0.14);
+ }
+
+ .gh-portal-popup-container.giftRedemption .gh-portal-closeicon:hover {
+ color: rgba(24, 32, 38, 0.28);
+ }
+
+ .gh-portal-gift-redemption {
+ overflow: hidden;
+ }
+
+ .gh-gift-redemption-panel {
+ position: relative;
+ background: var(--white);
+ }
+
+ .gh-gift-redemption-summary {
+ padding: 32px 32px 28px;
+ text-align: center;
+ background: #fff5f5;
+ border-bottom: 1px solid #f1e7e4;
+ }
+
+ .gh-gift-redemption-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 48px;
+ height: 48px;
+ color: var(--brandcolor);
+ }
+
+ .gh-gift-redemption-icon svg {
+ width: 40px;
+ height: 40px;
+ }
+
+ .gh-gift-redemption-kicker {
+ font-size: 1.15rem;
+ font-weight: 700;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--brandcolor);
+ }
+
+ .gh-gift-redemption-title {
+ max-width: none;
+ margin: 16px auto 0;
+ font-size: 2.25rem;
+ font-weight: 800;
+ line-height: 1.08;
+ letter-spacing: -0.03em;
+ white-space: nowrap;
+ color: var(--grey0);
+ }
+
+ .gh-gift-redemption-plan {
+ margin-top: 10px;
+ font-size: 1.65rem;
+ color: var(--grey2);
+ }
+
+ .gh-gift-redemption-tier {
+ font-weight: 700;
+ }
+
+ .gh-gift-redemption-cadence {
+ font-weight: 400;
+ }
+
+ .gh-gift-redemption-benefits {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-width: 302px;
+ margin: 20px auto 0;
+ }
+
+ .gh-gift-redemption-benefit {
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-start;
+ gap: 10px;
+ color: var(--grey2);
+ font-size: 1.45rem;
+ line-height: 1.35;
+ text-align: left;
+ }
+
+ .gh-gift-redemption-benefit svg {
+ width: 14px;
+ height: 14px;
+ margin-top: 2px;
+ color: var(--grey1);
+ flex-shrink: 0;
+ }
+
+ html[dir="rtl"] .gh-gift-redemption-benefit {
+ text-align: right;
+ flex-direction: row-reverse;
+ }
+
+ .gh-gift-redemption-benefit span {
+ display: block;
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .gh-gift-redemption-form {
+ padding: 22px 28px 28px;
+ background: var(--white);
+ }
+
+ .gh-gift-redemption-form .gh-portal-input-label {
+ margin-bottom: 5px;
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--grey1);
+ }
+
+ .gh-gift-redemption-form .gh-portal-input {
+ margin-bottom: 14px;
+ }
+
+ .gh-gift-redemption-submit {
+ width: 100%;
+ height: 44px;
+ margin-top: 22px;
+ font-size: 1.5rem;
+ font-weight: 600;
+ }
+
+ @media (max-width: 480px) {
+ .gh-gift-redemption-summary {
+ padding: 28px 24px 24px;
+ }
+
+ .gh-gift-redemption-title {
+ font-size: 2.1rem;
+ white-space: normal;
+ }
+
+ .gh-gift-redemption-benefit {
+ font-size: 1.4rem;
+ }
+
+ html[dir="rtl"] .gh-gift-redemption-benefit {
+ text-align: right;
+ }
+
+ .gh-gift-redemption-form {
+ padding: 20px 20px 22px;
+ }
+ }
+`;
+
+function getGiftCadenceLabel(gift) {
+ const {cadence, duration} = gift;
+
+ if (cadence === 'year') {
+ return duration === 1 ? t('1 year') : t('{years} years', {years: duration});
+ }
+
+ return duration === 1 ? t('1 month') : t('{months} months', {months: duration});
+}
+
+// TODO: Add translation strings once copy has been finalised
+const GiftRedemptionPage = () => {
+ const {brandColor, doAction, member, pageData, site} = useContext(AppContext);
+ const gift = pageData?.gift;
+ const giftSubscriptionsEnabled = hasGiftSubscriptions({site});
+ const [name, setName] = useState(member?.name || '');
+ const [email, setEmail] = useState(member?.email || '');
+
+ useEffect(() => {
+ setName(member?.name || '');
+ setEmail(member?.email || '');
+ }, [member?.email, member?.name]);
+
+ useEffect(() => {
+ if (giftSubscriptionsEnabled) {
+ return;
+ }
+
+ removePortalLinkFromUrl();
+ doAction('closePopup');
+ }, [doAction, giftSubscriptionsEnabled]);
+
+ useEffect(() => {
+ if (!giftSubscriptionsEnabled || gift) {
+ return;
+ }
+
+ doAction('openNotification', {
+ action: 'giftRedemption:failed',
+ status: 'error',
+ autoHide: false,
+ closeable: true,
+ message: getGiftRedemptionErrorMessage()
+ });
+ doAction('closePopup');
+ }, [doAction, gift, giftSubscriptionsEnabled]);
+
+ if (!giftSubscriptionsEnabled || !gift) {
+ return null;
+ }
+
+ const formFields = [
+ {
+ type: 'text',
+ value: name,
+ placeholder: t('Jamie Larson'),
+ label: t('Your name'),
+ name: 'name',
+ required: true,
+ tabIndex: 1,
+ autoFocus: true
+ },
+ {
+ type: 'email',
+ value: email,
+ placeholder: t('jamie@example.com'),
+ label: t('Your email'),
+ name: 'email',
+ required: true,
+ tabIndex: 2
+ }
+ ];
+
+ const handleFieldChange = (event, field) => {
+ if (field.name === 'name') {
+ setName(event.target.value);
+ }
+
+ if (field.name === 'email') {
+ setEmail(event.target.value);
+ }
+ };
+
+ const handleRedeemClick = (event) => {
+ event.preventDefault();
+ };
+
+ return (
+
+
+
+
+
+
+
{'Gift membership'}
+
{'You\'ve been gifted a membership'}
+
+
+ {gift.tier.name}
+ ·
+ {getGiftCadenceLabel(gift)}
+
+
+ {gift.tier.benefits.length > 0 && (
+
+ {gift.tier.benefits.map((benefit, index) => (
+
+
+ {benefit.name}
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default GiftRedemptionPage;
diff --git a/apps/portal/src/components/pages/gift-success-page.js b/apps/portal/src/components/pages/gift-success-page.js
index 88d4d192d04..956ac9bb397 100644
--- a/apps/portal/src/components/pages/gift-success-page.js
+++ b/apps/portal/src/components/pages/gift-success-page.js
@@ -112,7 +112,7 @@ const GiftSuccessPage = () => {
const token = pageData?.token;
const siteUrl = site?.url || '';
- const redeemUrl = `${siteUrl.replace(/\/$/, '')}/gift/${token}`;
+ const redeemUrl = `${siteUrl.replace(/\/$/, '')}/#/portal/gift/redeem/${token}`;
const handleCopy = () => {
copyTextToClipboard(redeemUrl);
diff --git a/apps/portal/src/pages.js b/apps/portal/src/pages.js
index ab85fe3722a..90bfc8151a6 100644
--- a/apps/portal/src/pages.js
+++ b/apps/portal/src/pages.js
@@ -18,6 +18,7 @@ import SupportSuccess from './components/pages/support-success';
import SupportError from './components/pages/support-error';
import RecommendationsPage from './components/pages/recommendations-page';
import GiftPage from './components/pages/gift-page';
+import GiftRedemptionPage from './components/pages/gift-redemption-page';
import GiftSuccessPage from './components/pages/gift-success-page';
/** List of all available pages in Portal, mapped to their UI component
@@ -44,6 +45,7 @@ const Pages = {
supportError: SupportError,
recommendations: RecommendationsPage,
gift: GiftPage,
+ giftRedemption: GiftRedemptionPage,
giftSuccess: GiftSuccessPage
};
diff --git a/apps/portal/src/utils/api.js b/apps/portal/src/utils/api.js
index 2890c78bbac..67374e2e376 100644
--- a/apps/portal/src/utils/api.js
+++ b/apps/portal/src/utils/api.js
@@ -167,6 +167,68 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) {
}
};
+ api.gift = {
+ async fetchRedemptionData({token}) {
+ // Temporary: Mocked API response
+ return {
+ gift: {
+ token,
+ cadence: 'year',
+ duration: 1,
+ currency: 'EUR',
+ amount: 4000,
+ expires_at: '2027-04-06T10:09:30.000Z',
+ tier: {
+ id: '00000000703e0fb16598bca1',
+ name: 'Ultra',
+ description: 'Everything in Premium, but fancier',
+ benefits: [
+ {
+ id: '69d39d43acb2b803745058a1',
+ name: 'Weekly round-up on Sunday'
+ },
+ {
+ id: '69d39d43acb2b803745058a2',
+ name: 'Access to all podcasts and videos'
+ },
+ {
+ id: '69d39d43acb2b803745058a3',
+ name: 'Five new stories per week'
+ }
+ ]
+ }
+ }
+ };
+
+ // Test out error cases:
+ // throw new HumanReadableError('This gift has already been redeemed.');
+ // throw new HumanReadableError('This gift has expired.');
+ // throw new HumanReadableError('This gift link is not valid.');
+
+ // TODO: Restore actual API code when ready to integrate
+ // const url = endpointFor({type: 'members', resource: `gifts/${encodeURIComponent(token)}`});
+ // const res = await makeRequest({
+ // url,
+ // method: 'GET',
+ // headers: {
+ // 'Content-Type': 'application/json'
+ // },
+ // credentials: 'same-origin'
+ // });
+
+ // if (res.ok) {
+ // return res.json();
+ // }
+
+ // const humanError = await HumanReadableError.fromApiResponse(res);
+ // if (humanError) {
+ // throw humanError;
+ // }
+
+ // throw new Error('Failed to load gift data');
+ }
+ };
+
api.recommendations = {
trackClicked({recommendationId}) {
let url = endpointFor({type: 'members', resource: 'recommendations/' + recommendationId + '/clicked'});
diff --git a/apps/portal/src/utils/gift-redemption-notification.js b/apps/portal/src/utils/gift-redemption-notification.js
new file mode 100644
index 00000000000..d358f87bd0d
--- /dev/null
+++ b/apps/portal/src/utils/gift-redemption-notification.js
@@ -0,0 +1,15 @@
+// TODO: Add translation strings once copy has been finalise
+
+const GIFT_REDEMPTION_ERROR_TITLE = 'Gift could not be redeemed';
+const INVALID_GIFT_LINK_MESSAGE = 'Gift link is not valid';
+
+export function getGiftRedemptionErrorMessage(error) {
+ const subtitle = error?.message && error.message !== 'Failed to load gift data'
+ ? error.message
+ : INVALID_GIFT_LINK_MESSAGE;
+
+ return {
+ title: GIFT_REDEMPTION_ERROR_TITLE,
+ subtitle
+ };
+}
diff --git a/apps/portal/src/utils/helpers.js b/apps/portal/src/utils/helpers.js
index d313622d94b..090e1b1bc8e 100644
--- a/apps/portal/src/utils/helpers.js
+++ b/apps/portal/src/utils/helpers.js
@@ -798,6 +798,23 @@ export const createPopupNotification = ({type, status, autoHide, duration = 2600
};
};
+export const createNotification = ({type, status, autoHide, duration = 2600, closeable, state, message}) => {
+ const previousCount = Number.isInteger(state?.notificationSequence)
+ ? state.notificationSequence
+ : state?.notification?.count;
+ const count = Number.isInteger(previousCount) ? previousCount + 1 : 0;
+
+ return {
+ type,
+ status,
+ autoHide,
+ closeable,
+ duration,
+ message,
+ count
+ };
+};
+
export function isSameCurrency(currency1, currency2) {
return currency1?.toLowerCase() === currency2?.toLowerCase();
}
@@ -1035,4 +1052,4 @@ export function translateCadence(cadence) {
return t('year');
}
return cadence;
-}
\ No newline at end of file
+}
diff --git a/apps/portal/test/actions.test.ts b/apps/portal/test/actions.test.ts
index d246826f8a3..7b29ba7258d 100644
--- a/apps/portal/test/actions.test.ts
+++ b/apps/portal/test/actions.test.ts
@@ -88,6 +88,59 @@ describe('startSigninOTCFromCustomForm action', () => {
});
});
+describe('notification actions', () => {
+ test('increments notification count after a notification is dismissed', async () => {
+ const firstNotification = await ActionHandler({
+ action: 'openNotification',
+ data: {
+ action: 'giftRedemption:failed',
+ status: 'error',
+ autoHide: false,
+ message: 'Gift could not be redeemed'
+ },
+ state: {
+ notification: null,
+ notificationSequence: -1
+ },
+ api: {}
+ });
+
+ expect(firstNotification.notification.count).toBe(0);
+ expect(firstNotification.notificationSequence).toBe(0);
+
+ const dismissedNotification = await ActionHandler({
+ action: 'closeNotification',
+ data: {},
+ state: {
+ ...firstNotification
+ },
+ api: {}
+ });
+
+ expect(dismissedNotification).toEqual({
+ notification: null
+ });
+
+ const secondNotification = await ActionHandler({
+ action: 'openNotification',
+ data: {
+ action: 'giftRedemption:failed',
+ status: 'error',
+ autoHide: false,
+ message: 'Gift could not be redeemed'
+ },
+ state: {
+ ...firstNotification,
+ ...dismissedNotification
+ },
+ api: {}
+ });
+
+ expect(secondNotification.notification.count).toBe(1);
+ expect(secondNotification.notificationSequence).toBe(1);
+ });
+});
+
describe('continueSubscription action', () => {
test('returns reloadOnPopupClose on success', async () => {
const mockApi = {
diff --git a/apps/portal/test/app.test.js b/apps/portal/test/app.test.js
index 9cd0e51a8e2..eb4ed13de99 100644
--- a/apps/portal/test/app.test.js
+++ b/apps/portal/test/app.test.js
@@ -15,6 +15,18 @@ vi.mock('../src/utils/i18n', () => ({
t: vi.fn(str => str)
}));
+const createDeferred = () => {
+ let resolve;
+ const promise = new Promise((res) => {
+ resolve = res;
+ });
+
+ return {
+ promise,
+ resolve
+ };
+};
+
describe('App', function () {
beforeEach(function () {
// Stub window.location with a URL object so we have an expected origin
@@ -115,6 +127,140 @@ describe('App', function () {
expect(window.location.reload).not.toHaveBeenCalled();
});
+ test('ignores malformed gift redemption tokens in hash links', async () => {
+ window.location.hash = '#/portal/gift/redeem/%E0%A4%A';
+
+ const app = new App({siteUrl: 'http://example.com'});
+ app.fetchGiftRedemptionData = vi.fn();
+
+ const result = await app.fetchLinkData(FixtureSite.singleTier.basic, FixtureMember.free);
+
+ expect(result).toEqual({});
+ expect(app.fetchGiftRedemptionData).not.toHaveBeenCalled();
+ });
+
+ test('ignores malformed gift redemption tokens in trigger links', async () => {
+ const app = new App({siteUrl: 'http://example.com'});
+ app.dispatchAction = vi.fn();
+ app.fetchGiftRedemptionData = vi.fn();
+ app.state = {
+ ...app.state,
+ initStatus: 'success',
+ site: {...FixtureSite.singleTier.basic, labs: {giftSubscriptions: true}}
+ };
+
+ await app.clickHandler({
+ preventDefault: vi.fn(),
+ currentTarget: {
+ dataset: {
+ portal: 'gift/redeem/%E0%A4%A'
+ }
+ }
+ });
+
+ expect(app.fetchGiftRedemptionData).not.toHaveBeenCalled();
+ expect(app.dispatchAction).not.toHaveBeenCalled();
+ });
+
+ test('drops stale custom-trigger gift redemption responses', async () => {
+ const app = new App({siteUrl: 'http://example.com'});
+ const firstRequest = createDeferred();
+ const secondRequest = createDeferred();
+
+ app.setState = vi.fn((updatedState) => {
+ app.state = {...app.state, ...updatedState};
+ });
+ app.state = {
+ ...app.state,
+ initStatus: 'success',
+ site: {...FixtureSite.singleTier.basic, labs: {giftSubscriptions: true}}
+ };
+ app.fetchGiftRedemptionData = vi.fn(({token}) => {
+ return token === 'first-token' ? firstRequest.promise : secondRequest.promise;
+ });
+
+ const firstClick = app.clickHandler({
+ preventDefault: vi.fn(),
+ currentTarget: {
+ dataset: {
+ portal: 'gift/redeem/first-token'
+ }
+ }
+ });
+ const secondClick = app.clickHandler({
+ preventDefault: vi.fn(),
+ currentTarget: {
+ dataset: {
+ portal: 'gift/redeem/second-token'
+ }
+ }
+ });
+
+ secondRequest.resolve({
+ page: 'giftRedemption',
+ pageData: {
+ token: 'second-token'
+ }
+ });
+ await secondClick;
+
+ firstRequest.resolve({
+ page: 'giftRedemption',
+ pageData: {
+ token: 'first-token'
+ }
+ });
+ await firstClick;
+
+ expect(app.setState).toHaveBeenCalledTimes(1);
+ expect(app.state.pageData.token).toBe('second-token');
+ });
+
+ test('drops stale hashchange gift redemption responses', async () => {
+ const app = new App({siteUrl: 'http://example.com'});
+ const firstRequest = createDeferred();
+ const secondRequest = createDeferred();
+
+ app.setState = vi.fn((updatedState) => {
+ app.state = {...app.state, ...updatedState};
+ });
+ app.state = {
+ ...app.state,
+ site: {...FixtureSite.singleTier.basic, labs: {giftSubscriptions: true}},
+ member: FixtureMember.free
+ };
+ app.fetchGiftRedemptionData = vi.fn(({token}) => {
+ return token === 'first-token' ? firstRequest.promise : secondRequest.promise;
+ });
+
+ window.location.hash = '#/portal/gift/redeem/first-token';
+ const firstUpdate = app.updateStateForPreviewLinks();
+
+ window.location.hash = '#/portal/gift/redeem/second-token';
+ const secondUpdate = app.updateStateForPreviewLinks();
+
+ secondRequest.resolve({
+ showPopup: true,
+ page: 'giftRedemption',
+ pageData: {
+ token: 'second-token'
+ }
+ });
+ await secondUpdate;
+
+ firstRequest.resolve({
+ showPopup: true,
+ page: 'giftRedemption',
+ pageData: {
+ token: 'first-token'
+ }
+ });
+ await firstUpdate;
+
+ expect(app.state.pageData.token).toBe('second-token');
+ expect(app.setState).toHaveBeenCalledTimes(1);
+ });
+
test('parses retention offer preview query data into account cancellation flow', () => {
const app = new App({siteUrl: 'http://example.com'});
const previewData = app.fetchOfferQueryStrData('redemption_type=retention&display_title=Before%2520you%2520go&display_description=Please%2520stay&type=percent&amount=100&duration=repeating&duration_in_months=2&cadence=month&tier_id=product_123&enabled=false');
diff --git a/apps/portal/test/portal-links.test.js b/apps/portal/test/portal-links.test.js
index 6214a062176..64d6319c106 100644
--- a/apps/portal/test/portal-links.test.js
+++ b/apps/portal/test/portal-links.test.js
@@ -3,7 +3,23 @@ import {site as FixtureSite, member as FixtureMember} from './utils/test-fixture
import {appRender, fireEvent, waitFor, within} from './utils/test-utils';
import setupGhostApi from '../src/utils/api';
-const setup = async ({site, member = null, showPopup = true}) => {
+const defaultGiftResponse = {
+ gift: {
+ token: 'gift-token-123',
+ cadence: 'year',
+ duration: 1,
+ tier: {
+ id: 'tier-gift',
+ name: 'Bronze',
+ benefits: [
+ {id: 'benefit-1', name: 'Five great stories to read every day'},
+ {id: 'benefit-2', name: 'Videos and podcasts to charm and delight you'}
+ ]
+ }
+ }
+};
+
+const setup = async ({site, member = null, showPopup = true, giftResponse = defaultGiftResponse, giftError = null}) => {
const ghostApi = setupGhostApi({siteUrl: 'https://example.com'});
ghostApi.init = vi.fn(() => {
@@ -25,6 +41,14 @@ const setup = async ({site, member = null, showPopup = true}) => {
return Promise.resolve();
});
+ ghostApi.gift.fetchRedemptionData = vi.fn(() => {
+ if (giftError) {
+ return Promise.reject(giftError);
+ }
+
+ return Promise.resolve(giftResponse);
+ });
+
const utils = appRender(
);
@@ -379,6 +403,120 @@ describe('Portal Data links:', () => {
});
});
+ describe('#/portal/gift/redeem/', () => {
+ const giftRedemptionHash = '#/portal/gift/redeem/gift-token-123';
+
+ const setupGiftRedemption = async ({giftError = null, giftResponse = defaultGiftResponse} = {}) => {
+ window.location.hash = giftRedemptionHash;
+
+ return setup({
+ site: {...FixtureSite.singleTier.basic, labs: {giftSubscriptions: true}},
+ member: FixtureMember.free,
+ showPopup: false,
+ giftError,
+ giftResponse
+ });
+ };
+
+ const expectGiftRedemptionErrorToast = async ({utils, subtitle}) => {
+ const notificationFrame = await utils.findByTitle(/portal-notification/i);
+ expect(notificationFrame).toBeInTheDocument();
+ expect(utils.queryByTitle(/portal-popup/i)).not.toBeInTheDocument();
+
+ const notificationIframeDocument = notificationFrame.contentDocument;
+ expect(await within(notificationIframeDocument).findByText(/Gift could not be redeemed/i)).toBeInTheDocument();
+ expect(within(notificationIframeDocument).queryByText(subtitle)).toBeInTheDocument();
+ };
+
+ test('renders a toast error when gift has expired', async () => {
+ let {
+ ghostApi, triggerButtonFrame, ...utils
+ } = await setupGiftRedemption({
+ giftError: new Error('This gift has expired.')
+ });
+
+ expect(triggerButtonFrame).toBeInTheDocument();
+ expect(ghostApi.gift.fetchRedemptionData).toHaveBeenCalledWith({token: 'gift-token-123'});
+
+ await expectGiftRedemptionErrorToast({
+ utils,
+ subtitle: /This gift has expired\./i
+ });
+ });
+
+ test('renders a toast error when gift has already been redeemed', async () => {
+ let {
+ ghostApi, triggerButtonFrame, ...utils
+ } = await setupGiftRedemption({
+ giftError: new Error('This gift has already been redeemed.')
+ });
+
+ expect(triggerButtonFrame).toBeInTheDocument();
+ expect(ghostApi.gift.fetchRedemptionData).toHaveBeenCalledWith({token: 'gift-token-123'});
+
+ await expectGiftRedemptionErrorToast({
+ utils,
+ subtitle: /This gift has already been redeemed\./i
+ });
+ });
+
+ test('renders a toast error when gift link is invalid', async () => {
+ let {
+ ghostApi, triggerButtonFrame, ...utils
+ } = await setupGiftRedemption({
+ giftError: new Error('Failed to load gift data')
+ });
+
+ expect(triggerButtonFrame).toBeInTheDocument();
+ expect(ghostApi.gift.fetchRedemptionData).toHaveBeenCalledWith({token: 'gift-token-123'});
+
+ await expectGiftRedemptionErrorToast({
+ utils,
+ subtitle: /Gift link is not valid/i
+ });
+ });
+
+ test('renders gift redemption popup when gift is valid', async () => {
+ let {
+ ghostApi, popupFrame, triggerButtonFrame, ...utils
+ } = await setupGiftRedemption();
+
+ expect(triggerButtonFrame).toBeInTheDocument();
+
+ popupFrame = await utils.findByTitle(/portal-popup/i);
+ expect(popupFrame).toBeInTheDocument();
+
+ const popupIframeDocument = popupFrame.contentDocument;
+ expect(await within(popupIframeDocument).findByText(/You've been gifted a membership/i)).toBeInTheDocument();
+ expect(within(popupIframeDocument).queryByText(/Bronze/i)).toBeInTheDocument();
+ expect(within(popupIframeDocument).queryByText(/1 year/i)).toBeInTheDocument();
+ expect(within(popupIframeDocument).queryByText(/Five great stories to read every day/i)).toBeInTheDocument();
+
+ const nameInput = within(popupIframeDocument).getByLabelText(/your name/i);
+ const emailInput = within(popupIframeDocument).getByLabelText(/your email/i);
+
+ expect(nameInput).toHaveValue('Jamie Larson');
+ expect(emailInput).toHaveValue('jamie@example.com');
+ expect(ghostApi.gift.fetchRedemptionData).toHaveBeenCalledWith({token: 'gift-token-123'});
+ });
+
+ // TODO for GA: Remove test
+ test('does not open when giftSubscriptions labs flag is disabled', async () => {
+ window.location.hash = '#/portal/gift/redeem/gift-token-123';
+
+ let {
+ ghostApi, popupFrame, triggerButtonFrame
+ } = await setup({
+ site: {...FixtureSite.singleTier.basic, labs: {}},
+ showPopup: false
+ });
+
+ expect(triggerButtonFrame).toBeInTheDocument();
+ expect(popupFrame).not.toBeInTheDocument();
+ expect(ghostApi.gift.fetchRedemptionData).not.toHaveBeenCalled();
+ });
+ });
+
describe('?stripe=gift-purchase-success', () => {
test('opens gift success page when giftSubscriptions labs flag is enabled', async () => {
window.location.href = 'https://portal.localhost/?stripe=gift-purchase-success&gift_token=abc123';
@@ -401,7 +539,7 @@ describe('Portal Data links:', () => {
const giftTitle = within(popupFrame.contentDocument).queryByText(/gift ready to share/i);
expect(giftTitle).toBeInTheDocument();
- const redeemUrl = within(popupFrame.contentDocument).queryByText(/\/gift\/abc123/);
+ const redeemUrl = within(popupFrame.contentDocument).queryByText(/#\/portal\/gift\/redeem\/abc123/);
expect(redeemUrl).toBeInTheDocument();
});
diff --git a/apps/portal/test/unit/components/notification.test.js b/apps/portal/test/unit/components/notification.test.js
new file mode 100644
index 00000000000..ff827267d08
--- /dev/null
+++ b/apps/portal/test/unit/components/notification.test.js
@@ -0,0 +1,88 @@
+import {act, render, waitFor} from '@testing-library/react';
+import {vi} from 'vitest';
+import Notification from '../../../src/components/notification';
+import AppContext from '../../../src/app-context';
+
+vi.mock('../../../src/components/frame', () => ({
+ default: ({children}) => {children}
+}));
+
+vi.mock('../../../src/utils/notifications', () => ({
+ default: vi.fn(() => null),
+ clearURLParams: vi.fn()
+}));
+
+describe('Notification', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ vi.clearAllMocks();
+ });
+
+ test('remounts notification content when notification count changes', async () => {
+ const doAction = vi.fn();
+ const site = {
+ url: 'https://example.com',
+ title: 'Example Site'
+ };
+
+ const {container, getByText, rerender} = render(
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(getByText('First notification')).toBeInTheDocument();
+ });
+
+ rerender(
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(getByText('Second notification')).toBeInTheDocument();
+ });
+
+ await act(async () => {
+ vi.advanceTimersByTime(150);
+ });
+
+ expect(container.querySelector('.gh-portal-notification')).not.toHaveClass('slideout');
+ });
+});
diff --git a/ghost/i18n/locales/af/portal.json b/ghost/i18n/locales/af/portal.json
index 3a2a757a247..a0ffa190b06 100644
--- a/ghost/i18n/locales/af/portal.json
+++ b/ghost/i18n/locales/af/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dae gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Rekening",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "U het suksesvol aangemeld.",
"You've successfully subscribed to {siteTitle}": "Jy het suksesvol ingeteken op {siteTitle}",
"Your account": "U rekening",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "U insette help om te bepaal wat gepubliseer word.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "U intekening sal verval op {expiryDate}",
"Your subscription will renew on {renewalDate}": "U intekening sal hernu op {renewalDate}",
diff --git a/ghost/i18n/locales/ar/portal.json b/ghost/i18n/locales/ar/portal.json
index 3ca4356c1c0..9899131e7f4 100644
--- a/ghost/i18n/locales/ar/portal.json
+++ b/ghost/i18n/locales/ar/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} أيام مجانية",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "الحساب",
"Account details updated successfully": ".تم تحديث تفاصيل الحساب بنجاح",
@@ -244,9 +246,11 @@
"You've successfully signed in.": ".تم تسجيل الدخول بنجاح",
"You've successfully subscribed to {siteTitle}": "تم الاشتراك بنجاح في {siteTitle}",
"Your account": "حسابك",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "بريدك الاكتروني لم ينجح في إعادة الاشتراك، رجاء المحاولة مرة أخرى",
"your inbox": "بريدك الوارد",
"Your input helps shape what gets published.": "آرائك تساهم في تحسين ما ينشر.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "{expiryDate} اشتراكك سوف ينتهي في",
"Your subscription will renew on {renewalDate}": "{renewalDate} اشتراكك سوف يتجدد في",
diff --git a/ghost/i18n/locales/bg/portal.json b/ghost/i18n/locales/bg/portal.json
index 702b056d13b..bb64d839d5d 100644
--- a/ghost/i18n/locales/bg/portal.json
+++ b/ghost/i18n/locales/bg/portal.json
@@ -12,9 +12,11 @@
"{months} months": "{months} месеца",
"{months} months free": "{months} месеца безплатно",
"{trialDays} days free": "{trialDays} дни безплатно",
+ "{years} years": "",
"+1 (123) 456-7890": "+359 88 123-4567",
"1 month": "1 месец",
"1 month free": "1 месец безплатно",
+ "1 year": "",
"Access your RSS feeds": "Достъп до вашите RSS емисии",
"Account": "Профил",
"Account details updated successfully": "Настройките бяха успешно обновени",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Влязохте успешно.",
"You've successfully subscribed to {siteTitle}": "Успешно се абонирахте за {siteTitle}",
"Your account": "Вашият профил",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Неуспешно подновяване на абонамент с този имейл, опитайте отново",
"your inbox": "вашия имейл",
"Your input helps shape what gets published.": "Вашият принос помага за създаването на съдържанието.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "Абонаментът ви е прекратен и ще изтече на {expiryDate}.",
"Your subscription will expire on {expiryDate}": "Абонаментът ви ще изтече на {expiryDate}",
"Your subscription will renew on {renewalDate}": "Абонаментът ви ще се поднови на {renewalDate}",
diff --git a/ghost/i18n/locales/bn/portal.json b/ghost/i18n/locales/bn/portal.json
index 30ccdc82b58..4e62ad20f60 100644
--- a/ghost/i18n/locales/bn/portal.json
+++ b/ghost/i18n/locales/bn/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} দিন ফ্রি",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "অ্যাকাউন্ট",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "আপনি সফলভাবে সাইন ইন করেছেন।",
"You've successfully subscribed to {siteTitle}": "আপনি সফলভাবে সাবস্ক্রাইব করেছেন {siteTitle}",
"Your account": "আপনার অ্যাকাউন্ট",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "আপনার ইনপুট প্রকাশিত হওয়ার বিষয়টি নির্ধারণ করতে সহায়তা করে।",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "আপনার সাবস্ক্রিপশনের মেয়াদ {expiryDate} এ শেষ হবে",
"Your subscription will renew on {renewalDate}": "আপনার সাবস্ক্রিপশন {renewalDate} এ নবায়ন হবে",
diff --git a/ghost/i18n/locales/bs/portal.json b/ghost/i18n/locales/bs/portal.json
index 0364d48ccc6..c73d7d2aa30 100644
--- a/ghost/i18n/locales/bs/portal.json
+++ b/ghost/i18n/locales/bs/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dana besplatno",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Račun",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Uspješna prijava.",
"You've successfully subscribed to {siteTitle}": "Uspješna pretplata na {siteTitle}",
"Your account": "Tvoj račun",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Tvoj feedback pomaže pri odabiru tema koje se objavljuju.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Tvoja pretplata istieče {expiryDate}",
"Your subscription will renew on {renewalDate}": "Tvoja pretplata će se obnoviti {renewalDate}",
diff --git a/ghost/i18n/locales/ca/portal.json b/ghost/i18n/locales/ca/portal.json
index 50c687332be..6f390b73f57 100644
--- a/ghost/i18n/locales/ca/portal.json
+++ b/ghost/i18n/locales/ca/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dies de prova",
+ "{years} years": "",
"+1 (123) 456-7890": "+34 123 456 789",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Compte",
"Account details updated successfully": "Detalls del compte actualitzats correctament",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Has iniciat la sessió correctament.",
"You've successfully subscribed to {siteTitle}": "T'has subscrit correctament a {siteTitle}",
"Your account": "El teu compte",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "La teva adreça de correu no s'ha pogut tornar a subscriure - torna-ho a intentar",
"your inbox": "",
"Your input helps shape what gets published.": "La teva opinió ajuda a definir què es publica.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "La teva subscripció caducarà el {expiryDate}",
"Your subscription will renew on {renewalDate}": "La teva subscripció es renovarà el {renewalDate}",
diff --git a/ghost/i18n/locales/context.json b/ghost/i18n/locales/context.json
index 22501ef1996..407669745d7 100644
--- a/ghost/i18n/locales/context.json
+++ b/ghost/i18n/locales/context.json
@@ -2,8 +2,9 @@
"(save {highestYearlyDiscount}%)": "Appears in portal next to the yearly plan selector",
"+1 (123) 456-7890": "Placeholder for phone number input field",
"1 comment": "Comment count displayed above the comments section in case there is only one",
- "1 month": "Internal helper value passed as '{amountOff}' in Portal retention-offer logic for one-month free offers",
- "1 month free": "Discount badge shown in Portal retention offer section when cancelling a subscription",
+ "1 month": "Duration label for a 1 month subscription",
+ "1 month free": "Retention offer discount badge shown during subscription cancellation",
+ "1 year": "Duration label for a 1 year subscription",
"Access your RSS feeds": "Default description text for the Transistor podcast integration in Portal",
"Account": "A label in Portal for your account area",
"Account details updated successfully": "Popover message in Portal",
@@ -365,10 +366,12 @@
"You've successfully signed in.": "A notification displayed when the user signs in",
"You've successfully subscribed to {siteTitle}": "Notification displayed after a successful signup. The ... tag wraps the site name. {siteTitle} is the name of the publication",
"Your account": "A label indicating member account details",
+ "Your email": "Form label for the email address input in e.g. signup forms",
"Your email address": "Placeholder text in an input field",
"Your email has failed to resubscribe, please try again": "error message in portal",
"Your free trial ends on {date}, at which time you will be charged the regular price. You can always cancel before then.": "Newsletter text - shown to trialing members when receiving the newsletter (if subscription status is being shown)",
"Your input helps shape what gets published.": "Descriptive text displayed on the member feedback UI, telling people how their feedback is used",
+ "Your name": "Form label for the name input in e.g. signup forms",
"Your request will be sent to the owner of this site.": "Descriptive text displayed in the report comment modal",
"Your subscription has been canceled and will expire on {date}. You can resume your subscription via your account settings.": "Shown in the newsletter footer when a member has cancelled their subscription but it hasn't expired yet.",
"Your subscription has been canceled and will expire on {expiryDate}.": "Banner message shown on Portal account page when a subscription is canceled but still active until the period end",
@@ -404,7 +407,8 @@
"{memberEmail} will no longer receive emails when someone replies to your comments.": "Shown when a member unsubscribes from comment replies",
"{memberEmail} will no longer receive this newsletter.": "Shown when a member unsubscribes from a newsletter",
"{memberEmail} will no longer receive {newsletterName} newsletter.": "Shown when a member unsubscribes from a newsletter",
- "{months} months": "Internal helper value passed as '{amountOff}' in Portal retention-offer logic for multi-month free offers",
- "{months} months free": "Discount badge in Portal retention offer section for multi-month free offers",
- "{trialDays} days free": "Portal - label for a free trial that lasts a specific number of days"
+ "{months} months": "Duration label for a multiple-month subscription",
+ "{months} months free": "Retention offer discount badge shown during subscription cancellation",
+ "{trialDays} days free": "Portal - label for a free trial that lasts a specific number of days",
+ "{years} years": "Duration label for a multiple-year subscription"
}
\ No newline at end of file
diff --git a/ghost/i18n/locales/cs/portal.json b/ghost/i18n/locales/cs/portal.json
index bf780b5d13f..76f932125bb 100644
--- a/ghost/i18n/locales/cs/portal.json
+++ b/ghost/i18n/locales/cs/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dní zdarma",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Účet",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Úspěšně jste se přihlásili.",
"You've successfully subscribed to {siteTitle}": "Úspěšně jste se přihlásili k odběru {siteTitle}",
"Your account": "Váš účet",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Vaše připomínky pomáhají ladit a tvořit obsah webu.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Vaše předplatné vyprší {expiryDate}",
"Your subscription will renew on {renewalDate}": "Vaše předplatné se obnoví {renewalDate}",
diff --git a/ghost/i18n/locales/da/portal.json b/ghost/i18n/locales/da/portal.json
index 1f88c9dc1fd..2e8962056e8 100644
--- a/ghost/i18n/locales/da/portal.json
+++ b/ghost/i18n/locales/da/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dage gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "+45 12 34 56 78",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "Kontooplysningerne blev opdateret",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Du er nu logget ind.",
"You've successfully subscribed to {siteTitle}": "Du er nu tilmeldt til {siteTitle}",
"Your account": "Din konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Din e-mail kunne ikke genabonneres, prøv venligst igen.",
"your inbox": "",
"Your input helps shape what gets published.": "Dit input hjælper med at forme det der bliver publiceret.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Dit abonnement udløber {expiryDate}",
"Your subscription will renew on {renewalDate}": "Dit abonnement fornyes {renewalDate}",
diff --git a/ghost/i18n/locales/de-CH/portal.json b/ghost/i18n/locales/de-CH/portal.json
index ffdb33864d8..db0cdd40731 100644
--- a/ghost/i18n/locales/de-CH/portal.json
+++ b/ghost/i18n/locales/de-CH/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} Tage kostenfrei",
+ "{years} years": "",
"+1 (123) 456-7890": "+41 12 234 56 78",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "Kontodaten erfolgreich aktualisiert",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Sie haben sich erfolgreich angemeldet.",
"You've successfully subscribed to {siteTitle}": "Sie haben sich erfolgreich abonniert bei {siteTitle}",
"Your account": "Ihr Konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Ihre E-Mail-Adresse konnte nicht angemeldet werden. Bitte versuchen Sie es erneut",
"your inbox": "Ihre Inbox",
"Your input helps shape what gets published.": "Ihr Beitrag kann unsere Berichterstattung beeinflussen und mitprägen.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "Ihr Abo wurde gekündigt und läuft am {expiryDate} aus.",
"Your subscription will expire on {expiryDate}": "Ihr Abo endet am {expiryDate}.",
"Your subscription will renew on {renewalDate}": "Ihr Abo wird am {renewalDate} erneuert.",
diff --git a/ghost/i18n/locales/de/portal.json b/ghost/i18n/locales/de/portal.json
index 25be9f3a708..f92f923f528 100644
--- a/ghost/i18n/locales/de/portal.json
+++ b/ghost/i18n/locales/de/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} Tage kostenfrei",
+ "{years} years": "",
"+1 (123) 456-7890": "+49 123 4567890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "Kontodaten erfolgreich aktualisiert",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Du hast dich erfolgreich angemeldet.",
"You've successfully subscribed to {siteTitle}": "Du hast dich erfolgreich angemeldet bei {siteTitle}",
"Your account": "Dein Konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Deine E-Mailadresse konnte nicht angemeldet werden. Bitte versuche es noch einmal.",
"your inbox": "Dein Posteingang",
"Your input helps shape what gets published.": "Dein Beitrag trägt dazu bei, was veröffentlicht wird.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Dein Abonnement wird am {expiryDate} ablaufen.",
"Your subscription will renew on {renewalDate}": "Dein Abonnement wird am {renewalDate} erneuert.",
diff --git a/ghost/i18n/locales/el/portal.json b/ghost/i18n/locales/el/portal.json
index c7561fd9ffb..7a972eebfa0 100644
--- a/ghost/i18n/locales/el/portal.json
+++ b/ghost/i18n/locales/el/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} ημέρες δωρεάν",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Λογαριασμός",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Έχετε συνδεθεί επιτυχώς.",
"You've successfully subscribed to {siteTitle}": "Έχετε εγγραφεί επιτυχώς στο {siteTitle}",
"Your account": "Ο λογαριασμός σας",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Η συνεισφορά σας βοηθά να διαμορφωθεί το τι δημοσιεύεται.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Η συνδρομή σας θα λήξει στις {expiryDate}",
"Your subscription will renew on {renewalDate}": "Η συνδρομή σας θα ανανεωθεί στις {renewalDate}",
diff --git a/ghost/i18n/locales/en/portal.json b/ghost/i18n/locales/en/portal.json
index 1d24e92b296..b48e74f3847 100644
--- a/ghost/i18n/locales/en/portal.json
+++ b/ghost/i18n/locales/en/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "",
"Your subscription will renew on {renewalDate}": "",
diff --git a/ghost/i18n/locales/eo/portal.json b/ghost/i18n/locales/eo/portal.json
index 23093912766..bc69efdb6b8 100644
--- a/ghost/i18n/locales/eo/portal.json
+++ b/ghost/i18n/locales/eo/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} tagoj senpagaj",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "Via konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Via enigo helpas formi kio estas aperigita.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "",
"Your subscription will renew on {renewalDate}": "",
diff --git a/ghost/i18n/locales/es/portal.json b/ghost/i18n/locales/es/portal.json
index d50841dc0ae..57c2f3ab342 100644
--- a/ghost/i18n/locales/es/portal.json
+++ b/ghost/i18n/locales/es/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} días gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Cuenta",
"Account details updated successfully": "Los detalles de la cuenta se actualizaron con éxito",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Has iniciado sesión correctamente.",
"You've successfully subscribed to {siteTitle}": "Te has suscrito correctamente a {siteTitle}",
"Your account": "Tu cuenta",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "No se ha podido volver a suscribir tu correo electrónico, inténtalo de nuevo por favor",
"your inbox": "",
"Your input helps shape what gets published.": "Tu opinión ayuda a definir lo que se publica.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Tu suscripción caducará el {expiryDate} ",
"Your subscription will renew on {renewalDate}": "Tu suscripción se renovará el {renewalDate}",
diff --git a/ghost/i18n/locales/et/portal.json b/ghost/i18n/locales/et/portal.json
index 1d29bf4819b..b538bcd88ce 100644
--- a/ghost/i18n/locales/et/portal.json
+++ b/ghost/i18n/locales/et/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} päeva tasuta",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Olete edukalt sisse loginud.",
"You've successfully subscribed to {siteTitle}": "Olete edukalt tellinud {siteTitle}",
"Your account": "Teie konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Teie sisend aitab kujundada seda, mida avaldatakse.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Teie tellimus aegub {expiryDate}",
"Your subscription will renew on {renewalDate}": "Teie tellimus uueneb {renewalDate}",
diff --git a/ghost/i18n/locales/eu/portal.json b/ghost/i18n/locales/eu/portal.json
index 22a44ae1be4..22e5c5ed1ba 100644
--- a/ghost/i18n/locales/eu/portal.json
+++ b/ghost/i18n/locales/eu/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} egun debalde",
+ "{years} years": "",
"+1 (123) 456-7890": "+3X 123 456 789",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Kontua",
"Account details updated successfully": "Kontuaren xehetasunak eguneratu dira",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Saioa hasi duzu.",
"You've successfully subscribed to {siteTitle}": "Honakora harpidetu zara: {siteTitle}",
"Your account": "Zure kontua",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Ezin izan da zure ePosta berriro harpidetu, saiatu berriro",
"your inbox": "",
"Your input helps shape what gets published.": "Zure ekarpenak argitaratzen denari forma ematen laguntzen digu.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Zure harpidetza {expiryDate}(e)an iraungiko da",
"Your subscription will renew on {renewalDate}": "Zure harpidetza {renewalDate}(e)an berrituko da",
diff --git a/ghost/i18n/locales/fa/portal.json b/ghost/i18n/locales/fa/portal.json
index 95eb3368ac7..25113889da9 100644
--- a/ghost/i18n/locales/fa/portal.json
+++ b/ghost/i18n/locales/fa/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} روز رایگان",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "حساب کاربری",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "شما با موفقیت وارد شدید.",
"You've successfully subscribed to {siteTitle}": "شما با موفقیت مشترک این موارد شدید: {siteTitle}",
"Your account": "حساب کاربری شما",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "تلاش شما به آنچه که منتشر می\u200cشود، شکل می\u200cدهد.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "اشتراک شما در تاریخ {expiryDate} منقضی می\u200cشود",
"Your subscription will renew on {renewalDate}": "اشتراک شما در تاریخ {renewalDate} تمدید می\u200cشود",
diff --git a/ghost/i18n/locales/fi/portal.json b/ghost/i18n/locales/fi/portal.json
index 6fa20a316f3..b86c0572b6f 100644
--- a/ghost/i18n/locales/fi/portal.json
+++ b/ghost/i18n/locales/fi/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} päivää ilmaiseksi",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Oma tili",
"Account details updated successfully": "Tilin lisätiedot on päivitetty",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Olet kirjautunut sisään onnistuneesti",
"You've successfully subscribed to {siteTitle}": "Tilaus onnistui: {siteTitle}",
"Your account": "Tilisi",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Tilauksen uusiminen epäonnistui sähköpostillesi. Yritäthän uudelleen.",
"your inbox": "sähköpostilaatikkoosi",
"Your input helps shape what gets published.": "Antamasi palautteen avulla muokataan julkaistavaa sisältöä",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Tilauksesi päättyy {expiryDate}",
"Your subscription will renew on {renewalDate}": "Tilauksesi uusiutuu {renewalDate}",
diff --git a/ghost/i18n/locales/fr/portal.json b/ghost/i18n/locales/fr/portal.json
index 6c4e06472f6..21ebed05d12 100644
--- a/ghost/i18n/locales/fr/portal.json
+++ b/ghost/i18n/locales/fr/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} jours gratuits",
+ "{years} years": "",
"+1 (123) 456-7890": "+33 1 23 45 67 89",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Compte",
"Account details updated successfully": "Mise à jour réussie des données du compte",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Vous vous êtes connecté avec succès.",
"You've successfully subscribed to {siteTitle}": "Vous vous êtes abonné à {siteTitle}",
"Your account": "Votre compte",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Votre e-mail n'a pas été pris en compte pour le réabonnement, veuillez réessayer",
"your inbox": "votre boîte de réception",
"Your input helps shape what gets published.": "Votre avis aide à améliorer ce qui est publié.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Votre abonnement expirera le {expiryDate}",
"Your subscription will renew on {renewalDate}": "Votre abonnement sera renouvelé le {renewalDate}",
diff --git a/ghost/i18n/locales/gd/portal.json b/ghost/i18n/locales/gd/portal.json
index c44570a876d..3ffe18b1e13 100644
--- a/ghost/i18n/locales/gd/portal.json
+++ b/ghost/i18n/locales/gd/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays}l an-asgaidh",
+ "{years} years": "",
"+1 (123) 456-7890": "+44 1234 567890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Cunntas",
"Account details updated successfully": "Chaidh roghainnean a' chunntais ùrachadh gu soirbheachail",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Chlàraich thu a-steach gu soirbheachail.",
"You've successfully subscribed to {siteTitle}": "Fo-sgrìobh thu gu soirbheachail gu {siteTitle}",
"Your account": "An cunntas agad",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Dh'fhàillig ath-nuadhachadh an fho-sgrìobhaidh, feuch a-rithist",
"your inbox": "",
"Your input helps shape what gets published.": "Bheir na beachdan agad buaidh air na foillseachaidhean ri teachd.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Falbhaidh an ùine air am fo-sgrìobhadh agad: {expiryDate}",
"Your subscription will renew on {renewalDate}": "Ath-nuadhaichidh am fo-sgrìobhadh agad: {renewalDate}",
diff --git a/ghost/i18n/locales/he/portal.json b/ghost/i18n/locales/he/portal.json
index 728eaeec8ee..9688317c281 100644
--- a/ghost/i18n/locales/he/portal.json
+++ b/ghost/i18n/locales/he/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} ימים חינם",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "חשבון",
"Account details updated successfully": "פרטי החשבון עודכנו בהצלחה",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "נכנסתם בהצלחה.",
"You've successfully subscribed to {siteTitle}": "נרשמתם בהצלחה ל {siteTitle}",
"Your account": "החשבון שלך",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "שגיאה בהרשמה מחדש עם כתובת המייל שלכם, נסו שוב",
"your inbox": "",
"Your input helps shape what gets published.": "המשוב שלכם עוזר לנו לעצב את התוכן שייפורסם.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "המינוי שלכם יפוג ב {expiryDate}",
"Your subscription will renew on {renewalDate}": "המנוי שלכם יתחדש ב {renewalDate}",
diff --git a/ghost/i18n/locales/hi/portal.json b/ghost/i18n/locales/hi/portal.json
index bfcc09ddd0a..9ce406155fd 100644
--- a/ghost/i18n/locales/hi/portal.json
+++ b/ghost/i18n/locales/hi/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} दिन मुफ्त",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "खाता",
"Account details updated successfully": "खाते की जानकारी सफलतापूर्वक अपडेट की गई",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "आपने सफलतापूर्वक साइन इन कर लिया है।",
"You've successfully subscribed to {siteTitle}": "आपने सफलतापूर्वक सदस्यता ली है {siteTitle}",
"Your account": "आपका खाता",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "आपका ईमेल पुनः सदस्यता लेने में विफल रहा, कृपया पुनः प्रयास करें",
"your inbox": "",
"Your input helps shape what gets published.": "आपका इनपुट प्रकाशित होने वाली चीज़ों को आकार देने में मदद करता है।",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "आपकी सदस्यता {expiryDate} को समाप्त हो जाएगी",
"Your subscription will renew on {renewalDate}": "आपकी सदस्यता {renewalDate} को नवीनीकृत होगी",
diff --git a/ghost/i18n/locales/hr/portal.json b/ghost/i18n/locales/hr/portal.json
index 0e4b9c8a8ba..61f34f32016 100644
--- a/ghost/i18n/locales/hr/portal.json
+++ b/ghost/i18n/locales/hr/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dana besplatno",
+ "{years} years": "",
"+1 (123) 456-7890": "(+385 1) 234-5678",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Vaš račun",
"Account details updated successfully": "Podaci računa su uspješno ažurirani",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Uspješno ste prijavljeni.",
"You've successfully subscribed to {siteTitle}": "Uspješno ste pretplaćeni na {siteTitle}",
"Your account": "Vaš korisnički račun",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Ponovna pretplata nije uspjela, pokušajte ponovno",
"your inbox": "",
"Your input helps shape what gets published.": "Vaš doprinos pomaže u oblikovanju sadržaja kojeg objavljujemo.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Vaša pretplata istječe {expiryDate}",
"Your subscription will renew on {renewalDate}": "Vaša pretplata će se obnoviti {renewalDate}",
diff --git a/ghost/i18n/locales/hu/portal.json b/ghost/i18n/locales/hu/portal.json
index df86e9864b9..e5c209afd7b 100644
--- a/ghost/i18n/locales/hu/portal.json
+++ b/ghost/i18n/locales/hu/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} nap ingyen",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Fiók",
"Account details updated successfully": "A fiók adatai sikeresen frissültek.",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Sikeresen bejelentkeztél.",
"You've successfully subscribed to {siteTitle}": "Sikeresen bejelentkeztél ide: {siteTitle}",
"Your account": "Fiókod",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Az e-mail-címedet nem sikerült újra regisztrálni. Kérjük, próbáld újra",
"your inbox": "",
"Your input helps shape what gets published.": "A visszajelzésed segít abban, hogy miről írjunk",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Az előfizetésed lejár ekkor: {expiryDate}",
"Your subscription will renew on {renewalDate}": "Az előfizetésed megújul ekkor: {renewalDate}",
diff --git a/ghost/i18n/locales/id/portal.json b/ghost/i18n/locales/id/portal.json
index bed53a35f7a..715a0eeeb7a 100644
--- a/ghost/i18n/locales/id/portal.json
+++ b/ghost/i18n/locales/id/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "Gratis {trialDays} hari",
+ "{years} years": "",
"+1 (123) 456-7890": "+62 123-4567-8900",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Akun",
"Account details updated successfully": "Detail akun berhasil diperbarui",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Anda telah berhasil masuk.",
"You've successfully subscribed to {siteTitle}": "Anda telah berhasil berlangganan ke {siteTitle}",
"Your account": "Akun Anda",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Email Anda gagal berlangganan ulang, harap coba lagi",
"your inbox": "",
"Your input helps shape what gets published.": "Masukan Anda membantu membentuk apa yang dipublikasikan.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Langganan Anda akan berakhir pada {expiryDate}",
"Your subscription will renew on {renewalDate}": "Langganan Anda akan diperpanjang pada {renewalDate}",
diff --git a/ghost/i18n/locales/is/portal.json b/ghost/i18n/locales/is/portal.json
index 34650d8c4e9..fa8fec3654e 100644
--- a/ghost/i18n/locales/is/portal.json
+++ b/ghost/i18n/locales/is/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "dagar án endurgjalds",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Aðgangur",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Þér tókst að skrá þig inn",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "Aðgangurinn þinn",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Áskrift þinni lýkur {expiryDate}",
"Your subscription will renew on {renewalDate}": "Áskrift þín verður endurnýjuð {expiryDate}",
diff --git a/ghost/i18n/locales/it/portal.json b/ghost/i18n/locales/it/portal.json
index 0e9177b518b..b0556cbac72 100644
--- a/ghost/i18n/locales/it/portal.json
+++ b/ghost/i18n/locales/it/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} giorni gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Account",
"Account details updated successfully": "Dettagli dell'account aggiornati con successo",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Accesso effettuato.",
"You've successfully subscribed to {siteTitle}": "Iscrizione effettuata a {siteTitle}",
"Your account": "Il tuo account",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "La re-iscrizione della tua e-mail è fallita, per favore riprova",
"your inbox": "la tua casella di posta",
"Your input helps shape what gets published.": "Il tuo contributo aiuta a dare forma a ciò che viene pubblicato.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "Il tuo abbonamento è stato annullato e scadrà il {expiryDate}.",
"Your subscription will expire on {expiryDate}": "Il tuo abbonamento scadrà il {expiryDate}",
"Your subscription will renew on {renewalDate}": "Il tuo abbonamento verrà rinnovato il {renewalDate}",
diff --git a/ghost/i18n/locales/ja/portal.json b/ghost/i18n/locales/ja/portal.json
index 63a861d15db..b791ec9d336 100644
--- a/ghost/i18n/locales/ja/portal.json
+++ b/ghost/i18n/locales/ja/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays}日間無料",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "アカウント",
"Account details updated successfully": "アカウント詳細が更新されました",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "ログインに成功しました",
"You've successfully subscribed to {siteTitle}": "の購読に成功しました {siteTitle}",
"Your account": "あなたのアカウント",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "あなたの感想を今後の内容の参考にさせていただきます。",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "あなたの購読は{expiryDate}に期限切れになります。",
"Your subscription will renew on {renewalDate}": "あなたの購読は{renewalDate}に更新されます。",
diff --git a/ghost/i18n/locales/ko/portal.json b/ghost/i18n/locales/ko/portal.json
index b3271ecad99..eb869c7a78a 100644
--- a/ghost/i18n/locales/ko/portal.json
+++ b/ghost/i18n/locales/ko/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays}일 무료",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "계정",
"Account details updated successfully": "계정 정보가 성공적으로 업데이트되었어요",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "성공적으로 로그인되었어요.",
"You've successfully subscribed to {siteTitle}": "성공적으로 구독하셨어요: {siteTitle}",
"Your account": "계정",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "이메일 재구독에 실패했어요. 다시 시도해 주세요",
"your inbox": "",
"Your input helps shape what gets published.": "회원님의 의견은 게시물을 제작하는 것에 큰 도움이 돼요.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "회원님의 구독은 {expiryDate}에 만료돼요",
"Your subscription will renew on {renewalDate}": "회원님의 구독은 {renewalDate}에 갱신돼요",
diff --git a/ghost/i18n/locales/kz/portal.json b/ghost/i18n/locales/kz/portal.json
index 17043429119..a4dfa7fb73c 100644
--- a/ghost/i18n/locales/kz/portal.json
+++ b/ghost/i18n/locales/kz/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} күн тегін",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Аккаунт",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Кіру сәтті орындалды.",
"You've successfully subscribed to {siteTitle}": "Жазылым сәтті орындалды {siteTitle}",
"Your account": "Сіздің аккаунт",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Сіздің пікіріңіз жарияланатын мазмұнды қалыптастыруға көмектеседі.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Сіздің жазылым {expiryDate} күні аяқталады",
"Your subscription will renew on {renewalDate}": "Сіздің жазылым {renewalDate} күні қайта жаңартылады",
diff --git a/ghost/i18n/locales/lt/portal.json b/ghost/i18n/locales/lt/portal.json
index 8c30ef68cfa..78786f50757 100644
--- a/ghost/i18n/locales/lt/portal.json
+++ b/ghost/i18n/locales/lt/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} d. nemokamai",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Paskyra",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Sėkmingai prisijungėte.",
"You've successfully subscribed to {siteTitle}": "Sėkmingai užsiprenumeravote {siteTitle}",
"Your account": "Jūsų paskyra",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Jūsų indėlis padeda kurti tai, kas yra viešinama.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Jūsų prenumerata baigsis {expiryDate}",
"Your subscription will renew on {renewalDate}": "Jūsų prenumerata atsinaujins {renewalDate}",
diff --git a/ghost/i18n/locales/lv/portal.json b/ghost/i18n/locales/lv/portal.json
index 137c0bc6949..6417dab6971 100644
--- a/ghost/i18n/locales/lv/portal.json
+++ b/ghost/i18n/locales/lv/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays}\u00a0dienas bez maksas",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konts",
"Account details updated successfully": "Konta informācija ir veiksmīgi atjaunināta",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Jūs esat veiksmīgi pierakstījies.",
"You've successfully subscribed to {siteTitle}": "Jūs esat veiksmīgi abonējis {siteTitle}",
"Your account": "Jūsu konts",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Jūsu e-pasta abonēšana neizdevās atkārtoti. Lūdzu, mēģiniet vēlreiz",
"your inbox": "",
"Your input helps shape what gets published.": "Jūsu ieguldījums palīdz veidot publicējamo saturu.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Jūsu abonementa derīguma termiņš beigsies {expiryDate}",
"Your subscription will renew on {renewalDate}": "Jūsu abonements tiks atjaunots šādā datumā: {renewalDate}",
diff --git a/ghost/i18n/locales/mk/portal.json b/ghost/i18n/locales/mk/portal.json
index 5a88c3587e5..63b2506c565 100644
--- a/ghost/i18n/locales/mk/portal.json
+++ b/ghost/i18n/locales/mk/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} денови бесплатно",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Сметка",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Успешно се најавивте.",
"You've successfully subscribed to {siteTitle}": "Успешно се претплативте на {siteTitle}",
"Your account": "Вашата сметка",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Вашиот прилог помага да се оформи она кое ќе биде објавувано.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Вашата претплата ќе истече на {expiryDate}",
"Your subscription will renew on {renewalDate}": "Вашата претплата ќе биде обновена на {renewalDate}",
diff --git a/ghost/i18n/locales/mn/portal.json b/ghost/i18n/locales/mn/portal.json
index 61429ac0674..6392c047654 100644
--- a/ghost/i18n/locales/mn/portal.json
+++ b/ghost/i18n/locales/mn/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} өдөр үнэгүй",
+ "{years} years": "",
"+1 (123) 456-7890": "+976 1234-5678",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Бүртгэл",
"Account details updated successfully": "Бүртгэлийн мэдээлэл амжилттай шинэчлэгдлээ",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Та амжилттай нэвтэрлээ.",
"You've successfully subscribed to {siteTitle}": "Таны захиалга амжилттай үүслээ. {siteTitle}",
"Your account": "Таны бүртгэл",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Таны имэйлийг дахин захиалахад алдаа гарлаа, дахин оролдоно уу",
"your inbox": "",
"Your input helps shape what gets published.": "Таны санал дараа дараагийн нийтлэлийг илүү чанартай болгоход туслана",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Таны захиалга {expiryDate}-нд дуусна",
"Your subscription will renew on {renewalDate}": "Таны захиалга {renewalDate}-нд шинэчлэгдэнэ",
diff --git a/ghost/i18n/locales/ms/portal.json b/ghost/i18n/locales/ms/portal.json
index 7c8ff05c770..3ed087f3adb 100644
--- a/ghost/i18n/locales/ms/portal.json
+++ b/ghost/i18n/locales/ms/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "Percuma {trialDays} hari",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Akaun",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Anda telah berjaya log masuk.",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "Akaun anda",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Input anda membantu membentuk apa yang diterbitkan.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Langganan anda akan tamat tempoh pada {expiryDate}",
"Your subscription will renew on {renewalDate}": "Langganan anda akan diperbaharui pada {renewalDate}",
diff --git a/ghost/i18n/locales/nb/portal.json b/ghost/i18n/locales/nb/portal.json
index 45555cc8275..9a64a8a9465 100644
--- a/ghost/i18n/locales/nb/portal.json
+++ b/ghost/i18n/locales/nb/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dager gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "+47 123 45 678",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "Kontodetaljer oppdatert",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Du har logget på.",
"You've successfully subscribed to {siteTitle}": "Du har meldt deg på {siteTitle}",
"Your account": "Din konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "E-posten din kunne ikke bli meldt på igjen, vennligst prøv på nytt",
"your inbox": "",
"Your input helps shape what gets published.": "Din tilbakemelding bidrar til å påvirke hva som blir publisert.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Ditt abonnement vil avsluttes den {expiryDate}",
"Your subscription will renew on {renewalDate}": "Ditt abonnement vil fornyes den {renewalDate}",
diff --git a/ghost/i18n/locales/ne/portal.json b/ghost/i18n/locales/ne/portal.json
index 1d24e92b296..b48e74f3847 100644
--- a/ghost/i18n/locales/ne/portal.json
+++ b/ghost/i18n/locales/ne/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "",
"Your subscription will renew on {renewalDate}": "",
diff --git a/ghost/i18n/locales/nl/portal.json b/ghost/i18n/locales/nl/portal.json
index cc456243025..64c248bae7e 100644
--- a/ghost/i18n/locales/nl/portal.json
+++ b/ghost/i18n/locales/nl/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dagen gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Account",
"Account details updated successfully": "Accountgegevens succesvol bijgewerkt",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Je bent succesvol ingelogd.",
"You've successfully subscribed to {siteTitle}": "Je bent succesvol geabonneerd op {siteTitle}",
"Your account": "Jouw account",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Je e-mail is niet opnieuw geabonneerd, probeer het opnieuw",
"your inbox": "",
"Your input helps shape what gets published.": "Jouw mening helpt bepalen wat er gepubliceerd wordt.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Je abonnement verloopt op {expiryDate}",
"Your subscription will renew on {renewalDate}": "Je abonnement wordt verlengd op {renewalDate}",
diff --git a/ghost/i18n/locales/nn/portal.json b/ghost/i18n/locales/nn/portal.json
index 80e216f76d1..19cf54dd153 100644
--- a/ghost/i18n/locales/nn/portal.json
+++ b/ghost/i18n/locales/nn/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dagar gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Brukar",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Vellykka innlogging.",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "Din brukar",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Dine tilbakemeldinger hjelper oss å forma tilbodet vårt.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Ditt abonnement går ut den {expiryDate}",
"Your subscription will renew on {renewalDate}": "Ditt abonnement vil fornyast den {renewalDate}",
diff --git a/ghost/i18n/locales/pa/portal.json b/ghost/i18n/locales/pa/portal.json
index aa80da05986..6edabf3bd0c 100644
--- a/ghost/i18n/locales/pa/portal.json
+++ b/ghost/i18n/locales/pa/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} ਦਿਨ ਮੁਫ਼ਤ",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "ਖਾਤਾ",
"Account details updated successfully": "ਖਾਤੇ ਦੇ ਵੇਰਵੇ ਸਫਲਤਾਪੂਰਵਕ ਅੱਪਡੇਟ ਕੀਤੇ ਗਏ",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "ਤੁਸੀਂ ਸਫਲਤਾਪੂਰਵਕ ਸਾਈਨ ਇਨ ਕਰ ਲਿਆ ਹੈ।",
"You've successfully subscribed to {siteTitle}": "ਤੁਸੀਂ ਸਫਲਤਾਪੂਰਵਕ ਸਦੱਸਤਾ ਲਈ ਹੈ: {siteTitle}",
"Your account": "ਤੁਹਾਡਾ ਖਾਤਾ",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "ਤੁਹਾਡੀ ਈਮੇਲ ਮੁੜ-ਸਦੱਸਤਾ ਲੈਣ ਵਿੱਚ ਅਸਫਲ ਰਹੀ ਹੈ, ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ",
"your inbox": "",
"Your input helps shape what gets published.": "ਤੁਹਾਡਾ ਇਨਪੁਟ ਇਹ ਆਕਾਰ ਦੇਣ ਵਿੱਚ ਮਦਦ ਕਰਦਾ ਹੈ ਕਿ ਕੀ ਪ੍ਰਕਾਸ਼ਿਤ ਹੁੰਦਾ ਹੈ।",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "ਤੁਹਾਡੀ ਸਦੱਸਤਾ {expiryDate} ਨੂੰ ਮਿਆਦ ਪੁੱਗ ਜਾਵੇਗੀ।",
"Your subscription will renew on {renewalDate}": "ਤੁਹਾਡੀ ਸਦੱਸਤਾ {renewalDate} ਨੂੰ ਨਵਿਆਈ ਜਾਵੇਗੀ।",
diff --git a/ghost/i18n/locales/pl/portal.json b/ghost/i18n/locales/pl/portal.json
index 7dd55613715..1848f3d1f0f 100644
--- a/ghost/i18n/locales/pl/portal.json
+++ b/ghost/i18n/locales/pl/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} darmowych dni",
+ "{years} years": "",
"+1 (123) 456-7890": "+48 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "Szczegóły konta zostały pomyślnie zaktualizowane",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Logowanie powiodło się.",
"You've successfully subscribed to {siteTitle}": "Pomyślnie zasubskrybowano {siteTitle}",
"Your account": "Twoje konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Ponowna subskrypcja Twojego emaila nie powiodła się, spróbuj ponownie",
"your inbox": "",
"Your input helps shape what gets published.": "Twoja ocena pomoże nam lepiej kształtować nasze publikacje.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Subskrypcja wygaśnie w dniu {expiryDate}",
"Your subscription will renew on {renewalDate}": "Subskrypcja zostanie odnowiona w dniu {renewalDate}",
diff --git a/ghost/i18n/locales/pt-BR/portal.json b/ghost/i18n/locales/pt-BR/portal.json
index 55d77e6f62e..8a16c18b340 100644
--- a/ghost/i18n/locales/pt-BR/portal.json
+++ b/ghost/i18n/locales/pt-BR/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dias grátis",
+ "{years} years": "",
"+1 (123) 456-7890": "+55 (00) 0 0000-0000",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Conta",
"Account details updated successfully": "Detalhes da conta atualizados com sucesso",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Você entrou com sucesso.",
"You've successfully subscribed to {siteTitle}": "Você se inscreveu com sucesso {siteTitle}",
"Your account": "Sua conta",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Não foi possível reinscrever seu e-mail, por favor, tente novamente.",
"your inbox": "sua caixa de entrada",
"Your input helps shape what gets published.": "Sua resposta ajuda a definir o que será publicado.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Sua assinatura expirará em {expiryDate}",
"Your subscription will renew on {renewalDate}": "Sua assinatura será renovada em {renewalDate}",
diff --git a/ghost/i18n/locales/pt/portal.json b/ghost/i18n/locales/pt/portal.json
index 003c6228e66..f61bfa1c233 100644
--- a/ghost/i18n/locales/pt/portal.json
+++ b/ghost/i18n/locales/pt/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dias grátis",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Conta",
"Account details updated successfully": "Detalhes da conta atualizados com sucesso",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Registou-se com sucesso.",
"You've successfully subscribed to {siteTitle}": "Subscreveu com sucesso {siteTitle}",
"Your account": "A sua conta",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "O seu feedback ajudará a decidir o conteúdo que será publicado no futuro.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "A sua assinatura expirará em {expiryDate}",
"Your subscription will renew on {renewalDate}": "A sua assinatura será renovada em {renewalDate}",
diff --git a/ghost/i18n/locales/ro/portal.json b/ghost/i18n/locales/ro/portal.json
index 704886d04db..1970dcac8a6 100644
--- a/ghost/i18n/locales/ro/portal.json
+++ b/ghost/i18n/locales/ro/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} zile gratuite",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Cont",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Te-ai autentificat cu succes.",
"You've successfully subscribed to {siteTitle}": "Te-ai abonat cu succes la {siteTitle}",
"Your account": "Contul tău",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Contribuția ta ajută la conturarea a ceea ce se publică.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Abonamentul tău va expira pe {expiryDate}",
"Your subscription will renew on {renewalDate}": "Abonamentul tău se va reînnoi pe {renewalDate}",
diff --git a/ghost/i18n/locales/ru/portal.json b/ghost/i18n/locales/ru/portal.json
index 053badc0679..a10bdce2782 100644
--- a/ghost/i18n/locales/ru/portal.json
+++ b/ghost/i18n/locales/ru/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} дня(ей) бесплатно",
+ "{years} years": "",
"+1 (123) 456-7890": "+7 (987) 654-3210",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Аккаунт",
"Account details updated successfully": "Данные учётной записи успешно обновлены",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Вы успешно вошли.",
"You've successfully subscribed to {siteTitle}": "Вы успешно подписались на {siteTitle}",
"Your account": "Ваш аккаунт",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Не удалось подписаться повторно используя ваш email, попробуйте ещё раз",
"your inbox": "",
"Your input helps shape what gets published.": "Ваш отзыв помогает формировать понимание, что вам интересно и что будет опубликовано в будущем.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Срок действия вашей подписки истекает {expiryDate}",
"Your subscription will renew on {renewalDate}": "Ваша подписка будет продлена {renewalDate}",
diff --git a/ghost/i18n/locales/si/portal.json b/ghost/i18n/locales/si/portal.json
index 4e55ee51473..c0f69c8642f 100644
--- a/ghost/i18n/locales/si/portal.json
+++ b/ghost/i18n/locales/si/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "දින {trialDays} ක් දක්වා නොමිලේ",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "ගිණුම",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "ඔබ සාර්ථකව sign in වන ලදී.",
"You've successfully subscribed to {siteTitle}": "ඔබ සාර්ථකව subscribe ක\u200bර ඇත {siteTitle}",
"Your account": "ඔබගේ ගිණුම",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "ඔබගේ අදහස් ඉදිරියේදී සිදු කරන පළකිරීම් වැඩිදියුණු කිරීමට උදව් කරනු ඇත.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "ඔබගේ subscription එක {expiryDate} වැනි දින කල් ඉකුත් වනු ඇත",
"Your subscription will renew on {renewalDate}": "ඔබගේ subscription එක {expiryDate} වැනි දින renew වනු ඇත",
diff --git a/ghost/i18n/locales/sk/portal.json b/ghost/i18n/locales/sk/portal.json
index 1a060ea94d4..5ebf4df113e 100644
--- a/ghost/i18n/locales/sk/portal.json
+++ b/ghost/i18n/locales/sk/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dní zdarma",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Účet",
"Account details updated successfully": "Podrobnosti o účte boli úspešne aktualizované",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Úspešne ste sa prihlásili",
"You've successfully subscribed to {siteTitle}": "Úspešne ste sa prihlásili na odber pre {siteTitle}",
"Your account": "Váš účet",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Váš email zlyhal pri obnovení predplatného, prosím skúste to znova",
"your inbox": "",
"Your input helps shape what gets published.": "Vaše pripomienky pomáhajú spoluvytvárať obsah webu.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Váše predplatné expiruje {expiryDate}",
"Your subscription will renew on {renewalDate}": "Váše predplatné bude obnovené {renewalDate}",
diff --git a/ghost/i18n/locales/sl/portal.json b/ghost/i18n/locales/sl/portal.json
index 248562cc708..cf160f10b71 100644
--- a/ghost/i18n/locales/sl/portal.json
+++ b/ghost/i18n/locales/sl/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dni brezplačno",
+ "{years} years": "",
"+1 (123) 456-7890": "+386 1 234 56 78",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Račun",
"Account details updated successfully": "Podatki o računu so bili uspešno posodobljeni",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Uspešno ste se prijavili.",
"You've successfully subscribed to {siteTitle}": "Uspešno ste se naročili na {siteTitle}",
"Your account": "Vaš račun",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Vašega e-poštnega naslova nismo uspeli ponovno prijaviti, prosimo, poskusite znova",
"your inbox": "",
"Your input helps shape what gets published.": "Vaše mnenje nam pomaga pri izbiri objavljenih vsebin.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Vaša naročnina bo potekla dne {expiryDate}",
"Your subscription will renew on {renewalDate}": "Vaša naročnina se bo obnovila dne {renewalDate}",
diff --git a/ghost/i18n/locales/sq/portal.json b/ghost/i18n/locales/sq/portal.json
index 01ca5168f15..ca1d6bdbfb8 100644
--- a/ghost/i18n/locales/sq/portal.json
+++ b/ghost/i18n/locales/sq/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dite falas",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Llogaria",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Ju jeni identifikuar me sukses.",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "Llogaria juar",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Të dhënat tuaja ndihmojnë në formimin e asaj që publikohet.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Abonimi juaj do te skadoje ne {expiryDate}",
"Your subscription will renew on {renewalDate}": "Abonimi juaj to te rinovohet ne {renewalDate}",
diff --git a/ghost/i18n/locales/sr-Cyrl/portal.json b/ghost/i18n/locales/sr-Cyrl/portal.json
index a7f87fd7396..511df3bfe95 100644
--- a/ghost/i18n/locales/sr-Cyrl/portal.json
+++ b/ghost/i18n/locales/sr-Cyrl/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} дана бесплатно",
+ "{years} years": "",
"+1 (123) 456-7890": "+381 60 123-456",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Налог",
"Account details updated successfully": "Детаљи налога су успешно ажурирани",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Успешно сте се пријавили.",
"You've successfully subscribed to {siteTitle}": "Успешно сте се претплатили на {siteTitle}",
"Your account": "Ваш налог",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Поновна претплата на Ваш email није успела, покушајте поново",
"your inbox": "Ваш inbox",
"Your input helps shape what gets published.": "Ваше учешће помаже у обликовању онога што ће бити објављено.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Ваша претплата истиче {expiryDate}",
"Your subscription will renew on {renewalDate}": "Ваша претплата ће бити обновљена {renewalDate}",
diff --git a/ghost/i18n/locales/sr/portal.json b/ghost/i18n/locales/sr/portal.json
index c62d33f1bc2..63c06803f0c 100644
--- a/ghost/i18n/locales/sr/portal.json
+++ b/ghost/i18n/locales/sr/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dana besplatno",
+ "{years} years": "",
"+1 (123) 456-7890": "+381 60 123-456",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Nalog",
"Account details updated successfully": "Detalji naloga su uspešno ažurirani",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Uspešno ste se prijavili.",
"You've successfully subscribed to {siteTitle}": "Uspešno ste se pretplatili na {siteTitle}",
"Your account": "Vaš nalog",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Ponovna pretplata na Vaš email nije uspela, pokušajte ponovo",
"your inbox": "Vaš inbox",
"Your input helps shape what gets published.": "Vaše učešće pomaže u oblikovanju onoga što će biti objavljeno.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Vaša pretplata ističe {expiryDate}",
"Your subscription will renew on {renewalDate}": "Vaša pretplata će biti obnovljena {renewalDate}",
diff --git a/ghost/i18n/locales/sv/portal.json b/ghost/i18n/locales/sv/portal.json
index 5f370484cb8..0a1754d912d 100644
--- a/ghost/i18n/locales/sv/portal.json
+++ b/ghost/i18n/locales/sv/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} dagar gratis",
+ "{years} years": "",
"+1 (123) 456-7890": "+46 (0)78 901 23 45",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Konto",
"Account details updated successfully": "Kontodetaljer uppdaterade framgångsrikt",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Du är nu inloggad.",
"You've successfully subscribed to {siteTitle}": "Du är nu anmäld till {siteTitle}",
"Your account": "Ditt konto",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Din e-postadress kunde inte återanmälas, vänligen försök igen",
"your inbox": "din inkorg",
"Your input helps shape what gets published.": "Din åsikt hjälper till att forma vad som publiceras.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Din prenumeration avslutas {expiryDate}",
"Your subscription will renew on {renewalDate}": "Din prenumeration förnyas {renewalDate}",
diff --git a/ghost/i18n/locales/sw/portal.json b/ghost/i18n/locales/sw/portal.json
index 03e02a2edb3..ad425137bd4 100644
--- a/ghost/i18n/locales/sw/portal.json
+++ b/ghost/i18n/locales/sw/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "siku {trialDays} bila malipo",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Akaunti",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Umeingia kwa mafanikio.",
"You've successfully subscribed to {siteTitle}": "Umejiunga kwa mafanikio na {siteTitle}",
"Your account": "Akaunti yako",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "Maoni yako yanasaidia kuunda yaliyochapishwa.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Usajili wako utaisha tarehe {expiryDate}",
"Your subscription will renew on {renewalDate}": "Usajili wako utaongezwa tarehe {renewalDate}",
diff --git a/ghost/i18n/locales/ta/portal.json b/ghost/i18n/locales/ta/portal.json
index 9e33a0eaa9a..bc1031d6448 100644
--- a/ghost/i18n/locales/ta/portal.json
+++ b/ghost/i18n/locales/ta/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} நாட்கள் இலவசம்",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "கணக்கு",
"Account details updated successfully": "கணக்கு விவரங்கள் வெற்றிகரமாக புதுப்பிக்கப்பட்டன",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "நீங்கள் வெற்றிகரமாக உள்நுழைந்துள்ளீர்கள்.",
"You've successfully subscribed to {siteTitle}": "நீங்கள் வெற்றிகரமாக சந்தா செய்துள்ளீர்கள் {siteTitle}",
"Your account": "உங்கள் கணக்கு",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "உங்கள் மின்னஞ்சல் மீண்டும் சந்தா செய்ய முடியவில்லை, தயவுசெய்து மீண்டும் முயற்சிக்கவும்",
"your inbox": "",
"Your input helps shape what gets published.": "உங்கள் உள்ளீடு வெளியிடப்படுவதை வடிவமைக்க உதவுகிறது.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "உங்கள் சந்தா {expiryDate} அன்று காலாவதியாகும்",
"Your subscription will renew on {renewalDate}": "உங்கள் சந்தா {renewalDate} அன்று புதுப்பிக்கப்படும்",
diff --git a/ghost/i18n/locales/th/portal.json b/ghost/i18n/locales/th/portal.json
index 64e167aace7..8bffe422878 100644
--- a/ghost/i18n/locales/th/portal.json
+++ b/ghost/i18n/locales/th/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "ฟรี {trialDays} วัน",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "บัญชี",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "คุณลงชื่อเข้าใช้สำเร็จแล้ว",
"You've successfully subscribed to {siteTitle}": "คุณรับสมัครข้อมูลสำเร็จแล้ว {siteTitle}",
"Your account": "บัญชีของคุณ",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "ข้อมูลของคุณ จะช่วยกำหนดสิ่งที่จะได้รับการเผยแพร่ในอนาคต",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "การรับสมัครข้อมูลของคุณจะหมดอายุในวันที่ {expiryDate}",
"Your subscription will renew on {renewalDate}": "การรับสมัครข้อมูลของคุณจะต่ออายุในวันที่ {renewalDate}",
diff --git a/ghost/i18n/locales/tr/portal.json b/ghost/i18n/locales/tr/portal.json
index 69876ad81dc..33e36927764 100644
--- a/ghost/i18n/locales/tr/portal.json
+++ b/ghost/i18n/locales/tr/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} gün ücretsiz",
+ "{years} years": "",
"+1 (123) 456-7890": "+90 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Hesap",
"Account details updated successfully": "Hesap bilgileri başarıyla güncellendi",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Başarıyla oturum açtınız.",
"You've successfully subscribed to {siteTitle}": "Başarıyla abone oldunuz {siteTitle}",
"Your account": "Hesabın",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "E-postanız yeniden abone olmayı başaramadı, lütfen tekrar deneyin",
"your inbox": "gelen kutunuz",
"Your input helps shape what gets published.": "Yorumun yayımlanan içeriklerin şekillenmesine yardımcı olur.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Aboneliğiniz {expiryDate} tarihinde sona erecek",
"Your subscription will renew on {renewalDate}": "Aboneliğiniz {renewalDate} tarihinde yenilenecek",
diff --git a/ghost/i18n/locales/uk/portal.json b/ghost/i18n/locales/uk/portal.json
index 6ea3f16f371..f8d723e8d74 100644
--- a/ghost/i18n/locales/uk/portal.json
+++ b/ghost/i18n/locales/uk/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "Безплатно {trialDays} дні(-в)",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Oбліковий запис",
"Account details updated successfully": "Дані облікового запису успішно оновлено",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Ви успішно увійшли.",
"You've successfully subscribed to {siteTitle}": "Ви успішно підписалися на {siteTitle}",
"Your account": "Ваш обліковий запис",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Не вдалося повторно підписатися за вашою електронну адресою, спробуйте ще раз",
"your inbox": "",
"Your input helps shape what gets published.": "Ваш відгук допомагає обирати що публікувати далі.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "Термін дії вашої підписки закінчується {expiryDate}",
"Your subscription will renew on {renewalDate}": "Ваша підписка буде продовжена {renewalDate}",
diff --git a/ghost/i18n/locales/ur/portal.json b/ghost/i18n/locales/ur/portal.json
index e279fdd97b0..a9ef6e3c22c 100644
--- a/ghost/i18n/locales/ur/portal.json
+++ b/ghost/i18n/locales/ur/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} دن مفت پڑھیں",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "اکاؤنٹ",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "آپ نے کامیابی سے سائن ان کیا ہے۔",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "آپ کا اکاؤنٹ",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "آپ کا مدخلہ وہ چیزیں شکل دینے میں مدد فراہم کرتا ہے جو شائع ہوتی ہیں۔",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "آپ کی ہوشیاری {expiryDate} تک چلے گی",
"Your subscription will renew on {renewalDate}": "آپ کی ہوشیاری {renewalDate} تک تجدید ہوگی",
diff --git a/ghost/i18n/locales/uz/portal.json b/ghost/i18n/locales/uz/portal.json
index 21a4bad653e..9e77c3dc902 100644
--- a/ghost/i18n/locales/uz/portal.json
+++ b/ghost/i18n/locales/uz/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} kun bepul",
+ "{years} years": "",
"+1 (123) 456-7890": "",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "Hisob",
"Account details updated successfully": "",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "",
"You've successfully subscribed to {siteTitle}": "",
"Your account": "Sizning hisobingiz",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "",
"your inbox": "",
"Your input helps shape what gets published.": "",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "",
"Your subscription will expire on {expiryDate}": "",
"Your subscription will renew on {renewalDate}": "",
diff --git a/ghost/i18n/locales/vi/portal.json b/ghost/i18n/locales/vi/portal.json
index 6656f0b27f9..93956b7564a 100644
--- a/ghost/i18n/locales/vi/portal.json
+++ b/ghost/i18n/locales/vi/portal.json
@@ -12,9 +12,11 @@
"{months} months": "{months} tháng",
"{months} months free": "Miễn phí {months} tháng",
"{trialDays} days free": "{trialDays} ngày đọc thử miễn phí",
+ "{years} years": "",
"+1 (123) 456-7890": "+84 (987) 654-321",
"1 month": "1 tháng",
"1 month free": "Miễn phí 1 tháng",
+ "1 year": "",
"Access your RSS feeds": "Truy cập các nguồn tin RSS của bạn",
"Account": "Tài khoản",
"Account details updated successfully": "Đã cập nhật chi tiết tài khoản thành công",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "Bạn đã đăng nhập thành công.",
"You've successfully subscribed to {siteTitle}": "Bạn đã đăng ký gói thành công {siteTitle}",
"Your account": "Tài khoản của bạn",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "Không thể gia hạn với email của bạn, vui lòng thử lại",
"your inbox": "hộp thư đến của bạn",
"Your input helps shape what gets published.": "Thông tin bạn chọn giúp định hình nội dung được xuất bản.",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "Gói thành viên của bạn đã bị hủy và sẽ hết hạn vào {expiryDate}.",
"Your subscription will expire on {expiryDate}": "Gói thành viên của bạn sẽ hết hạn vào {expiryDate}",
"Your subscription will renew on {renewalDate}": "Gói thành viên của bạn sẽ tự động gia hạn vào {renewalDate}",
diff --git a/ghost/i18n/locales/zh-Hant/portal.json b/ghost/i18n/locales/zh-Hant/portal.json
index ea9aea043c7..f7f66847f5d 100644
--- a/ghost/i18n/locales/zh-Hant/portal.json
+++ b/ghost/i18n/locales/zh-Hant/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} 天免費試用",
+ "{years} years": "",
"+1 (123) 456-7890": "0912-123-123",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "帳號",
"Account details updated successfully": "帳號資訊已更新成功",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "您已成功登入。",
"You've successfully subscribed to {siteTitle}": "您已經成功訂閱 {siteTitle}",
"Your account": "您的帳號",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "您的電子郵件重新訂閱失敗,請再試一次",
"your inbox": "您的收件匣",
"Your input helps shape what gets published.": "您的建議將使我們變得更好。",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "您的訂閱已取消,將於 {expiryDate} 到期。",
"Your subscription will expire on {expiryDate}": "您的訂閱將於 {expiryDate} 到期",
"Your subscription will renew on {renewalDate}": "您的訂閱將於 {renewalDate} 自動續訂",
diff --git a/ghost/i18n/locales/zh/portal.json b/ghost/i18n/locales/zh/portal.json
index c63796632b8..c9948da1ba2 100644
--- a/ghost/i18n/locales/zh/portal.json
+++ b/ghost/i18n/locales/zh/portal.json
@@ -12,9 +12,11 @@
"{months} months": "",
"{months} months free": "",
"{trialDays} days free": "{trialDays} 天免费试用",
+ "{years} years": "",
"+1 (123) 456-7890": "+1 (123) 456-7890",
"1 month": "",
"1 month free": "",
+ "1 year": "",
"Access your RSS feeds": "",
"Account": "账户",
"Account details updated successfully": "账户信息更新成功",
@@ -244,9 +246,11 @@
"You've successfully signed in.": "您已成功登录。",
"You've successfully subscribed to {siteTitle}": "您已成功订阅 {siteTitle}",
"Your account": "您的账户",
+ "Your email": "",
"Your email has failed to resubscribe, please try again": "您的邮箱未能成功重新订阅,请重试。",
"your inbox": "您的收件箱",
"Your input helps shape what gets published.": "您的建议将使我们变得更好。",
+ "Your name": "",
"Your subscription has been canceled and will expire on {expiryDate}.": "您的订阅已取消,将于 {expiryDate} 到期。",
"Your subscription will expire on {expiryDate}": "您的订阅将在{expiryDate}到期",
"Your subscription will renew on {renewalDate}": "您的订阅将在{renewalDate}续费",