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:
Valerie Pomerleau 2022-10-19 16:37:32 -07:00
Родитель 63ff7ad6d1
Коммит d79c30d50a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 080A1FA79ED12365
61 изменённых файлов: 631 добавлений и 600 удалений

8
.gitignore поставляемый
Просмотреть файл

@ -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"

9
packages/fxa-payments-server/.gitignore поставляемый
Просмотреть файл

@ -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 cant switch between currencies.
no-subscription-change = Sorry. You cant change your subscription plan.
# $mobileAppStore (String) - "Google Play Store" or "App Store", localized when the translation is available.
iap-already-subscribed = Youre 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 isnt 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 = Youre 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 dont 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 cant 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> &nbsp; <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 youll be charged an adjusted
amount for the rest of your billing cycle. Starting { $startingDate }
youll 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! Youre 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 = Were 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 = Didnt 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 = Youll 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 = Id 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

23
packages/fxa-payments-server/src/.gitignore поставляемый
Просмотреть файл

@ -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 = Id 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 = Youll 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 dont 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> &nbsp; <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 isnt 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 cant switch between currencies.
no-subscription-change = Sorry. You cant change your subscription plan.
# $mobileAppStore (String) - "Google Play Store" or "App Store", localized when the translation is available.
iap-already-subscribed = Youre 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 = Youre 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 cant 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 youll be charged an adjusted
amount for the rest of your billing cycle. Starting { $startingDate }
youll 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! Youre 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 = Were 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