diff --git a/CHANGELOG.md b/CHANGELOG.md index 441e0577bb..96ef7bd2d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Quick search query param needs to be fixed while navigating to search page. [#230](https://jira.bigcommerce.com/browse/BCTHEME-230) ## Draft +- Move phrases and static strings to en.json for improving translation customizing. [#1850](https://github.com/bigcommerce/cornerstone/pull/1850) - Create unified focus styling in Cornerstone. [#1812](https://github.com/bigcommerce/cornerstone/pull/1812) - Review link in quick modal focused twice. [#1797](https://github.com/bigcommerce/cornerstone/pull/1797) - Fixed product image doesn't change on click when viewing a product with multiple images in IE11 [#1748](https://github.com/bigcommerce/cornerstone/pull/1748) diff --git a/assets/js/test-unit/theme/common/faceted-search.spec.js b/assets/js/test-unit/theme/common/faceted-search.spec.js index 675424d5c2..7bec488f9e 100644 --- a/assets/js/test-unit/theme/common/faceted-search.spec.js +++ b/assets/js/test-unit/theme/common/faceted-search.spec.js @@ -10,6 +10,7 @@ describe('FacetedSearch', () => { let onSearchSuccess; let html; let $element; + let options; beforeEach(() => { onSearchSuccess = jest.fn(); @@ -26,6 +27,16 @@ describe('FacetedSearch', () => { }, }; + options = { + validationErrorMessages: { + onMinPriceError: jasmine.any(String), + onMaxPriceError: jasmine.any(String), + minPriceNotEntered: jasmine.any(String), + maxPriceNotEntered: jasmine.any(String), + onInvalidPrice: jasmine.any(String), + }, + }; + html = '
' + 'Clear' + @@ -57,8 +68,7 @@ describe('FacetedSearch', () => { $element = $(html); $element.appendTo(document.body); - - facetedSearch = new FacetedSearch(requestOptions, onSearchSuccess); + facetedSearch = new FacetedSearch(requestOptions, onSearchSuccess, options); }); afterEach(() => { @@ -99,7 +109,7 @@ describe('FacetedSearch', () => { it('should re-init price range validator', function() { facetedSearch.refreshView(content); - expect(Validators.setMinMaxPriceValidation).toHaveBeenCalledWith(facetedSearch.priceRangeValidator, jasmine.any(Object)); + expect(Validators.setMinMaxPriceValidation).toHaveBeenCalledWith(facetedSearch.priceRangeValidator, jasmine.any(Object), options.validationErrorMessages); }); }); diff --git a/assets/js/test-unit/theme/common/form-utils.spec.js b/assets/js/test-unit/theme/common/form-utils.spec.js index e0a53ca903..e653073c79 100644 --- a/assets/js/test-unit/theme/common/form-utils.spec.js +++ b/assets/js/test-unit/theme/common/form-utils.spec.js @@ -13,6 +13,13 @@ describe('Validators', () => { describe('setMinMaxPriceValidation', () => { let selectors; + const priceValidationErrorTexts = { + onMinPriceError: jasmine.any(String), + onMaxPriceError: jasmine.any(String), + minPriceNotEntered: jasmine.any(String), + maxPriceNotEntered: jasmine.any(String), + onInvalidPrice: jasmine.any(String), + }; beforeEach(() => { selectors = { @@ -25,7 +32,7 @@ describe('Validators', () => { }); it('should add min-max validator to min price input', () => { - Validators.setMinMaxPriceValidation(validator, selectors); + Validators.setMinMaxPriceValidation(validator, selectors, priceValidationErrorTexts); expect(validator.add).toHaveBeenCalledWith({ errorMessage: jasmine.any(String), @@ -35,7 +42,7 @@ describe('Validators', () => { }); it('should add presence validator to max price input', () => { - Validators.setMinMaxPriceValidation(validator, selectors); + Validators.setMinMaxPriceValidation(validator, selectors, priceValidationErrorTexts); expect(validator.add).toHaveBeenCalledWith({ errorMessage: jasmine.any(String), @@ -45,7 +52,7 @@ describe('Validators', () => { }); it('should add presence validator to max price input', () => { - Validators.setMinMaxPriceValidation(validator, selectors); + Validators.setMinMaxPriceValidation(validator, selectors, priceValidationErrorTexts); expect(validator.add).toHaveBeenCalledWith({ errorMessage: jasmine.any(String), @@ -55,7 +62,7 @@ describe('Validators', () => { }); it('should add min-number validator to max/min price inputs', () => { - Validators.setMinMaxPriceValidation(validator, selectors); + Validators.setMinMaxPriceValidation(validator, selectors, priceValidationErrorTexts); expect(validator.add).toHaveBeenCalledWith({ errorMessage: jasmine.any(String), diff --git a/assets/js/theme/account.js b/assets/js/theme/account.js index 1486d4b0f8..b12ccd24e2 100644 --- a/assets/js/theme/account.js +++ b/assets/js/theme/account.js @@ -4,14 +4,15 @@ import nod from './common/nod'; import Wishlist from './wishlist'; import validation from './common/form-validation'; import stateCountry from './common/state-country'; -import { classifyForm, Validators, insertStateHiddenField } from './common/utils/form-utils'; +import { classifyForm, Validators, insertStateHiddenField, createPasswordValidationErrorTextObject } from './common/utils/form-utils'; +import { createTranslationDictionary } from './common/utils/translations-utils'; import { creditCardType, storeInstrument, Validators as CCValidators, Formatters as CCFormatters } from './common/payment-method'; import swal from './global/sweet-alert'; export default class Account extends PageManager { constructor(context) { super(context); - + this.validationDictionary = createTranslationDictionary(context); this.$state = $('[data-field-type="State"]'); this.$body = $('body'); } @@ -130,7 +131,7 @@ export default class Account extends PageManager { } initAddressFormValidation($addressForm) { - const validationModel = validation($addressForm); + const validationModel = validation($addressForm, this.context); const stateSelector = 'form[data-address-form] [data-field-type="State"]'; const $stateElement = $(stateSelector); const addressValidator = nod({ @@ -160,7 +161,7 @@ export default class Account extends PageManager { if ($field.is('select')) { $last = field; - Validators.setStateCountryValidation(addressValidator, field); + Validators.setStateCountryValidation(addressValidator, field, this.validationDictionary.field_not_blank); } else { Validators.cleanUpStateValidation(field); } @@ -220,7 +221,7 @@ export default class Account extends PageManager { $paymentMethodForm.find('#state.form-field').attr('data-validation', `{ "type": "singleline", "label": "${this.context.stateLabel}", "required": true, "maxlength": 0 }`); $paymentMethodForm.find('#postal_code.form-field').attr('data-validation', `{ "type": "singleline", "label": "${this.context.postalCodeLabel}", "required": true, "maxlength": 0 }`); - const validationModel = validation($paymentMethodForm); + const validationModel = validation($paymentMethodForm, this.context); const paymentMethodSelector = 'form[data-payment-method-form]'; const paymentMethodValidator = nod({ submit: `${paymentMethodSelector} input[type="submit"]` }); const $stateElement = $(`${paymentMethodSelector} [data-field-type="State"]`); @@ -244,7 +245,7 @@ export default class Account extends PageManager { if ($field.is('select')) { $last = field; - Validators.setStateCountryValidation(paymentMethodValidator, field); + Validators.setStateCountryValidation(paymentMethodValidator, field, this.validationDictionary.field_not_blank); } else { Validators.cleanUpStateValidation(field); } @@ -309,7 +310,7 @@ export default class Account extends PageManager { } registerEditAccountValidation($editAccountForm) { - const validationModel = validation($editAccountForm); + const validationModel = validation($editAccountForm, this.context); const formEditSelector = 'form[data-edit-account-form]'; const editValidator = nod({ submit: '${formEditSelector} input[type="submit"]', @@ -328,10 +329,11 @@ export default class Account extends PageManager { if ($emailElement) { editValidator.remove(emailSelector); - Validators.setEmailValidation(editValidator, emailSelector); + Validators.setEmailValidation(editValidator, emailSelector, this.validationDictionary.valid_email); } if ($passwordElement && $password2Element) { + const { password: enterPassword, password_match: matchPassword, invalid_password: invalidPassword } = this.validationDictionary; editValidator.remove(passwordSelector); editValidator.remove(password2Selector); Validators.setPasswordValidation( @@ -339,6 +341,7 @@ export default class Account extends PageManager { passwordSelector, password2Selector, this.passwordRequirements, + createPasswordValidationErrorTextObject(enterPassword, enterPassword, matchPassword, invalidPassword), true, ); } diff --git a/assets/js/theme/auth.js b/assets/js/theme/auth.js index 8a08d0b5f8..23c11dfba2 100644 --- a/assets/js/theme/auth.js +++ b/assets/js/theme/auth.js @@ -3,11 +3,13 @@ import stateCountry from './common/state-country'; import nod from './common/nod'; import validation from './common/form-validation'; import forms from './common/models/forms'; -import { classifyForm, Validators } from './common/utils/form-utils'; +import { classifyForm, Validators, createPasswordValidationErrorTextObject } from './common/utils/form-utils'; +import { createTranslationDictionary } from './common/utils/translations-utils'; export default class Auth extends PageManager { constructor(context) { super(context); + this.validationDictionary = createTranslationDictionary(context); this.formCreateSelector = 'form[data-create-account-form]'; } @@ -79,23 +81,25 @@ export default class Auth extends PageManager { } registerNewPasswordValidation() { + const { password: enterPassword, password_match: matchPassword, invalid_password: invalidPassword } = this.validationDictionary; const newPasswordForm = '.new-password-form'; const newPasswordValidator = nod({ submit: $(`${newPasswordForm} input[type="submit"]`), }); const passwordSelector = $(`${newPasswordForm} input[name="password"]`); const password2Selector = $(`${newPasswordForm} input[name="password_confirm"]`); - + const errorTextMessages = createPasswordValidationErrorTextObject(enterPassword, enterPassword, matchPassword, invalidPassword); Validators.setPasswordValidation( newPasswordValidator, passwordSelector, password2Selector, this.passwordRequirements, + errorTextMessages, ); } registerCreateAccountValidator($createAccountForm) { - const validationModel = validation($createAccountForm); + const validationModel = validation($createAccountForm, this.context); const createAccountValidator = nod({ submit: `${this.formCreateSelector} input[type='submit']`, }); @@ -130,7 +134,7 @@ export default class Auth extends PageManager { if ($field.is('select')) { $last = field; - Validators.setStateCountryValidation(createAccountValidator, field); + Validators.setStateCountryValidation(createAccountValidator, field, this.validationDictionary.field_not_blank); } else { Validators.cleanUpStateValidation(field); } @@ -139,10 +143,12 @@ export default class Auth extends PageManager { if ($emailElement) { createAccountValidator.remove(emailSelector); - Validators.setEmailValidation(createAccountValidator, emailSelector); + Validators.setEmailValidation(createAccountValidator, emailSelector, this.validationDictionary.valid_email); } if ($passwordElement && $password2Element) { + const { password: enterPassword, password_match: matchPassword, invalid_password: invalidPassword } = this.validationDictionary; + createAccountValidator.remove(passwordSelector); createAccountValidator.remove(password2Selector); Validators.setPasswordValidation( @@ -150,6 +156,7 @@ export default class Auth extends PageManager { passwordSelector, password2Selector, this.passwordRequirements, + createPasswordValidationErrorTextObject(enterPassword, enterPassword, matchPassword, invalidPassword), ); } diff --git a/assets/js/theme/brand.js b/assets/js/theme/brand.js index ddf9ba86aa..3cddd3d07a 100644 --- a/assets/js/theme/brand.js +++ b/assets/js/theme/brand.js @@ -2,8 +2,14 @@ import { hooks } from '@bigcommerce/stencil-utils'; import CatalogPage from './catalog'; import compareProducts from './global/compare-products'; import FacetedSearch from './common/faceted-search'; +import { createTranslationDictionary } from '../theme/common/utils/translations-utils'; export default class Brand extends CatalogPage { + constructor(context) { + super(context); + this.validationDictionary = createTranslationDictionary(context); + } + onReady() { compareProducts(this.context.urls); @@ -16,6 +22,13 @@ export default class Brand extends CatalogPage { } initFacetedSearch() { + const { + price_min_evaluation: onMinPriceError, + price_max_evaluation: onMaxPriceError, + price_min_not_entered: minPriceNotEntered, + price_max_not_entered: maxPriceNotEntered, + price_invalid_value: onInvalidPrice, + } = this.validationDictionary; const $productListingContainer = $('#product-listing-container'); const $facetedSearchContainer = $('#faceted-search-container'); const productsPerPage = this.context.brandProductsPerPage; @@ -44,6 +57,14 @@ export default class Brand extends CatalogPage { $('html, body').animate({ scrollTop: 0, }, 100); + }, { + validationErrorMessages: { + onMinPriceError, + onMaxPriceError, + minPriceNotEntered, + maxPriceNotEntered, + onInvalidPrice, + }, }); } } diff --git a/assets/js/theme/category.js b/assets/js/theme/category.js index 387f443b62..04d7f497c4 100644 --- a/assets/js/theme/category.js +++ b/assets/js/theme/category.js @@ -2,8 +2,14 @@ import { hooks } from '@bigcommerce/stencil-utils'; import CatalogPage from './catalog'; import compareProducts from './global/compare-products'; import FacetedSearch from './common/faceted-search'; +import { createTranslationDictionary } from '../theme/common/utils/translations-utils'; export default class Category extends CatalogPage { + constructor(context) { + super(context); + this.validationDictionary = createTranslationDictionary(context); + } + onReady() { compareProducts(this.context.urls); @@ -16,6 +22,13 @@ export default class Category extends CatalogPage { } initFacetedSearch() { + const { + price_min_evaluation: onMinPriceError, + price_max_evaluation: onMaxPriceError, + price_min_not_entered: minPriceNotEntered, + price_max_not_entered: maxPriceNotEntered, + price_invalid_value: onInvalidPrice, + } = this.validationDictionary; const $productListingContainer = $('#product-listing-container'); const $facetedSearchContainer = $('#faceted-search-container'); const productsPerPage = this.context.categoryProductsPerPage; @@ -44,6 +57,14 @@ export default class Category extends CatalogPage { $('html, body').animate({ scrollTop: 0, }, 100); + }, { + validationErrorMessages: { + onMinPriceError, + onMaxPriceError, + minPriceNotEntered, + maxPriceNotEntered, + onInvalidPrice, + }, }); } } diff --git a/assets/js/theme/common/faceted-search.js b/assets/js/theme/common/faceted-search.js index 9f30973925..e3add6d1d3 100644 --- a/assets/js/theme/common/faceted-search.js +++ b/assets/js/theme/common/faceted-search.js @@ -253,7 +253,7 @@ class FacetedSearch { minPriceSelector: this.options.priceRangeMinPriceSelector, }; - Validators.setMinMaxPriceValidation(validator, selectors); + Validators.setMinMaxPriceValidation(validator, selectors, this.options.validationErrorMessages); this.priceRangeValidator = validator; } diff --git a/assets/js/theme/common/form-validation.js b/assets/js/theme/common/form-validation.js index 03658440fa..41b7c1003d 100644 --- a/assets/js/theme/common/form-validation.js +++ b/assets/js/theme/common/form-validation.js @@ -1,3 +1,5 @@ +import { createTranslationDictionary } from './utils/translations-utils'; + /** * Validate that the given date for the day/month/year select inputs is within potential range * @param $formField @@ -35,8 +37,9 @@ function buildDateValidation($formField, validation) { * from many different inputs * @param $formField * @param validation + * @param errorText provides error validation message */ -function buildRequiredCheckboxValidation($formField, validation) { +function buildRequiredCheckboxValidation(validation, $formField, errorText) { const formFieldId = $formField.attr('id'); const primarySelector = `#${formFieldId} input:first-of-type`; const secondarySelector = `#${formFieldId} input`; @@ -57,17 +60,17 @@ function buildRequiredCheckboxValidation($formField, validation) { cb(result); }, - errorMessage: `The '${validation.label}' field cannot be blank.`, + errorMessage: errorText, }; } -function buildRequiredValidation(validation, selector) { +function buildRequiredValidation(validation, selector, errorText) { return { selector, validate(cb, val) { cb(val.length > 0); }, - errorMessage: `The '${validation.label}' field cannot be blank.`, + errorMessage: errorText, }; } @@ -88,7 +91,7 @@ function buildNumberRangeValidation(validation, formFieldSelector) { } -function buildValidation($validateableElement) { +function buildValidation($validateableElement, errorMessage) { const validation = $validateableElement.data('validation'); const fieldValidations = []; const formFieldSelector = `#${$validateableElement.attr('id')}`; @@ -100,7 +103,7 @@ function buildValidation($validateableElement) { fieldValidations.push(dateValidation); } } else if (validation.required && (validation.type === 'checkboxselect' || validation.type === 'radioselect')) { - fieldValidations.push(buildRequiredCheckboxValidation($validateableElement, validation)); + fieldValidations.push(buildRequiredCheckboxValidation(validation, $validateableElement, errorMessage)); } else { $validateableElement.find('input, select, textarea').each((index, element) => { const $inputElement = $(element); @@ -112,7 +115,7 @@ function buildValidation($validateableElement) { fieldValidations.push(buildNumberRangeValidation(validation, formFieldSelector)); } if (validation.required) { - fieldValidations.push(buildRequiredValidation(validation, elementSelector)); + fieldValidations.push(buildRequiredValidation(validation, elementSelector, errorMessage)); } }); } @@ -123,13 +126,18 @@ function buildValidation($validateableElement) { /** * Builds the validation model for dynamic forms * @param $form + * @param context provides access for error messages on required fields validation * @returns {Array} */ -export default function ($form) { +export default function ($form, context) { let validationsToPerform = []; + const { field_not_blank: requiredFieldValidationText } = createTranslationDictionary(context); $form.find('[data-validation]').each((index, input) => { - validationsToPerform = validationsToPerform.concat(buildValidation($(input))); + const getLabel = $el => $el.first().data('validation').label; + const requiredValidationMessage = getLabel($(input)) + requiredFieldValidationText; + + validationsToPerform = validationsToPerform.concat(buildValidation($(input), requiredValidationMessage)); }); return validationsToPerform; diff --git a/assets/js/theme/common/utils/form-utils.js b/assets/js/theme/common/utils/form-utils.js index 124cc0872c..5000a5e4ef 100644 --- a/assets/js/theme/common/utils/form-utils.js +++ b/assets/js/theme/common/utils/form-utils.js @@ -7,6 +7,21 @@ const inputTagNames = [ 'select', 'textarea', ]; +/** + * Set up Object with Error Messages on Password Validation. Please use messages in mentioned order + * @param {string} empty defines error text for empty field + * @param {string} confirm defines error text for empty confirmation field + * @param {string} mismatch defines error text if confirm passford mismatches passford field + * @param {string} invalid defines error text for invalid password charaters sequence + * @return {object} messages or default texts if nothing is providing + */ +export const createPasswordValidationErrorTextObject = (empty, confirm, mismatch, invalid) => ({ + onEmptyPasswordErrorText: empty, + onConfirmPasswordErrorText: confirm, + onMismatchPasswordErrorText: mismatch, + onNotValidPasswordErrorText: invalid, +}); + /** * Apply class name to an input element on its type @@ -114,8 +129,9 @@ const Validators = { * Sets up a new validation when the form is dirty * @param validator * @param field + * @param {string} errorText describes errorMassage on email validation */ - setEmailValidation: (validator, field) => { + setEmailValidation: (validator, field, errorText) => { if (field) { validator.add({ selector: field, @@ -124,7 +140,7 @@ const Validators = { cb(result); }, - errorMessage: 'You must enter a valid email.', + errorMessage: errorText, }); } }, @@ -135,9 +151,12 @@ const Validators = { * @param passwordSelector * @param password2Selector * @param requirements + * @param {object} errorTextsObject * @param isOptional */ - setPasswordValidation: (validator, passwordSelector, password2Selector, requirements, isOptional) => { + setPasswordValidation: (validator, passwordSelector, password2Selector, requirements, { + onEmptyPasswordErrorText, onConfirmPasswordErrorText, onMismatchPasswordErrorText, onNotValidPasswordErrorText, + }, isOptional) => { const $password = $(passwordSelector); const passwordValidations = [ { @@ -151,7 +170,7 @@ const Validators = { cb(result); }, - errorMessage: 'You must enter a password.', + errorMessage: onEmptyPasswordErrorText, }, { selector: passwordSelector, @@ -167,7 +186,7 @@ const Validators = { cb(result); }, - errorMessage: requirements.error, + errorMessage: onNotValidPasswordErrorText, }, { selector: password2Selector, @@ -180,7 +199,7 @@ const Validators = { cb(result); }, - errorMessage: 'You must enter a password.', + errorMessage: onConfirmPasswordErrorText, }, { selector: password2Selector, @@ -189,7 +208,7 @@ const Validators = { cb(result); }, - errorMessage: 'Your passwords do not match.', + errorMessage: onMismatchPasswordErrorText, }, ]; @@ -206,7 +225,7 @@ const Validators = { * @param {string} selectors.maxPriceSelector * @param {string} selectors.minPriceSelector */ - setMinMaxPriceValidation: (validator, selectors) => { + setMinMaxPriceValidation: (validator, selectors, priceValidationErrorTexts = {}) => { const { errorSelector, fieldsetSelector, @@ -215,6 +234,9 @@ const Validators = { minPriceSelector, } = selectors; + // eslint-disable-next-line object-curly-newline + const { onMinPriceError, onMaxPriceError, minPriceNotEntered, maxPriceNotEntered, onInvalidPrice } = priceValidationErrorTexts; + validator.configure({ form: formSelector, preventSubmit: true, @@ -222,31 +244,31 @@ const Validators = { }); validator.add({ - errorMessage: 'Min price must be less than max. price.', + errorMessage: onMinPriceError, selector: minPriceSelector, validate: `min-max:${minPriceSelector}:${maxPriceSelector}`, }); validator.add({ - errorMessage: 'Min price must be less than max. price.', + errorMessage: onMaxPriceError, selector: maxPriceSelector, validate: `min-max:${minPriceSelector}:${maxPriceSelector}`, }); validator.add({ - errorMessage: 'Max. price is required.', + errorMessage: maxPriceNotEntered, selector: maxPriceSelector, validate: 'presence', }); validator.add({ - errorMessage: 'Min. price is required.', + errorMessage: minPriceNotEntered, selector: minPriceSelector, validate: 'presence', }); validator.add({ - errorMessage: 'Input must be greater than 0.', + errorMessage: onInvalidPrice, selector: [minPriceSelector, maxPriceSelector], validate: 'min-number:0', }); @@ -263,12 +285,12 @@ const Validators = { * @param validator * @param field */ - setStateCountryValidation: (validator, field) => { + setStateCountryValidation: (validator, field, errorText) => { if (field) { validator.add({ selector: field, validate: 'presence', - errorMessage: 'The \'State/Province\' field cannot be blank.', + errorMessage: errorText, }); } }, diff --git a/assets/js/theme/common/utils/translations-utils.js b/assets/js/theme/common/utils/translations-utils.js new file mode 100644 index 0000000000..c96dde833d --- /dev/null +++ b/assets/js/theme/common/utils/translations-utils.js @@ -0,0 +1,28 @@ +const TRANSLATIONS = 'translations'; +const isTranslationDictionaryNotEmpty = (dictionary) => !!Object.keys(dictionary[TRANSLATIONS]).length; +const chooseActiveDictionary = (...dictionaryJsonList) => { + for (let i = 0; i < dictionaryJsonList.length; i++) { + const dictionary = JSON.parse(dictionaryJsonList[i]); + if (isTranslationDictionaryNotEmpty(dictionary)) { + return dictionary; + } + } +}; + +/** + * defines Translation Dictionary to use + * @param context provides access to 3 validation JSONs from en.json: + * validation_messages, validation_fallback_messages and default_messages + * @returns {Object} + */ +export const createTranslationDictionary = (context) => { + const { validationDictionaryJSON, validationFallbackDictionaryJSON, validationDefaultDictionaryJSON } = context; + const activeDictionary = chooseActiveDictionary(validationDictionaryJSON, validationFallbackDictionaryJSON, validationDefaultDictionaryJSON); + const localizations = Object.values(activeDictionary[TRANSLATIONS]); + const translationKeys = Object.keys(activeDictionary[TRANSLATIONS]).map(key => key.split('.').pop()); + + return translationKeys.reduce((acc, key, i) => { + acc[key] = localizations[i]; + return acc; + }, {}); +}; diff --git a/assets/js/theme/gift-certificate.js b/assets/js/theme/gift-certificate.js index 12f19bc2b6..e9dd44c492 100644 --- a/assets/js/theme/gift-certificate.js +++ b/assets/js/theme/gift-certificate.js @@ -2,12 +2,14 @@ import PageManager from './page-manager'; import nod from './common/nod'; import giftCertChecker from './common/gift-certificate-validator'; import formModel from './common/models/forms'; +import { createTranslationDictionary } from './common/utils/translations-utils'; import { api } from '@bigcommerce/stencil-utils'; import { defaultModal } from './global/modal'; export default class GiftCertificate extends PageManager { constructor(context) { super(context); + this.validationDictionary = createTranslationDictionary(context); const $certBalanceForm = $('#gift-certificate-balance'); @@ -54,6 +56,16 @@ export default class GiftCertificate extends PageManager { const minFormatted = $element.data('minFormatted'); const max = $element.data('max'); const maxFormatted = $element.data('maxFormatted'); + const insertFormattedAmountsIntoErrorMessage = (message, ...amountRange) => { + const amountPlaceholders = ['[MIN]', '[MAX]']; + let updatedErrorText = message; + amountPlaceholders.forEach((placeholder, i) => { + updatedErrorText = updatedErrorText.includes(placeholder) ? + updatedErrorText.replace(placeholder, amountRange[i]) : + updatedErrorText; + }); + return updatedErrorText; + }; purchaseValidator.add({ selector: '#gift-certificate-form input[name="certificate_amount"]', @@ -66,7 +78,7 @@ export default class GiftCertificate extends PageManager { cb(numberVal >= min && numberVal <= max); }, - errorMessage: `You must enter a certificate amount between ${minFormatted} and ${maxFormatted}.`, + errorMessage: insertFormattedAmountsIntoErrorMessage(this.validationDictionary.certificate_amount_range, minFormatted, maxFormatted), }); } diff --git a/assets/js/theme/search.js b/assets/js/theme/search.js index 81725ce612..eb955441c2 100644 --- a/assets/js/theme/search.js +++ b/assets/js/theme/search.js @@ -252,6 +252,8 @@ export default class Search extends CatalogPage { } initFacetedSearch() { + // eslint-disable-next-line object-curly-newline + const { onMinPriceError, onMaxPriceError, minPriceNotEntered, maxPriceNotEntered, onInvalidPrice } = this.context; const $productListingContainer = $('#product-listing-container'); const $contentListingContainer = $('#search-results-content'); const $facetedSearchContainer = $('#faceted-search-container'); @@ -296,6 +298,14 @@ export default class Search extends CatalogPage { $('html, body').animate({ scrollTop: 0, }, 100); + }, { + validationErrorMessages: { + onMinPriceError, + onMaxPriceError, + minPriceNotEntered, + maxPriceNotEntered, + onInvalidPrice, + }, }); } diff --git a/lang/en.json b/lang/en.json index 43ceb29866..e816b62269 100755 --- a/lang/en.json +++ b/lang/en.json @@ -9,7 +9,8 @@ "navigate": "Navigate", "info": "Info", "categories": "Categories", - "call_us": "Call us at {phone_number}" + "call_us": "Call us at {phone_number}", + "powered_by": "Powered by" }, "home": { "heading": "Home" @@ -153,6 +154,7 @@ "cancel": "Cancel", "close": "Close", "or": "or", + "and": "and", "compare": "Compare", "sign_up": "Register", "login": "Sign in", @@ -652,8 +654,7 @@ "name": "You must enter your name.", "password": "You must enter a password.", "password_match": "Your passwords do not match.", - "email_address": "Please use a valid email address, such as user@example.com.", - "valid_email": "You must enter a valid email." + "email_address": "Please use a valid email address, such as user@example.com." }, "contact": { "email_address": "Please use a valid email address, such as user@example.com.", @@ -665,8 +666,7 @@ "from_name": "You must enter your name.", "from_email": "You must enter a valid email.", "cert_theme": "You must select a gift certificate theme.", - "agree_terms": "You must agree to these terms.", - "amount": "You must enter a gift certificate amount." + "agree_terms": "You must agree to these terms." }, "payment_method": { "credit_card_number": "You must enter a valid credit card number.", @@ -890,5 +890,47 @@ }, "carousel": { "arrowAriaLabel": "Go to slide [NUMBER] of" + }, + "validation_messages": { + "valid_email": "You must enter a valid email.", + "password": "You must enter a password.", + "password_match": "Your passwords do not match.", + "invalid_password": "Passwords must be at least 7 characters and contain both alphabetic and numeric characters.", + "field_not_blank": " field cannot be blank.", + "certificate_amount": "You must enter a gift certificate amount.", + "certificate_amount_range": "You must enter a certificate amount between [MIN] and [MAX]", + "price_min_evaluation": "Min. price must be less than max. price.", + "price_max_evaluation": "Min. price must be less than max. price.", + "price_min_not_entered": "Min. price is required.", + "price_max_not_entered": "Max. price is required.", + "price_invalid_value": "Input must be greater than 0." + }, + "validation_fallback_messages": { + "valid_email": "You must enter a valid email.", + "password": "You must enter a password.", + "password_match": "Your passwords do not match.", + "invalid_password": "Passwords must be at least 7 characters and contain both alphabetic and numeric characters.", + "field_not_blank": " field cannot be blank.", + "certificate_amount": "You must enter a gift certificate amount.", + "certificate_amount_range": "You must enter a certificate amount between [MIN] and [MAX]", + "price_min_evaluation": "Min. price must be less than max. price.", + "price_max_evaluation": "Min. price must be less than max. price.", + "price_min_not_entered": "Min. price is required.", + "price_max_not_entered": "Max. price is required.", + "price_invalid_value": "Input must be greater than 0." + }, + "validation_default_messages": { + "valid_email": "You must enter a valid email.", + "password": "You must enter a password.", + "password_match": "Your passwords do not match.", + "invalid_password": "Passwords must be at least 7 characters and contain both alphabetic and numeric characters.", + "field_not_blank": "The field cannot be blank.", + "certificate_amount": "You must enter a gift certificate amount.", + "certificate_amount_range": "You must enter a certificate amount between [MIN] and [MAX]", + "price_min_evaluation": "Min. price must be less than max. price.", + "price_max_evaluation": "Min. price must be less than max. price.", + "price_min_not_entered": "Min. price is required.", + "price_max_not_entered": "Max. price is required.", + "price_invalid_value": "Input must be greater than 0." } } diff --git a/templates/components/amp/common/footer.html b/templates/components/amp/common/footer.html index efb7714e8e..660cfb1fb6 100644 --- a/templates/components/amp/common/footer.html +++ b/templates/components/amp/common/footer.html @@ -45,7 +45,7 @@
{{#if theme_settings.show_powered_by}} -

Powered by BigCommerce

+

{{lang 'footer.powered_by'}} BigCommerce

{{/if}} {{#if theme_settings.show_copyright_footer}}

© {{settings.store_name}}

diff --git a/templates/components/common/footer.html b/templates/components/common/footer.html index 4d0e6667c6..0cf488f681 100644 --- a/templates/components/common/footer.html +++ b/templates/components/common/footer.html @@ -94,7 +94,7 @@ {{/if}} {{#if theme_settings.show_powered_by}} {{/if}} {{#if theme_settings.show_copyright_footer}} diff --git a/templates/layout/base.html b/templates/layout/base.html index 7a512f8df6..45ed0855c2 100644 --- a/templates/layout/base.html +++ b/templates/layout/base.html @@ -39,6 +39,9 @@ {{~inject 'cartId' cart_id}} {{~inject 'channelId' settings.channel_id}} {{~inject 'template' template}} + {{~inject 'validationDictionaryJSON' (langJson 'validation_messages')}} + {{~inject 'validationFallbackDictionaryJSON' (langJson 'validation_fallback_messages')}} + {{~inject 'validationDefaultDictionaryJSON' (langJson 'default_messages')}}