зеркало из https://github.com/mozilla/fxa.git
feat(l10n): Split payments-server single FTL file into per-component files
Because * Organizing l10n strings in per-component files aligns payments-server with the setup used for auth-server and settings. Splitting the FTL files also improves maintability by more tightling coupling the strings with the components where the strings are used. This commit * Create a temporary branding file in fxa-payments-server/src * Create a gruntfile with tasks for FTL concatenation and watching * Rename the destination file from main.ftl to payments.ftl * Switch payments-server's default locale from en-US to en and update all references of en-US to en * Create individual FTL files per component and move messages to their respective component FTL file * Add a l10n entry in the package readme * Update clone-l10n.sh to copy 'payments' ftl files instead of 'main' * Update AppLocalizationProvider to use 'payments' bundles * Replace setupFluentLocalizationTest with getFtlBundle/getFtlFromPackage from fxa-react * Remove setupFluentLocalizationTest function (no longer used in fxa-settings) * Add merge-ftl:test task to package.json start and test:frontend scripts * Remove legacy strings from FTL files * Update currency and date formats to use 'en' as default locale * Remove legacy paths from .gitignore files in payments-server * Move remaining paths to global .gitignore * Delete .gitignore files in payments-server Closes #FXA-5996 and #FXA-6255
This commit is contained in:
Родитель
63ff7ad6d1
Коммит
d79c30d50a
|
@ -8,6 +8,10 @@
|
|||
|
||||
# Secrets
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.keys
|
||||
public-key.json
|
||||
secret-key.json
|
||||
|
@ -39,6 +43,8 @@ tailwind.out.*
|
|||
# Dependencies
|
||||
**/node_modules
|
||||
**/browser_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# Logging
|
||||
*.log*
|
||||
|
@ -117,6 +123,8 @@ packages/fxa-customs-server/test/mocks/temp.netset
|
|||
|
||||
# fxa-payments-server
|
||||
packages/fxa-payments-server/fxa-content-server-l10n/
|
||||
packages/fxa-payments-server/public/locales
|
||||
packages/fxa-payments-server/test
|
||||
|
||||
# fxa-profile-server
|
||||
packages/fxa-profile-server/var
|
||||
|
|
|
@ -135,7 +135,7 @@ AUTH="fxa-auth-server"
|
|||
# Copy .ftl files for payments, settings, and auth (emails)
|
||||
case "$MODULE" in
|
||||
"$PAYMENTS")
|
||||
copy_ftl "main"
|
||||
copy_ftl "payments"
|
||||
;;
|
||||
"$SETTINGS")
|
||||
copy_ftl "settings"
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# copied l10n files
|
||||
/public/fxa-content-server-l10n
|
||||
/public/locales/git-head.txt
|
||||
/public/locales/**/*.ftl
|
||||
|
||||
# must exclude the original that's copied _into_ fxa-content-server-l10n
|
||||
!/public/locales/en-US/*.ftl
|
||||
|
||||
/server/lib/fxa-content-server-l10n
|
|
@ -95,7 +95,7 @@ export const MockApp = ({
|
|||
<AppLocalizationProvider
|
||||
baseDir="./locales"
|
||||
userLocales={languages}
|
||||
bundles={['main']}
|
||||
bundles={['payments']}
|
||||
>
|
||||
<StripeProvider stripe={mockStripe}>
|
||||
<MockLoader>
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = function (grunt) {
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
concat: {
|
||||
ftl: {
|
||||
src: [
|
||||
// 'src/branding.ftl' is temporary
|
||||
// and will be replaced with '../fxa-shared/lib/l10n/branding.ftl'
|
||||
// in a later ticket - will require coordination with l10n to resolve
|
||||
// conflicting IDs for identical terms.
|
||||
'src/branding.ftl',
|
||||
'src/**/*.ftl',
|
||||
],
|
||||
dest: 'public/locales/en/payments.ftl',
|
||||
},
|
||||
|
||||
// We need this for tests because we pull the latest from `fxa-content-server-l10n`
|
||||
// and place those in our `public` directory at `postinstall` time, and sometimes we have
|
||||
// FTL updates on our side that haven't landed yet on the l10n side. We want to test
|
||||
// against _our_ latest, and not necessarily the l10n repo's latest.
|
||||
'ftl-test': {
|
||||
src: ['src/branding.ftl', 'src/**/*.ftl'],
|
||||
dest: 'test/payments.ftl',
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
ftl: {
|
||||
files: ['src/**/*.ftl'],
|
||||
tasks: ['merge-ftl'],
|
||||
options: {
|
||||
interrupt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
|
||||
grunt.registerTask('merge-ftl', ['concat:ftl']);
|
||||
grunt.registerTask('merge-ftl:test', ['concat:ftl-test']);
|
||||
grunt.registerTask('watch-ftl', ['watch:ftl']);
|
||||
};
|
|
@ -84,6 +84,10 @@ We use rescripts and yarn workspaces to manage our node packages. If you are enc
|
|||
|
||||
This launch config currently only works for the tests under the `/src` directory in the `fxa-payments-server` package, not the `/server` directory.
|
||||
|
||||
### L10N
|
||||
|
||||
Strings are automatically extracted to the [`fxa-content-server-l10n` repo](https://github.com/mozilla/fxa-content-server-l10n) where they reach Pontoon for translations to occur by our l10n team and contributors. This is achieved by concatenating all of our .ftl (Fluent) files into a single `payments.ftl` file with the `merge-ftl` grunttask, and the script that runs in `fxa-content-server-l10n` on a weekly cadence. For more detailed information, check out the [ecosystem platform l10n](https://mozilla.github.io/ecosystem-platform/reference/localization) doc.
|
||||
|
||||
## License
|
||||
|
||||
MPL-2.0
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
basepath = "."
|
||||
|
||||
[[paths]]
|
||||
reference = "public/locales/en-US/*.ftl"
|
||||
reference = "public/locales/en/*.ftl"
|
||||
l10n = "public/locales/{locale}/*.ftl"
|
||||
|
|
|
@ -8,19 +8,22 @@
|
|||
"lint": "npm-run-all --parallel lint:eslint",
|
||||
"lint:eslint": "eslint . .storybook",
|
||||
"audit": "npm audit --json | audit-filter --nsp-config=.nsprc --audit=-",
|
||||
"start": "tsc --build ../fxa-react && npm run build-css && pm2 start pm2.config.js && ../../_scripts/check-url.sh localhost:3031/__lbheartbeat__",
|
||||
"start": "tsc --build ../fxa-react && npm run build-css && grunt merge-ftl && pm2 start pm2.config.js && ../../_scripts/check-url.sh localhost:3031/__lbheartbeat__",
|
||||
"stop": "pm2 stop pm2.config.js",
|
||||
"restart": "tsc --build ../fxa-react && npm run build-css && pm2 restart pm2.config.js",
|
||||
"delete": "pm2 delete pm2.config.js",
|
||||
"build": "tsc --build ../fxa-react && NODE_ENV=production npm run build-css && SKIP_PREFLIGHT_CHECK=true PUBLIC_URL=/ INLINE_RUNTIME_CHUNK=false CI=false rescripts build",
|
||||
"eject": "react-scripts eject",
|
||||
"test": "npm-run-all test:frontend test:server",
|
||||
"test:frontend": "SKIP_PREFLIGHT_CHECK=true PUBLIC_URL=/ INLINE_RUNTIME_CHUNK=false rescripts test --watchAll=false",
|
||||
"test:frontend": "yarn merge-ftl:test && SKIP_PREFLIGHT_CHECK=true PUBLIC_URL=/ INLINE_RUNTIME_CHUNK=false rescripts test --watchAll=false",
|
||||
"test:frontend:watch": "SKIP_PREFLIGHT_CHECK=true PUBLIC_URL=/ INLINE_RUNTIME_CHUNK=false rescripts test",
|
||||
"test:server": "jest --runInBand --coverage --verbose --config server/jest.config.js --forceExit",
|
||||
"format": "prettier --write --config ../../_dev/.prettierrc '**'",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "NODE_ENV=production npm run build-css && build-storybook && cp -r public/locales public/images storybook-static/"
|
||||
"build-storybook": "NODE_ENV=production npm run build-css && build-storybook && cp -r public/images storybook-static/",
|
||||
"merge-ftl": "grunt merge-ftl",
|
||||
"merge-ftl:test": "grunt merge-ftl:test",
|
||||
"watch-ftl": "grunt watch-ftl"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
@ -87,6 +90,10 @@
|
|||
"eslint-plugin-jest": "^27.1.3",
|
||||
"eslint-plugin-react": "^7.31.10",
|
||||
"express-http-proxy": "^1.6.3",
|
||||
"grunt": "^1.5.3",
|
||||
"grunt-cli": "^1.4.3",
|
||||
"grunt-contrib-concat": "^2.1.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"intl": "1.2.5",
|
||||
"jest": "27.5.1",
|
||||
|
|
|
@ -72,5 +72,14 @@ module.exports = {
|
|||
ignore_watch: ['src/styles/tailwind.out.*'],
|
||||
time: true,
|
||||
},
|
||||
{
|
||||
name: 'payments-ftl',
|
||||
script: 'yarn grunt watch-ftl',
|
||||
cwd: __dirname,
|
||||
filter_env: ['npm_'],
|
||||
max_restarts: '1',
|
||||
min_uptime: '2m',
|
||||
time: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,472 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
## Branding
|
||||
|
||||
project-brand = Firefox Accounts
|
||||
-brand-name-mozilla = Mozilla
|
||||
-brand-name-firefox = Firefox
|
||||
-brand-name-paypal = PayPal
|
||||
-brand-name-stripe = Stripe
|
||||
-brand-name-google = Google
|
||||
-brand-name-apple = Apple
|
||||
-brand-name-pocket = Pocket
|
||||
|
||||
# The following are not terms because they are not used directly in messages,
|
||||
# but rather looked up in code and passed into the message as variables.
|
||||
brand-name-google-play = { -brand-name-google } Play Store
|
||||
# App Store here refers to Apple's App Store not the generic app store.
|
||||
brand-name-apple-app-store = App Store
|
||||
|
||||
document =
|
||||
.title = Firefox Accounts
|
||||
|
||||
brand-name-firefox-logo = { -brand-name-firefox } logo
|
||||
|
||||
## General aria-label
|
||||
|
||||
close-aria =
|
||||
.aria-label = Close modal
|
||||
|
||||
# Aria label for spinner image indicating data is loading
|
||||
app-loading-spinner-aria-label-loading = Loading...
|
||||
|
||||
## App error dialog
|
||||
|
||||
general-error-heading = General application error
|
||||
|
||||
basic-error-message = Something went wrong. Please try again later.
|
||||
payment-error-1 = Hmm. There was a problem authorizing your payment. Try again or get in touch with your card issuer.
|
||||
payment-error-2 = Hmm. There was a problem authorizing your payment. Get in touch with your card issuer.
|
||||
payment-error-3b = An unexpected error has occurred while processing your payment, please try again.
|
||||
payment-error-retry-button = Try again
|
||||
payment-error-manage-subscription-button = Manage my subscription
|
||||
|
||||
country-currency-mismatch = The currency of this subscription is not valid for the country associated with your payment.
|
||||
currency-currency-mismatch = Sorry. You can’t switch between currencies.
|
||||
|
||||
no-subscription-change = Sorry. You can’t change your subscription plan.
|
||||
|
||||
# $mobileAppStore (String) - "Google Play Store" or "App Store", localized when the translation is available.
|
||||
iap-already-subscribed = You’re already subscribed through the { $mobileAppStore }.
|
||||
|
||||
expired-card-error = It looks like your credit card has expired. Try another card.
|
||||
insufficient-funds-error = It looks like your card has insufficient funds. Try another card.
|
||||
withdrawal-count-limit-exceeded-error = It looks like this transaction will put you over your credit limit. Try another card.
|
||||
charge-exceeds-source-limit = It looks like this transaction will put you over your daily credit limit. Try another card or in 24 hours.
|
||||
instant-payouts-unsupported = It looks like your debit card isn’t setup for instant payments. Try another debit or credit card.
|
||||
duplicate-transaction = Hmm. Looks like an identical transaction was just sent. Check your payment history.
|
||||
coupon-expired = It looks like that promo code has expired.
|
||||
card-error = Your transaction could not be processed. Please verify your credit card information and try again.
|
||||
|
||||
# $productName (String) - The name of the subscribed product.
|
||||
fxa-account-signup-error-2 = A system error caused your { $productName } sign-up to fail. Your payment method has not been charged. Please try again.
|
||||
newsletter-signup-error = You’re not signed up for product update emails. You can try again in your account settings.
|
||||
fxa-post-passwordless-sub-error = Subscription confirmed, but the confirmation page failed to load. Please check your email to set up your account.
|
||||
|
||||
## IAP upgrade errors
|
||||
|
||||
# $productName (String) - The name of the subscribed product.
|
||||
iap-upgrade-already-subscribed = You already have a { $productName } subscription via the { -brand-name-google } or { -brand-name-apple } app stores.
|
||||
iap-upgrade-no-bundle-support = We don’t support upgrades for these subscriptions, but we will soon.
|
||||
iap-upgrade-contact-support = You can still get this product — please contact support so we can help you.
|
||||
iap-upgrade-get-help-button = Get help
|
||||
|
||||
## Settings
|
||||
|
||||
settings-home = Account Home
|
||||
settings-subscriptions-title = Subscriptions
|
||||
|
||||
## Legal footer
|
||||
|
||||
terms = Terms of Service
|
||||
privacy = Privacy Notice
|
||||
terms-download = Download Terms
|
||||
|
||||
## Subscription titles
|
||||
|
||||
subscription-create-title = Set up your subscription
|
||||
subscription-success-title = Subscription confirmation
|
||||
subscription-processing-title = Confirming subscription…
|
||||
subscription-error-title = Error confirming subscription…
|
||||
subscription-noplanchange-title = This subscription plan change is not supported
|
||||
subscription-iapsubscribed-title = Already subscribed
|
||||
subscription-iaperrorupgrade-title = We can’t upgrade you quite yet
|
||||
|
||||
## $productName (String) - The name of the subscribed product.
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
||||
# $intervalCount (Number) - The interval between payments, in days.
|
||||
day-based-plan-details-amount = { $intervalCount ->
|
||||
[one] { $productName } billed { $amount } daily
|
||||
*[other] { $productName } billed { $amount } every { $intervalCount } days
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in weeks.
|
||||
week-based-plan-details-amount = { $intervalCount ->
|
||||
[one] { $productName } billed { $amount } weekly
|
||||
*[other] { $productName } billed { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in months.
|
||||
month-based-plan-details-amount = { $intervalCount ->
|
||||
[one] { $productName } billed { $amount } monthly
|
||||
*[other] { $productName } billed { $amount } every { $intervalCount } months
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in years.
|
||||
year-based-plan-details-amount = { $intervalCount ->
|
||||
[one] { $productName } billed { $amount } yearly
|
||||
*[other] { $productName } billed { $amount } every { $intervalCount } years
|
||||
}
|
||||
|
||||
## Product route
|
||||
|
||||
product-plan-error =
|
||||
.title = Problem loading plans
|
||||
product-profile-error =
|
||||
.title = Problem loading profile
|
||||
product-customer-error =
|
||||
.title = Problem loading customer
|
||||
product-plan-not-found = Plan not found
|
||||
product-no-such-plan = No such plan for this product.
|
||||
|
||||
## Payment legal blurb
|
||||
|
||||
payment-legal-copy-stripe-and-paypal-2 = { -brand-name-mozilla } uses { -brand-name-stripe } and { -brand-name-paypal } for secure payment processing.
|
||||
payment-legal-link-stripe-paypal = <stripePrivacyLink>{ -brand-name-stripe } privacy policy</stripePrivacyLink> <paypalPrivacyLink>{ -brand-name-paypal } privacy policy</paypalPrivacyLink>
|
||||
|
||||
payment-legal-copy-paypal = { -brand-name-mozilla } uses { -brand-name-paypal } for secure payment processing.
|
||||
payment-legal-link-paypal-2 = <paypalPrivacyLink>{ -brand-name-paypal } privacy policy</paypalPrivacyLink>
|
||||
|
||||
payment-legal-copy-stripe-2 = { -brand-name-mozilla } uses { -brand-name-stripe } for secure payment processing.
|
||||
payment-legal-link-stripe-3 = <stripePrivacyLink>{ -brand-name-stripe } privacy policy</stripePrivacyLink>
|
||||
|
||||
## Payment form
|
||||
|
||||
payment-name =
|
||||
.placeholder = Full Name
|
||||
.label = Name as it appears on your card
|
||||
payment-cc =
|
||||
.label = Your card
|
||||
payment-ccn =
|
||||
.label = Card number
|
||||
payment-exp =
|
||||
.label = Expiration
|
||||
payment-cvc =
|
||||
.label = CVC
|
||||
payment-zip =
|
||||
.label = ZIP code
|
||||
|
||||
payment-confirm-with-legal-links-static = I authorize { -brand-name-mozilla }, maker of { -brand-name-firefox } products, to charge my payment method for the amount shown, according to <termsOfServiceLink>Terms of Service</termsOfServiceLink> and <privacyNoticeLink>Privacy Notice</privacyNoticeLink>, until I cancel my subscription.
|
||||
|
||||
payment-cancel-btn = Cancel
|
||||
payment-update-btn = Update
|
||||
payment-pay-btn = Pay now
|
||||
payment-pay-with-paypal-btn = Pay with {-brand-name-paypal}
|
||||
|
||||
payment-validate-name-error = Please enter your name
|
||||
payment-validate-zip-required = Zip code is required
|
||||
payment-validate-zip-short = Zip code is too short
|
||||
|
||||
## Subscription redirect
|
||||
|
||||
sub-redirect-ready = Your subscription is ready
|
||||
sub-redirect-copy = Please take a moment to tell us about your experience.
|
||||
sub-redirect-skip-survey = No thanks, just take me to my product.
|
||||
|
||||
## Fields
|
||||
|
||||
default-input-error = This field is required
|
||||
input-error-is-required = { $label } is required
|
||||
|
||||
## Subscription upgrade
|
||||
|
||||
product-plan-change-heading = Review your change
|
||||
sub-change-failed = Plan change failed
|
||||
sub-update-payment-title = Payment information
|
||||
sub-update-card-exp = Expires { $cardExpMonth }/{ $cardExpYear }
|
||||
sub-update-copy =
|
||||
Your plan will change immediately, and you’ll be charged an adjusted
|
||||
amount for the rest of your billing cycle. Starting { $startingDate }
|
||||
you’ll be charged the full amount.
|
||||
|
||||
##
|
||||
|
||||
sub-change-submit = Confirm change
|
||||
sub-change-indicator =
|
||||
.aria-label = change indicator
|
||||
sub-update-current-plan-label = Current plan
|
||||
sub-update-new-plan-label = New plan
|
||||
sub-update-total-label = New total
|
||||
|
||||
## Subscription upgrade plan details
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
||||
# $intervalCount (Number) - The interval between payments, in days.
|
||||
plan-price-day = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in weeks.
|
||||
plan-price-week = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in months.
|
||||
plan-price-month = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in years.
|
||||
plan-price-year = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
||||
|
||||
## Subscription billing details
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
||||
# $intervalCount (Number) - The interval between payments, in days.
|
||||
sub-plan-price-day = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in weeks.
|
||||
sub-plan-price-week = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in months.
|
||||
sub-plan-price-month = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in years.
|
||||
sub-plan-price-year = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
||||
|
||||
## $date (Date) - The date for the next time a charge will occur.
|
||||
|
||||
sub-next-bill = Next billed on { $date }
|
||||
sub-expires-on = Expires on { $date }
|
||||
|
||||
##
|
||||
|
||||
pay-update-card-exp = Expires { $expirationDate }
|
||||
pay-update-change-btn = Change
|
||||
|
||||
## reactivate
|
||||
## $name (String) - The name of the subscribed product.
|
||||
|
||||
reactivate-confirm-dialog-header = Want to keep using { $name }?
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $last (String) - The last 4 digits of the card that will be charged
|
||||
# $endDate (Date) - Last day of product access
|
||||
reactivate-confirm-copy =
|
||||
Your access to { $name } will continue, and your billing cycle
|
||||
and payment will stay the same. Your next charge will be
|
||||
{ $amount } to the card ending in { $last } on { $endDate }.
|
||||
# Alternate copy used when a payment method is not available, e.g. for free trials
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $endDate (Date) - Last day of product access
|
||||
reactivate-confirm-without-payment-method-copy =
|
||||
Your access to { $name } will continue, and your billing cycle
|
||||
and payment will stay the same. Your next charge will be
|
||||
{ $amount } on { $endDate }.
|
||||
reactivate-confirm-button = Resubscribe
|
||||
|
||||
## $date (Date) - Last day of product access
|
||||
|
||||
reactivate-panel-date = You cancelled your subscription on { $date }.
|
||||
reactivate-panel-copy = You will lose access to { $name } on <strong>{ $date }</strong>.
|
||||
reactivate-success-copy = Thanks! You’re all set.
|
||||
reactivate-success-button = Close
|
||||
|
||||
## Subscription item
|
||||
## $name (String) - The name of the subscribed product.
|
||||
## $period (Date) - The last day of product access
|
||||
|
||||
sub-item-missing = Problem loading subscriptions
|
||||
sub-item-missing-msg = Please try again later.
|
||||
sub-item-no-such-plan = No such plan for this subscription.
|
||||
sub-item-cancel-sub = Cancel Subscription
|
||||
sub-item-stay-sub = Stay Subscribed
|
||||
sub-item-cancel-msg =
|
||||
You will no longer be able to use { $name } after
|
||||
{ $period }, the last day of your billing cycle.
|
||||
sub-item-cancel-confirm =
|
||||
Cancel my access and my saved information within
|
||||
{ $name } on { $period }
|
||||
invoice-not-found = Subsequent invoice not found
|
||||
sub-item-no-such-subsequent-invoice = Subsequent invoice not found for this subscription.
|
||||
|
||||
## Subscription iap item
|
||||
|
||||
sub-iap-item-google-purchase = { -brand-name-google }: In-App purchase
|
||||
sub-iap-item-apple-purchase = { -brand-name-apple }: In-App purchase
|
||||
sub-iap-item-manage-button = Manage
|
||||
|
||||
account-activated = Your account is activated, <userEl/>
|
||||
|
||||
## Subscription route index
|
||||
|
||||
sub-route-idx-updating = Updating billing information…
|
||||
sub-route-idx-reactivating = Reactivating subscription failed
|
||||
sub-route-idx-cancel-failed = Cancelling subscription failed
|
||||
sub-route-idx-contact = Contact Support
|
||||
sub-route-idx-cancel-msg-title = We’re sorry to see you go
|
||||
# $name (String) - The name of the subscribed product.
|
||||
# $date (Date) - Last day of product access
|
||||
sub-route-idx-cancel-msg =
|
||||
Your { $name } subscription has been cancelled.
|
||||
<br />
|
||||
You will still have access to { $name } until { $date }.
|
||||
sub-route-idx-cancel-aside =
|
||||
Have questions? Visit <a>{ -brand-name-mozilla } Support</a>.
|
||||
sub-subscription-error =
|
||||
.title = Problem loading subscriptions
|
||||
sub-customer-error =
|
||||
.title = Problem loading customer
|
||||
sub-invoice-error =
|
||||
.title = Problem loading invoices
|
||||
sub-billing-update-success = Your billing information has been updated successfully
|
||||
sub-route-payment-modal-heading = Invalid billing information
|
||||
sub-route-payment-modal-message = There seems to be an error with your { -brand-name-paypal } account, we need you to take the necessary steps to resolve this payment issue.
|
||||
sub-route-missing-billing-agreement-payment-alert = Invalid payment information; there is an error with your account. <div>Manage</div>
|
||||
sub-route-funding-source-payment-alert = Invalid payment information; there is an error with your account. This alert may take some time to clear after you successfully update your information. <div>Manage</div>
|
||||
pay-update-manage-btn = Manage
|
||||
|
||||
## Subscription create
|
||||
|
||||
sub-guarantee = 30-day money-back guarantee
|
||||
pay-with-heading-other = Select payment option
|
||||
pay-with-heading-card-or = Or pay with card
|
||||
pay-with-heading-card-only = Pay with card
|
||||
|
||||
## Plan details
|
||||
|
||||
plan-details-header = Product details
|
||||
plan-details-show-button = Show details
|
||||
plan-details-hide-button = Hide details
|
||||
plan-details-total-label = Total
|
||||
plan-details-list-price = List Price
|
||||
plan-details-tax = Taxes and Fees
|
||||
|
||||
## Coupons
|
||||
|
||||
coupon-discount = Discount
|
||||
coupon-discount-applied = Discount Reward Applied
|
||||
# Title of container where a user can input a coupon code to get a discount on a subscription.
|
||||
coupon-promo-code = Promo Code
|
||||
# Title of container showing discount coupon code applied to a subscription.
|
||||
coupon-promo-code-applied = Promo Code Applied
|
||||
coupon-submit = Apply
|
||||
coupon-remove = Remove
|
||||
coupon-error = The code you entered is invalid or expired.
|
||||
coupon-error-generic = An error occurred processing the code. Please try again.
|
||||
coupon-error-expired = The code you entered has expired.
|
||||
coupon-error-limit-reached = The code you entered has reached its limit.
|
||||
coupon-error-invalid = The code you entered is invalid.
|
||||
coupon-success = Your plan will automatically renew at the list price.
|
||||
# $couponDurationDate (Date) - The date at which the coupon is no longer valid, and the subscription is billed the list price.
|
||||
coupon-success-repeating = Your plan will automatically renew after { $couponDurationDate } at the list price.
|
||||
coupon-enter-code =
|
||||
.placeholder = Enter Code
|
||||
|
||||
## Payment processing
|
||||
|
||||
payment-processing-message = Please wait while we process your payment…
|
||||
|
||||
## Payment confirmation
|
||||
|
||||
payment-confirmation-alert = Click here to download
|
||||
payment-confirmation-mobile-alert = Didn’t open app? <a>Click Here</a>
|
||||
payment-confirmation-thanks-heading = Thank you!
|
||||
|
||||
## Payment confirmation details
|
||||
## $email (string) - The user's email.
|
||||
## $productName (String) - The name of the subscribed product.
|
||||
|
||||
payment-confirmation-thanks-subheading = A confirmation email has been sent to { $email } with details on how to get started with { $product_name }.
|
||||
payment-confirmation-thanks-heading-account-exists = Thanks, now check your email!
|
||||
|
||||
## $email (string) - The user's email.
|
||||
|
||||
payment-confirmation-thanks-subheading-account-exists = You’ll receive an email at { $email } with instructions for setting up your account, as well as your payment details.
|
||||
payment-confirmation-order-heading = Order details
|
||||
payment-confirmation-invoice-number = Invoice #{ $invoiceNumber }
|
||||
payment-confirmation-billing-heading = Billed to
|
||||
payment-confirmation-details-heading-2 = Payment information
|
||||
payment-confirmation-amount = { $amount } per { $interval }
|
||||
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
||||
# $intervalCount (Number) - The interval between payments, in days.
|
||||
payment-confirmation-amount-day = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in weeks.
|
||||
payment-confirmation-amount-week = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in months.
|
||||
payment-confirmation-amount-month = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in years.
|
||||
payment-confirmation-amount-year = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
||||
|
||||
payment-confirmation-download-button = Continue to download
|
||||
payment-confirmation-cc-card-ending-in = Card ending in { $last4 }
|
||||
|
||||
## New user email form
|
||||
|
||||
new-user-sign-in-link = Already have a { -brand-name-firefox } account? <a>Sign in</a>
|
||||
new-user-step-1 = 1. Create a { -brand-name-firefox } account
|
||||
# "Required" to indicate that the user must use the checkbox below this text to
|
||||
# agree to a payment method's terms of service and privacy notice in order to
|
||||
# continue.
|
||||
new-user-email =
|
||||
.label = Enter your email
|
||||
new-user-confirm-email =
|
||||
.label = Confirm your email
|
||||
new-user-subscribe-product-updates = I’d like to receive product updates from { -brand-name-firefox }
|
||||
new-user-subscribe-product-assurance = We only use your email to create your account. We will never sell it to a third party.
|
||||
new-user-email-validate = Email is not valid
|
||||
new-user-email-validate-confirm = Emails do not match
|
||||
new-user-already-has-account-sign-in = You already have an account. <a>Sign in</a>
|
||||
# $domain (String) - the email domain provided by the user during sign up
|
||||
new-user-invalid-email-domain = Mistyped email? { $domain } does not offer email.
|
||||
new-user-card-title = Enter your card information
|
||||
new-user-submit = Subscribe Now
|
||||
|
||||
manage-pocket-title = Looking for your { -brand-name-pocket } premium subscription?
|
||||
manage-pocket-body-2 = To manage it, <linkExternal>click here</linkExternal>.
|
||||
|
||||
payment-method-header = Choose your payment method
|
||||
# This message is used to indicate the second step in a multi step process.
|
||||
payment-method-header-second-step = 2. { payment-method-header }
|
||||
payment-method-required = Required
|
|
@ -1,23 +0,0 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -159,7 +159,7 @@ describe('App', () => {
|
|||
initialState,
|
||||
storeEnhancers,
|
||||
appPath = '/',
|
||||
navigatorLanguages = ['en', 'en-US'],
|
||||
navigatorLanguages = ['en'],
|
||||
}: {
|
||||
config?: Partial<Config>;
|
||||
initialState?: State;
|
||||
|
|
|
@ -103,7 +103,7 @@ export const App = ({
|
|||
<Head />
|
||||
<AppLocalizationProvider
|
||||
userLocales={navigatorLanguages}
|
||||
bundles={['main']}
|
||||
bundles={['payments']}
|
||||
>
|
||||
<Localized id="document" attrs={{ title: true }}>
|
||||
<AppErrorBoundary>
|
||||
|
@ -176,8 +176,8 @@ export class AppErrorBoundary extends React.Component {
|
|||
|
||||
export const AppErrorDialog = ({ error: { message } }: { error: Error }) => {
|
||||
const { locationReload } = useContext(AppContext);
|
||||
const ariaLabelledBy = "general-application-error-header";
|
||||
const ariaDescribedBy = "general-application-error-description";
|
||||
const ariaLabelledBy = 'general-application-error-header';
|
||||
const ariaDescribedBy = 'general-application-error-description';
|
||||
// TODO: Not displaying the actual error message to the user, just logging it.
|
||||
// Most of these errors will probably be failure to load Stripe widgets.
|
||||
return (
|
||||
|
@ -189,10 +189,15 @@ export const AppErrorDialog = ({ error: { message } }: { error: Error }) => {
|
|||
descId={ariaDescribedBy}
|
||||
>
|
||||
<Localized id="general-error-heading">
|
||||
<h4 id={ariaLabelledBy} data-testid="error-loading-app">General application error</h4>
|
||||
<h4 id={ariaLabelledBy} data-testid="error-loading-app">
|
||||
General application error
|
||||
</h4>
|
||||
</Localized>
|
||||
<Localized id={getErrorMessage({ code: 'api_connection_error' })}>
|
||||
<p id={ariaDescribedBy}> Something went wrong. Please try again later.</p>
|
||||
<p id={ariaDescribedBy}>
|
||||
{' '}
|
||||
Something went wrong. Please try again later.
|
||||
</p>
|
||||
</Localized>
|
||||
</DialogMessage>
|
||||
</SettingsLayout>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
### Terms and messages used in fxa-payments-server
|
||||
|
||||
## Firefox and Mozilla must be treated as a brand.
|
||||
##
|
||||
## They cannot be:
|
||||
## - Transliterated.
|
||||
## - Translated.
|
||||
##
|
||||
## Declension should be avoided where possible, leaving the original
|
||||
## brand unaltered in prominent UI positions.
|
||||
##
|
||||
## For further details, consult:
|
||||
## https://mozilla-l10n.github.io/styleguides/mozilla_general/#brands-copyright-and-trademark
|
||||
|
||||
-brand-name-mozilla = Mozilla
|
||||
-brand-name-firefox = Firefox
|
||||
|
||||
# “Accounts” can be localized, “Firefox” must be treated as a brand.
|
||||
# 'Firefox Accounts' refers to the service
|
||||
project-brand = Firefox Accounts
|
||||
|
||||
## Brands cannot be transliterated or translated. Decelension should be avoided where possible.
|
||||
|
||||
-brand-name-paypal = PayPal
|
||||
-brand-name-stripe = Stripe
|
||||
-brand-name-google = Google
|
||||
-brand-name-apple = Apple
|
||||
-brand-name-pocket = Pocket
|
|
@ -0,0 +1,3 @@
|
|||
## Component - AppLayout
|
||||
|
||||
settings-home = Account Home
|
|
@ -0,0 +1,14 @@
|
|||
## Component - CouponForm
|
||||
|
||||
# Title of container showing discount coupon code applied to a subscription.
|
||||
coupon-promo-code-applied = Promo Code Applied
|
||||
coupon-submit = Apply
|
||||
coupon-remove = Remove
|
||||
coupon-error = The code you entered is invalid or expired.
|
||||
coupon-error-generic = An error occurred processing the code. Please try again.
|
||||
coupon-error-expired = The code you entered has expired.
|
||||
coupon-error-limit-reached = The code you entered has reached its limit.
|
||||
coupon-error-invalid = The code you entered is invalid.
|
||||
# $couponDurationDate (Date) - The date at which the coupon is no longer valid, and the subscription is billed the list price.
|
||||
coupon-enter-code =
|
||||
.placeholder = Enter Code
|
|
@ -0,0 +1,3 @@
|
|||
## Component - Header
|
||||
|
||||
brand-name-firefox-logo = { -brand-name-firefox } logo
|
|
@ -19,7 +19,7 @@ const profile: Profile = {
|
|||
email: 'foxy@firefox.com',
|
||||
amrValues: ['amrval'],
|
||||
avatarDefault: true,
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: false,
|
||||
uid: 'UIDSTRINGHERE',
|
||||
metricsEnabled: true,
|
||||
|
|
|
@ -15,7 +15,7 @@ let userProfile: Profile = {
|
|||
email: 'foxy@firefox.com',
|
||||
amrValues: ['amrval'],
|
||||
avatarDefault: true,
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: false,
|
||||
uid: 'UIDSTRINGHERE',
|
||||
metricsEnabled: true,
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
## Component - NewUserEmailForm
|
||||
|
||||
new-user-sign-in-link = Already have a { -brand-name-firefox } account? <a>Sign in</a>
|
||||
# "Required" to indicate that the user must use the checkbox below this text to
|
||||
# agree to a payment method's terms of service and privacy notice in order to
|
||||
# continue.
|
||||
new-user-email =
|
||||
.label = Enter your email
|
||||
new-user-confirm-email =
|
||||
.label = Confirm your email
|
||||
new-user-subscribe-product-updates = I’d like to receive product updates from { -brand-name-firefox }
|
||||
new-user-subscribe-product-assurance = We only use your email to create your account. We will never sell it to a third party.
|
||||
new-user-email-validate = Email is not valid
|
||||
new-user-email-validate-confirm = Emails do not match
|
||||
new-user-already-has-account-sign-in = You already have an account. <a>Sign in</a>
|
||||
# $domain (String) - the email domain provided by the user during sign up
|
||||
new-user-invalid-email-domain = Mistyped email? { $domain } does not offer email.
|
|
@ -0,0 +1,44 @@
|
|||
## Component - PaymentConfirmation
|
||||
|
||||
payment-confirmation-thanks-heading = Thank you!
|
||||
payment-confirmation-thanks-heading-account-exists = Thanks, now check your email!
|
||||
|
||||
# $email (string) - The user's email.
|
||||
# $productName (String) - The name of the subscribed product.
|
||||
payment-confirmation-thanks-subheading = A confirmation email has been sent to { $email } with details on how to get started with { $product_name }.
|
||||
|
||||
# $email (string) - The user's email.
|
||||
payment-confirmation-thanks-subheading-account-exists = You’ll receive an email at { $email } with instructions for setting up your account, as well as your payment details.
|
||||
|
||||
payment-confirmation-order-heading = Order details
|
||||
payment-confirmation-invoice-number = Invoice #{ $invoiceNumber }
|
||||
payment-confirmation-details-heading-2 = Payment information
|
||||
|
||||
payment-confirmation-amount = { $amount } per { $interval }
|
||||
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $intervalCount (Number) - The interval between payments, in days.
|
||||
payment-confirmation-amount-day = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $intervalCount (Number) - The interval between payments, in weeks.
|
||||
payment-confirmation-amount-week = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $intervalCount (Number) - The interval between payments, in months.
|
||||
payment-confirmation-amount-month = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $intervalCount (Number) - The interval between payments, in years.
|
||||
payment-confirmation-amount-year = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
||||
|
||||
payment-confirmation-download-button = Continue to download
|
|
@ -22,7 +22,7 @@ const userProfile: Profile = {
|
|||
email: 'foxy@firefox.com',
|
||||
amrValues: ['amrval'],
|
||||
avatarDefault: true,
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: false,
|
||||
uid: 'UIDSTRINGHERE',
|
||||
metricsEnabled: true,
|
||||
|
|
|
@ -6,11 +6,9 @@ import TestRenderer from 'react-test-renderer';
|
|||
import PaymentConfirmation from './index';
|
||||
import { getLocalizedCurrency } from '../../lib/formats';
|
||||
import { Customer, Plan } from '../../store/types';
|
||||
import {
|
||||
MOCK_PLANS,
|
||||
setupFluentLocalizationTest,
|
||||
getLocalizedMessage,
|
||||
} from '../../lib/test-utils';
|
||||
import { MOCK_PLANS, getLocalizedMessage } from '../../lib/test-utils';
|
||||
import { getFtlBundle } from 'fxa-react/lib/test-utils';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
import AppContext, { defaultAppContext } from '../../lib/AppContext';
|
||||
import { CouponDetails } from 'fxa-shared/dto/auth/payments/coupon';
|
||||
import { MozillaSubscriptionTypes } from 'fxa-shared/subscriptions/types';
|
||||
|
@ -22,7 +20,7 @@ const userProfile = {
|
|||
email: 'foxy@firefox.com',
|
||||
amrValues: ['amrval'],
|
||||
avatarDefault: true,
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: false,
|
||||
uid: 'UIDSTRINGHERE',
|
||||
metricsEnabled: false,
|
||||
|
@ -535,7 +533,10 @@ describe('PaymentConfirmation', () => {
|
|||
});
|
||||
|
||||
describe('Fluent Translations for Plan Billing Description', () => {
|
||||
const bundle = setupFluentLocalizationTest('en-US');
|
||||
let bundle: FluentBundle;
|
||||
beforeAll(async () => {
|
||||
bundle = await getFtlBundle('payments');
|
||||
});
|
||||
const amount = getLocalizedCurrency(500, 'USD');
|
||||
const args = {
|
||||
amount,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
## Component - PaymentConsentCheckbox
|
||||
|
||||
payment-confirm-with-legal-links-static = I authorize { -brand-name-mozilla }, maker of { -brand-name-firefox } products, to charge my payment method for the amount shown, according to <termsOfServiceLink>Terms of Service</termsOfServiceLink> and <privacyNoticeLink>Privacy Notice</privacyNoticeLink>, until I cancel my subscription.
|
|
@ -0,0 +1,12 @@
|
|||
## Component - PaymentErrorView
|
||||
|
||||
payment-error-retry-button = Try again
|
||||
payment-error-manage-subscription-button = Manage my subscription
|
||||
|
||||
## Component - PaymentErrorView - IAP upgrade errors
|
||||
|
||||
# $productName (String) - The name of the subscribed product.
|
||||
iap-upgrade-already-subscribed = You already have a { $productName } subscription via the { -brand-name-google } or { -brand-name-apple } app stores.
|
||||
iap-upgrade-no-bundle-support = We don’t support upgrades for these subscriptions, but we will soon.
|
||||
iap-upgrade-contact-support = You can still get this product — please contact support so we can help you.
|
||||
iap-upgrade-get-help-button = Get help
|
|
@ -1,10 +1,9 @@
|
|||
import React from 'react';
|
||||
import { render, cleanup, fireEvent, act } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import {
|
||||
setupFluentLocalizationTest,
|
||||
getLocalizedMessage,
|
||||
} from '../../lib/test-utils';
|
||||
import { getLocalizedMessage } from '../../lib/test-utils';
|
||||
import { getFtlBundle } from 'fxa-react/lib/test-utils';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
|
||||
import { PaymentErrorView } from './index';
|
||||
import SubscriptionTitle, { titles } from '../SubscriptionTitle';
|
||||
|
@ -22,7 +21,10 @@ jest.mock('react-router-dom', () => {
|
|||
|
||||
afterEach(cleanup);
|
||||
describe('PaymentErrorView test with l10n', () => {
|
||||
const bundle = setupFluentLocalizationTest('en-US');
|
||||
let bundle: FluentBundle;
|
||||
beforeAll(async () => {
|
||||
bundle = await getFtlBundle('payments');
|
||||
});
|
||||
|
||||
it('renders as expected', () => {
|
||||
const { queryByTestId, queryByAltText } = render(
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
## Component - PaymentForm
|
||||
|
||||
payment-name =
|
||||
.placeholder = Full Name
|
||||
.label = Name as it appears on your card
|
||||
payment-cc =
|
||||
.label = Your card
|
||||
|
||||
payment-cancel-btn = Cancel
|
||||
payment-update-btn = Update
|
||||
payment-pay-btn = Pay now
|
||||
payment-pay-with-paypal-btn = Pay with {-brand-name-paypal}
|
||||
|
||||
payment-validate-name-error = Please enter your name
|
|
@ -0,0 +1,10 @@
|
|||
## Component - PaymentLegalBlurb
|
||||
|
||||
payment-legal-copy-stripe-and-paypal-2 = { -brand-name-mozilla } uses { -brand-name-stripe } and { -brand-name-paypal } for secure payment processing.
|
||||
payment-legal-link-stripe-paypal = <stripePrivacyLink>{ -brand-name-stripe } privacy policy</stripePrivacyLink> <paypalPrivacyLink>{ -brand-name-paypal } privacy policy</paypalPrivacyLink>
|
||||
|
||||
payment-legal-copy-paypal = { -brand-name-mozilla } uses { -brand-name-paypal } for secure payment processing.
|
||||
payment-legal-link-paypal-2 = <paypalPrivacyLink>{ -brand-name-paypal } privacy policy</paypalPrivacyLink>
|
||||
|
||||
payment-legal-copy-stripe-2 = { -brand-name-mozilla } uses { -brand-name-stripe } for secure payment processing.
|
||||
payment-legal-link-stripe-3 = <stripePrivacyLink>{ -brand-name-stripe } privacy policy</stripePrivacyLink>
|
|
@ -0,0 +1,6 @@
|
|||
## Component - PaymentMethodHeader
|
||||
|
||||
payment-method-header = Choose your payment method
|
||||
# This message is used to indicate the second step in a multi step process.
|
||||
payment-method-header-second-step = 2. { payment-method-header }
|
||||
payment-method-required = Required
|
|
@ -4,11 +4,9 @@ import { act, cleanup, fireEvent, render } from '@testing-library/react';
|
|||
import React from 'react';
|
||||
|
||||
import { PaymentMethodHeader, PaymentMethodHeaderType } from '.';
|
||||
import {
|
||||
getLocalizedMessage,
|
||||
MOCK_PLANS,
|
||||
setupFluentLocalizationTest,
|
||||
} from '../../lib/test-utils';
|
||||
import { getLocalizedMessage, MOCK_PLANS } from '../../lib/test-utils';
|
||||
import { getFtlBundle } from 'fxa-react/lib/test-utils';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
|
||||
jest.mock('../../lib/sentry');
|
||||
|
||||
|
@ -41,7 +39,10 @@ describe('components/PaymentMethodHeader', () => {
|
|||
});
|
||||
|
||||
describe('Fluent localized text', () => {
|
||||
const bundle = setupFluentLocalizationTest('en-US');
|
||||
let bundle: FluentBundle;
|
||||
beforeAll(async () => {
|
||||
bundle = await getFtlBundle('payments');
|
||||
});
|
||||
|
||||
it('returns correct heading without prefix', async () => {
|
||||
const msgId = 'payment-method-header';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
## Component - PaymentProcessing
|
||||
|
||||
payment-processing-message = Please wait while we process your payment…
|
|
@ -1,16 +1,18 @@
|
|||
import React from 'react';
|
||||
import { render, cleanup } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import {
|
||||
setupFluentLocalizationTest,
|
||||
getLocalizedMessage,
|
||||
} from '../../lib/test-utils';
|
||||
import { getLocalizedMessage } from '../../lib/test-utils';
|
||||
import { getFtlBundle } from 'fxa-react/lib/test-utils';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
|
||||
import { PaymentProcessing } from './index';
|
||||
|
||||
afterEach(cleanup);
|
||||
describe('PaymentProcessing tests', () => {
|
||||
const bundle = setupFluentLocalizationTest('en-US');
|
||||
let bundle: FluentBundle;
|
||||
beforeAll(async () => {
|
||||
bundle = await getFtlBundle('payments');
|
||||
});
|
||||
|
||||
it('renders as expected', () => {
|
||||
const { queryByTestId } = render(<PaymentProcessing provider="paypal" />);
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
## Component - PaymentProviderDetails
|
||||
|
||||
payment-confirmation-cc-card-ending-in = Card ending in { $last4 }
|
|
@ -0,0 +1,8 @@
|
|||
## Component - PlanDetails
|
||||
|
||||
plan-details-header = Product details
|
||||
plan-details-list-price = List Price
|
||||
plan-details-show-button = Show details
|
||||
plan-details-hide-button = Hide details
|
||||
plan-details-total-label = Total
|
||||
plan-details-tax = Taxes and Fees
|
|
@ -21,7 +21,7 @@ const userProfile: Profile = {
|
|||
email: 'foxy@firefox.com',
|
||||
amrValues: ['amrval'],
|
||||
avatarDefault: true,
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: false,
|
||||
uid: 'UIDSTRINGHERE',
|
||||
metricsEnabled: true,
|
||||
|
|
|
@ -9,11 +9,9 @@ import TestRenderer from 'react-test-renderer';
|
|||
|
||||
import PlanDetails from './index';
|
||||
import { getLocalizedCurrency } from '../../lib/formats';
|
||||
import {
|
||||
MOCK_PLANS,
|
||||
setupFluentLocalizationTest,
|
||||
getLocalizedMessage,
|
||||
} from '../../lib/test-utils';
|
||||
import { MOCK_PLANS, getLocalizedMessage } from '../../lib/test-utils';
|
||||
import { getFtlBundle } from 'fxa-react/lib/test-utils';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
import { updateConfig } from '../../lib/config';
|
||||
import { Plan } from 'fxa-shared/subscriptions/types';
|
||||
import { CouponDetails } from 'fxa-shared/dto/auth/payments/coupon';
|
||||
|
@ -26,7 +24,7 @@ const userProfile: Profile = {
|
|||
email: 'foxy@firefox.com',
|
||||
amrValues: ['amrval'],
|
||||
avatarDefault: true,
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: false,
|
||||
uid: 'UIDSTRINGHERE',
|
||||
metricsEnabled: true,
|
||||
|
@ -581,7 +579,10 @@ describe('PlanDetails', () => {
|
|||
});
|
||||
|
||||
describe('Fluent Translations for Plan Billing Description', () => {
|
||||
const bundle = setupFluentLocalizationTest('en-US');
|
||||
let bundle: FluentBundle;
|
||||
beforeAll(async () => {
|
||||
bundle = await getFtlBundle('payments');
|
||||
});
|
||||
const amount = getLocalizedCurrency(500, 'USD');
|
||||
const args = {
|
||||
amount,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
## Component - PlanErrorDialog
|
||||
|
||||
product-no-such-plan = No such plan for this product.
|
|
@ -0,0 +1,10 @@
|
|||
## Component - SubscriptionTitle
|
||||
|
||||
subscription-create-title = Set up your subscription
|
||||
subscription-success-title = Subscription confirmation
|
||||
subscription-processing-title = Confirming subscription…
|
||||
subscription-error-title = Error confirming subscription…
|
||||
subscription-noplanchange-title = This subscription plan change is not supported
|
||||
subscription-iapsubscribed-title = Already subscribed
|
||||
|
||||
sub-guarantee = 30-day money-back guarantee
|
|
@ -4,10 +4,9 @@ import '@testing-library/jest-dom/extend-expect';
|
|||
|
||||
import SubscriptionTitle, { SubscriptionTitleProps, titles } from './index';
|
||||
|
||||
import {
|
||||
setupFluentLocalizationTest,
|
||||
getLocalizedMessage,
|
||||
} from '../../lib/test-utils';
|
||||
import { getLocalizedMessage } from '../../lib/test-utils';
|
||||
import { getFtlBundle } from 'fxa-react/lib/test-utils';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
|
||||
const defaultProps: SubscriptionTitleProps = {
|
||||
screenType: 'create',
|
||||
|
@ -16,7 +15,10 @@ const defaultProps: SubscriptionTitleProps = {
|
|||
afterEach(cleanup);
|
||||
|
||||
describe('SubscriptionTitle', () => {
|
||||
const bundle = setupFluentLocalizationTest('en-US');
|
||||
let bundle: FluentBundle;
|
||||
beforeAll(async () => {
|
||||
bundle = await getFtlBundle('payments');
|
||||
});
|
||||
it('renders as expected', async () => {
|
||||
const subject = () => {
|
||||
return render(<SubscriptionTitle {...defaultProps} />);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
## Component - TermsAndPrivacy
|
||||
|
||||
terms = Terms of Service
|
||||
privacy = Privacy Notice
|
||||
terms-download = Download Terms
|
|
@ -0,0 +1,4 @@
|
|||
## Component - Fields
|
||||
|
||||
default-input-error = This field is required
|
||||
input-error-is-required = { $label } is required
|
|
@ -0,0 +1,56 @@
|
|||
## App-level string(s) and messages shared by multiple components or routes
|
||||
|
||||
document =
|
||||
.title = Firefox Accounts
|
||||
|
||||
# General aria-label for closing modals
|
||||
close-aria =
|
||||
.aria-label = Close modal
|
||||
|
||||
# Aria label for spinner image indicating data is loading
|
||||
app-loading-spinner-aria-label-loading = Loading...
|
||||
|
||||
settings-subscriptions-title = Subscriptions
|
||||
|
||||
# Title of container where a user can input a coupon code to get a discount on a subscription.
|
||||
coupon-promo-code = Promo Code
|
||||
|
||||
## Subscription upgrade plan details - shared by multiple components, including plan details and payment form
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
||||
# $intervalCount (Number) - The interval between payments, in days.
|
||||
plan-price-day = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in weeks.
|
||||
plan-price-week = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in months.
|
||||
plan-price-month = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in years.
|
||||
plan-price-year = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
||||
.title = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
|
@ -29,7 +29,7 @@ export const defaultAppContext = {
|
|||
matchMedia: () => false,
|
||||
matchMediaDefault: (query: string) => matchMedia(query),
|
||||
navigateToUrl: noopFunction,
|
||||
navigatorLanguages: ['en-US', 'en'],
|
||||
navigatorLanguages: ['en'],
|
||||
stripePromise: Promise.resolve(null),
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
## Error messages
|
||||
|
||||
# App error dialog
|
||||
general-error-heading = General application error
|
||||
|
||||
basic-error-message = Something went wrong. Please try again later.
|
||||
payment-error-1 = Hmm. There was a problem authorizing your payment. Try again or get in touch with your card issuer.
|
||||
payment-error-2 = Hmm. There was a problem authorizing your payment. Get in touch with your card issuer.
|
||||
payment-error-3b = An unexpected error has occurred while processing your payment, please try again.
|
||||
expired-card-error = It looks like your credit card has expired. Try another card.
|
||||
insufficient-funds-error = It looks like your card has insufficient funds. Try another card.
|
||||
withdrawal-count-limit-exceeded-error = It looks like this transaction will put you over your credit limit. Try another card.
|
||||
charge-exceeds-source-limit = It looks like this transaction will put you over your daily credit limit. Try another card or in 24 hours.
|
||||
instant-payouts-unsupported = It looks like your debit card isn’t setup for instant payments. Try another debit or credit card.
|
||||
duplicate-transaction = Hmm. Looks like an identical transaction was just sent. Check your payment history.
|
||||
coupon-expired = It looks like that promo code has expired.
|
||||
card-error = Your transaction could not be processed. Please verify your credit card information and try again.
|
||||
country-currency-mismatch = The currency of this subscription is not valid for the country associated with your payment.
|
||||
currency-currency-mismatch = Sorry. You can’t switch between currencies.
|
||||
no-subscription-change = Sorry. You can’t change your subscription plan.
|
||||
# $mobileAppStore (String) - "Google Play Store" or "App Store", localized when the translation is available.
|
||||
iap-already-subscribed = You’re already subscribed through the { $mobileAppStore }.
|
||||
# $productName (String) - The name of the subscribed product.
|
||||
fxa-account-signup-error-2 = A system error caused your { $productName } sign-up to fail. Your payment method has not been charged. Please try again.
|
||||
fxa-post-passwordless-sub-error = Subscription confirmed, but the confirmation page failed to load. Please check your email to set up your account.
|
||||
newsletter-signup-error = You’re not signed up for product update emails. You can try again in your account settings.
|
||||
|
||||
product-plan-error =
|
||||
.title = Problem loading plans
|
||||
product-profile-error =
|
||||
.title = Problem loading profile
|
||||
product-customer-error =
|
||||
.title = Problem loading customer
|
||||
product-plan-not-found = Plan not found
|
||||
|
||||
## Hooks - coupons
|
||||
|
||||
coupon-success = Your plan will automatically renew at the list price.
|
||||
# $couponDurationDate (Date) - The date at which the coupon is no longer valid, and the subscription is billed the list price.
|
||||
coupon-success-repeating = Your plan will automatically renew after { $couponDurationDate } at the list price.
|
|
@ -41,7 +41,7 @@ const FXA_POST_PASSWORDLESS_SUB_ERROR = 'fxa-post-passwordless-sub-error';
|
|||
const errorToErrorMessageMap: { [key: string]: string } = {
|
||||
expired_card: 'expired-card-error',
|
||||
insufficient_funds: 'insufficient-funds-error',
|
||||
withdrawal_count_limit_exceeded: 'withdrawal-count-limit-error',
|
||||
withdrawal_count_limit_exceeded: 'withdrawal-count-limit-exceeded-error',
|
||||
charge_exceeds_source_limit: 'charge-exceeds-source-limit-error',
|
||||
instant_payouts_unsupported: 'instant-payouts-unsupported-error',
|
||||
duplicate_transaction: 'duplicate-transaction-error',
|
||||
|
|
|
@ -32,7 +32,7 @@ export function getLocalizedCurrency(
|
|||
|
||||
/**
|
||||
* This method is to get a default localized currency value for displaying in fallback strings in the event of a Fluent failure.
|
||||
* This returns a string that is formatted accorning to the 'en-US' locale standard to match the rest of our fallback library.
|
||||
* This returns a string that is formatted accorning to the 'en' locale standard to match the rest of our fallback library.
|
||||
*
|
||||
* @param amountInCents
|
||||
* @param currency
|
||||
|
@ -44,7 +44,7 @@ export function getLocalizedCurrencyString(
|
|||
const decimal = (amountInCents || 0) / 100;
|
||||
const options = { ...baseCurrencyOptions, currency };
|
||||
|
||||
return new Intl.NumberFormat('en-US', options).format(decimal);
|
||||
return new Intl.NumberFormat('en', options).format(decimal);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,7 +97,7 @@ export function getLocalizedDate(
|
|||
|
||||
/**
|
||||
* This method is to get a default localized datetime value for displaying in fallback strings in the event of a Fluent failure.
|
||||
* This returns a string that is formatted accorning to the 'en-US' locale standard to match the rest of our fallback library.
|
||||
* This returns a string that is formatted accorning to the 'en' locale standard to match the rest of our fallback library.
|
||||
*
|
||||
* @param unixSeconds
|
||||
* @param numericDate
|
||||
|
@ -111,7 +111,7 @@ export function getLocalizedDateString(
|
|||
|
||||
const options = numericDate ? numericDateOptions : defaultDateOptions;
|
||||
|
||||
return new Intl.DateTimeFormat('en-US', options).format(date);
|
||||
return new Intl.DateTimeFormat('en', options).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,7 @@ export const PROFILE: Profile = {
|
|||
avatarDefault: false,
|
||||
displayName: 'Foo Barson',
|
||||
email: 'foo@example.com',
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: true,
|
||||
uid: '8675309asdf',
|
||||
metricsEnabled: true,
|
||||
|
|
|
@ -9,9 +9,7 @@ import {
|
|||
import ScreenInfo from '../../src/lib/screen-info';
|
||||
import { ReactStripeElements } from 'react-stripe-elements';
|
||||
import nock from 'nock';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { FluentBundle, FluentResource } from '@fluent/bundle';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
|
||||
import { State } from '../store/state';
|
||||
import { Store, createAppStore } from '../../src/store';
|
||||
|
@ -595,7 +593,7 @@ export const MOCK_PLANS: Plan[] = [
|
|||
|
||||
export const MOCK_PROFILE: Profile = {
|
||||
email: 'foo@example.com',
|
||||
locale: 'en-US,en;q=0.5',
|
||||
locale: 'en;q=0.5',
|
||||
amrValues: ['pwd', 'email'],
|
||||
twoFactorAuthentication: false,
|
||||
uid: 'a90fef48240b49b2b6a33d333aee9b13',
|
||||
|
@ -692,24 +690,6 @@ export const MOCK_CUSTOMER_AFTER_SUBSCRIPTION = {
|
|||
],
|
||||
};
|
||||
|
||||
export function setupFluentLocalizationTest(locale: string): FluentBundle {
|
||||
const filepath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'public',
|
||||
'locales',
|
||||
locale,
|
||||
'main.ftl'
|
||||
);
|
||||
const messages = fs.readFileSync(filepath).toString();
|
||||
const resource = new FluentResource(messages);
|
||||
const bundle = new FluentBundle(locale, { useIsolating: false });
|
||||
bundle.addResource(resource);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
export function getLocalizedMessage(
|
||||
bundle: FluentBundle,
|
||||
msgId: string,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
## Routes - Checkout - New user
|
||||
|
||||
new-user-step-1 = 1. Create a { -brand-name-firefox } account
|
||||
new-user-card-title = Enter your card information
|
||||
new-user-submit = Subscribe Now
|
|
@ -0,0 +1,10 @@
|
|||
## Routes - Product - IapRoadblock
|
||||
|
||||
subscription-iaperrorupgrade-title = We can’t upgrade you quite yet
|
||||
|
||||
# The following are not terms because they are not used directly in messages,
|
||||
# but rather looked up in code and passed into the message as variables.
|
||||
|
||||
brand-name-google-play = { -brand-name-google } Play Store
|
||||
# App Store here refers to Apple's App Store not the generic app store.
|
||||
brand-name-apple-app-store = App Store
|
|
@ -0,0 +1,12 @@
|
|||
## Routes - Product - Subscription upgrade
|
||||
|
||||
product-plan-change-heading = Review your change
|
||||
sub-change-failed = Plan change failed
|
||||
sub-update-copy =
|
||||
Your plan will change immediately, and you’ll be charged an adjusted
|
||||
amount for the rest of your billing cycle. Starting { $startingDate }
|
||||
you’ll be charged the full amount.
|
||||
sub-change-submit = Confirm change
|
||||
sub-update-current-plan-label = Current plan
|
||||
sub-update-new-plan-label = New plan
|
||||
sub-update-total-label = New total
|
|
@ -15,9 +15,10 @@ import {
|
|||
import {
|
||||
MockApp,
|
||||
MOCK_PLANS,
|
||||
setupFluentLocalizationTest,
|
||||
getLocalizedMessage,
|
||||
} from '../../../lib/test-utils';
|
||||
import { getFtlBundle } from 'fxa-react/lib/test-utils';
|
||||
import { FluentBundle } from '@fluent/bundle';
|
||||
|
||||
import {
|
||||
CUSTOMER,
|
||||
|
@ -289,7 +290,10 @@ describe('PlanDetailsCard', () => {
|
|||
});
|
||||
|
||||
describe('Fluent Translations for Plan Billing Description', () => {
|
||||
const bundle = setupFluentLocalizationTest('en-US');
|
||||
let bundle: FluentBundle;
|
||||
beforeAll(async () => {
|
||||
bundle = await getFtlBundle('payments');
|
||||
});
|
||||
const amount = getLocalizedCurrency(500, 'USD');
|
||||
const args = {
|
||||
amount,
|
||||
|
|
|
@ -186,7 +186,7 @@ const PROFILE: Profile = {
|
|||
avatarDefault: false,
|
||||
displayName: 'Foo Barson',
|
||||
email: 'foo@example.com',
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: true,
|
||||
uid: '8675309asdf',
|
||||
metricsEnabled: true,
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
## Routes - Subscriptions - Cancel
|
||||
|
||||
sub-item-cancel-sub = Cancel Subscription
|
||||
sub-item-stay-sub = Stay Subscribed
|
||||
|
||||
## $name (String) - The name of the subscribed product.
|
||||
## $period (Date) - The last day of product access
|
||||
sub-item-cancel-msg =
|
||||
You will no longer be able to use { $name } after
|
||||
{ $period }, the last day of your billing cycle.
|
||||
sub-item-cancel-confirm =
|
||||
Cancel my access and my saved information within
|
||||
{ $name } on { $period }
|
||||
|
||||
## Subscription billing details
|
||||
## $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
|
||||
# $intervalCount (Number) - The interval between payments, in days.
|
||||
sub-plan-price-day = { $intervalCount ->
|
||||
[one] { $amount } daily
|
||||
*[other] { $amount } every { $intervalCount } days
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in weeks.
|
||||
sub-plan-price-week = { $intervalCount ->
|
||||
[one] { $amount } weekly
|
||||
*[other] { $amount } every { $intervalCount } weeks
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in months.
|
||||
sub-plan-price-month = { $intervalCount ->
|
||||
[one] { $amount } monthly
|
||||
*[other] { $amount } every { $intervalCount } months
|
||||
}
|
||||
# $intervalCount (Number) - The interval between payments, in years.
|
||||
sub-plan-price-year = { $intervalCount ->
|
||||
[one] { $amount } yearly
|
||||
*[other] { $amount } every { $intervalCount } years
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
## Routes - Subscriptions - Reactivate
|
||||
## $name (String) - The name of the subscribed product.
|
||||
|
||||
reactivate-confirm-dialog-header = Want to keep using { $name }?
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $last (String) - The last 4 digits of the card that will be charged
|
||||
# $endDate (Date) - Last day of product access
|
||||
reactivate-confirm-copy =
|
||||
Your access to { $name } will continue, and your billing cycle
|
||||
and payment will stay the same. Your next charge will be
|
||||
{ $amount } to the card ending in { $last } on { $endDate }.
|
||||
# Alternate copy used when a payment method is not available, e.g. for free trials
|
||||
# $amount (Number) - The amount billed. It will be formatted as currency.
|
||||
# $endDate (Date) - Last day of product access
|
||||
reactivate-confirm-without-payment-method-copy =
|
||||
Your access to { $name } will continue, and your billing cycle
|
||||
and payment will stay the same. Your next charge will be
|
||||
{ $amount } on { $endDate }.
|
||||
reactivate-confirm-button = Resubscribe
|
||||
|
||||
## $date (Date) - Last day of product access
|
||||
|
||||
reactivate-panel-copy = You will lose access to { $name } on <strong>{ $date }</strong>.
|
||||
reactivate-success-copy = Thanks! You’re all set.
|
||||
reactivate-success-button = Close
|
|
@ -0,0 +1,5 @@
|
|||
## Routes - Subscriptions - Subscription iap item
|
||||
|
||||
sub-iap-item-google-purchase = { -brand-name-google }: In-App purchase
|
||||
sub-iap-item-apple-purchase = { -brand-name-apple }: In-App purchase
|
||||
sub-iap-item-manage-button = Manage
|
|
@ -0,0 +1,54 @@
|
|||
## Routes - Subscription
|
||||
|
||||
sub-route-idx-reactivating = Reactivating subscription failed
|
||||
sub-route-idx-cancel-failed = Cancelling subscription failed
|
||||
sub-route-idx-contact = Contact Support
|
||||
sub-route-idx-cancel-msg-title = We’re sorry to see you go
|
||||
# $name (String) - The name of the subscribed product.
|
||||
# $date (Date) - Last day of product access
|
||||
sub-route-idx-cancel-msg =
|
||||
Your { $name } subscription has been cancelled.
|
||||
<br />
|
||||
You will still have access to { $name } until { $date }.
|
||||
sub-route-idx-cancel-aside =
|
||||
Have questions? Visit <a>{ -brand-name-mozilla } Support</a>.
|
||||
|
||||
## Routes - Subscriptions - Errors
|
||||
|
||||
sub-customer-error =
|
||||
.title = Problem loading customer
|
||||
sub-invoice-error =
|
||||
.title = Problem loading invoices
|
||||
sub-billing-update-success = Your billing information has been updated successfully
|
||||
|
||||
## Routes - Subscription - ActionButton
|
||||
|
||||
pay-update-change-btn = Change
|
||||
pay-update-manage-btn = Manage
|
||||
|
||||
## Routes - Subscriptions - Cancel and IapItem
|
||||
## $date (Date) - The date for the next time a charge will occur.
|
||||
|
||||
sub-next-bill = Next billed on { $date }
|
||||
sub-expires-on = Expires on { $date }
|
||||
|
||||
## Routes - Subscription - PaymentUpdate
|
||||
# $expirationDate (Date) - The payment card's expiration date.
|
||||
|
||||
pay-update-card-exp = Expires { $expirationDate }
|
||||
sub-route-idx-updating = Updating billing information…
|
||||
sub-route-payment-modal-heading = Invalid billing information
|
||||
sub-route-payment-modal-message = There seems to be an error with your { -brand-name-paypal } account, we need you to take the necessary steps to resolve this payment issue.
|
||||
sub-route-missing-billing-agreement-payment-alert = Invalid payment information; there is an error with your account. <div>Manage</div>
|
||||
sub-route-funding-source-payment-alert = Invalid payment information; there is an error with your account. This alert may take some time to clear after you successfully update your information. <div>Manage</div>
|
||||
|
||||
## Routes - Subscription - SubscriptionItem
|
||||
|
||||
sub-item-no-such-plan = No such plan for this subscription.
|
||||
invoice-not-found = Subsequent invoice not found
|
||||
sub-item-no-such-subsequent-invoice = Subsequent invoice not found for this subscription.
|
||||
|
||||
## Routes - Subscriptions - Pocket Subscription
|
||||
|
||||
manage-pocket-title = Looking for your { -brand-name-pocket } premium subscription?
|
||||
manage-pocket-body-2 = To manage it, <linkExternal>click here</linkExternal>.
|
|
@ -259,7 +259,7 @@ const PROFILE: Profile = {
|
|||
avatarDefault: false,
|
||||
displayName: 'Foo Barson',
|
||||
email: 'foo@example.com',
|
||||
locale: 'en-US',
|
||||
locale: 'en',
|
||||
twoFactorAuthentication: true,
|
||||
uid: '8675309asdf',
|
||||
metricsEnabled: true,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
## Routes - Product and Subscriptions
|
||||
sub-update-payment-title = Payment information
|
||||
|
||||
## Routes - Checkout and Product/Subscription create
|
||||
pay-with-heading-card-or = Or pay with card
|
||||
pay-with-heading-card-only = Pay with card
|
|
@ -42,18 +42,30 @@ async function getFtlFromPackage(packageName: PackageName, locale: string) {
|
|||
}
|
||||
break;
|
||||
case 'payments':
|
||||
// TODO: Not currently used. We need to set up test stuff for payments similarly, FXA-5996
|
||||
ftlPath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'fxa-payments-server',
|
||||
'public',
|
||||
'locales',
|
||||
locale,
|
||||
'main.ftl'
|
||||
);
|
||||
if (locale === 'en') {
|
||||
ftlPath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'fxa-payments-server',
|
||||
'test',
|
||||
'payments.ftl'
|
||||
);
|
||||
} else {
|
||||
// TODO: Not currently used, but probably want to add one translation test
|
||||
ftlPath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'fxa-payments-server',
|
||||
'public',
|
||||
'locales',
|
||||
locale,
|
||||
'payments.ftl'
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ftlPath = path.join(__dirname, 'test.ftl');
|
||||
|
|
|
@ -25307,6 +25307,10 @@ fsevents@~2.1.1:
|
|||
fxa-react: "workspace:*"
|
||||
fxa-settings: "workspace:*"
|
||||
fxa-shared: "workspace:*"
|
||||
grunt: ^1.5.3
|
||||
grunt-cli: ^1.4.3
|
||||
grunt-contrib-concat: ^2.1.0
|
||||
grunt-contrib-watch: ^1.1.0
|
||||
handlebars: ^4.7.7
|
||||
helmet: ^6.0.0
|
||||
hot-shots: ^9.3.0
|
||||
|
|
Загрузка…
Ссылка в новой задаче