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')}}