diff --git a/packages/vulcan-forms/lib/components/FormComponent.jsx b/packages/vulcan-forms/lib/components/FormComponent.jsx
index 3303dd3f5..51572e4ee 100644
--- a/packages/vulcan-forms/lib/components/FormComponent.jsx
+++ b/packages/vulcan-forms/lib/components/FormComponent.jsx
@@ -196,10 +196,12 @@ class FormComponent extends Component {
Get errors from Form state through context
+ Note: we use `includes` to get all errors from nested components, which have longer paths
+
*/
getErrors = errors => {
errors = errors || this.props.errors;
- const fieldErrors = errors.filter(error => error.path === this.props.path);
+ const fieldErrors = errors.filter(error => error.path.includes(this.props.path));
return fieldErrors;
};
diff --git a/packages/vulcan-forms/lib/components/FormIntl.jsx b/packages/vulcan-forms/lib/components/FormIntl.jsx
index a3a3f9cc6..5db706322 100644
--- a/packages/vulcan-forms/lib/components/FormIntl.jsx
+++ b/packages/vulcan-forms/lib/components/FormIntl.jsx
@@ -1,6 +1,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Components, registerComponent, Locales } from 'meteor/vulcan:core';
+import omit from 'lodash/omit';
class FormIntl extends PureComponent {
@@ -11,20 +12,20 @@ class FormIntl extends PureComponent {
so we just use the order of the Locales array.
*/
- getLocalePath = (locale, defaultIndex) => {
+ getLocalePath = (defaultIndex) => {
return `${this.props.path}_intl.${defaultIndex}`;
}
-
+
render() {
// do not pass FormIntl's own value, inputProperties, and intlInput props down
- const properties = _.omit(this.props, 'value', 'inputProperties', 'intlInput');
+ const properties = omit(this.props, 'value', 'inputProperties', 'intlInput');
return (
{Locales.map((locale, i) => (
-
+
))}
diff --git a/packages/vulcan-lib/lib/modules/collections.js b/packages/vulcan-lib/lib/modules/collections.js
index 28581afdf..965793542 100644
--- a/packages/vulcan-lib/lib/modules/collections.js
+++ b/packages/vulcan-lib/lib/modules/collections.js
@@ -1,12 +1,12 @@
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
-import { addGraphQLCollection, addGraphQLQuery, addGraphQLMutation, addGraphQLResolvers, addToGraphQLContext } from './graphql.js';
+import { addGraphQLCollection, addToGraphQLContext } from './graphql.js';
import { Utils } from './utils.js';
import { runCallbacks } from './callbacks.js';
import { getSetting, registerSetting } from './settings.js';
import { registerFragment, getDefaultFragmentText } from './fragments.js';
import escapeStringRegexp from 'escape-string-regexp';
-import { multiQueryTemplate, singleQueryTemplate, createMutationTemplate, updateMutationTemplate, upsertMutationTemplate, deleteMutationTemplate } from './graphql_templates';
+import { Locales, getIntlString } from './intl';
const wrapAsync = (Meteor.wrapAsync)? Meteor.wrapAsync : Meteor._wrapAsync;
// import { debug } from './debug.js';
@@ -119,6 +119,33 @@ Mongo.Collection.prototype.helpers = function(helpers) {
});
};
+/*
+
+Custom validation function to check for required locales
+
+See https://github.com/aldeed/simple-schema-js#custom-field-validation
+
+*/
+const validateIntlField = function () {
+ let errors = [];
+
+ // if field is required, go through locales to check which one are required
+ if (!this.definition.optional) {
+ const requiredLocales = Locales.filter(locale => locale.required);
+
+ requiredLocales.forEach((locale, index) => {
+ const strings = this.value;
+ const hasString = strings.some(s => s.locale === locale.id && s.value);
+ if (!hasString) {
+ errors.push({ path: this.key, locale: locale.id, index, name: `${this.key.replace('_intl', '')} (${locale.id})` });
+ }
+ });
+
+ }
+ // hack to work around the fact that custom validation function can only return a single string
+ return `intlError|${JSON.stringify(errors)}`;
+}
+
export const createCollection = options => {
const { typeName, collectionName = getCollectionName(typeName), schema, generateGraphQLSchema = true, dbCollectionName } = options;
@@ -138,23 +165,23 @@ export const createCollection = options => {
// generate foo_intl fields
Object.keys(schema).forEach(fieldName => {
const fieldSchema = schema[fieldName];
- if (fieldSchema.type && fieldSchema.type.name === 'IntlString') {
+ if (fieldSchema.intl || (fieldSchema.type && fieldSchema.type.name === 'IntlString')) {
// we have at least one intl field
hasIntlFields = true;
- // make non-intl field optional
- schema[fieldName].optional = true;
-
schema[`${fieldName}_intl`] = {
...schema[fieldName], // copy properties from regular field
hidden: true,
type: Array,
+ custom: validateIntlField,
}
schema[`${fieldName}_intl.$`] = {
- type: Object,
- blackbox: true,
+ type: getIntlString(),
}
+
+ // make non-intl field optional
+ schema[fieldName].optional = true;
}
});
diff --git a/packages/vulcan-lib/lib/modules/config.js b/packages/vulcan-lib/lib/modules/config.js
index f7dd70f2f..e8808dab6 100644
--- a/packages/vulcan-lib/lib/modules/config.js
+++ b/packages/vulcan-lib/lib/modules/config.js
@@ -51,6 +51,8 @@ SimpleSchema.extendOptions([
'query', // field-specific data loading query
'selectable', // field can be used as part of a selector when querying for data
'orderable', // field can be used to order results when querying for data
+
+ 'intl', // set to `true` to make a field international
]);
// eslint-disable-next-line no-undef
diff --git a/packages/vulcan-lib/lib/modules/intl.js b/packages/vulcan-lib/lib/modules/intl.js
index 9f5f587a6..c1d5df8c7 100644
--- a/packages/vulcan-lib/lib/modules/intl.js
+++ b/packages/vulcan-lib/lib/modules/intl.js
@@ -1,5 +1,4 @@
import SimpleSchema from 'simpl-schema';
-import { Strings } from './strings';
export const Locales = [];
@@ -31,14 +30,16 @@ Generate custom IntlString SimpleSchema type
*/
export const getIntlString = () => {
- const schema = {};
-
- Object.keys(Strings).forEach(locale => {
- schema[locale] = {
+ const schema = {
+ locale: {
type: String,
- optional: true,
- };
- });
+ optional: false,
+ },
+ value: {
+ type: String,
+ optional: false,
+ }
+ };
const IntlString = new SimpleSchema(schema);
IntlString.name = 'IntlString';
diff --git a/packages/vulcan-lib/lib/modules/validation.js b/packages/vulcan-lib/lib/modules/validation.js
index 08b677ab2..55768fa6c 100644
--- a/packages/vulcan-lib/lib/modules/validation.js
+++ b/packages/vulcan-lib/lib/modules/validation.js
@@ -47,11 +47,22 @@ export const validateDocument = (document, collection, context) => {
errors.forEach(error => {
// eslint-disable-next-line no-console
// console.log(error);
- validationErrors.push({
- id: `errors.${error.type}`,
- path: error.name,
- properties: error,
- });
+ if (error.type.includes('intlError')) {
+ const intlErrors = JSON.parse(error.type.replace('intlError|', ''));
+ intlErrors.forEach(intlError => {
+ validationErrors.push({
+ id: `errors.required`,
+ path: `${intlError.path}.${intlError.index}`,
+ properties: intlError,
+ });
+ });
+ } else {
+ validationErrors.push({
+ id: `errors.${error.type}`,
+ path: error.name,
+ properties: error,
+ });
+ }
});
}
@@ -96,11 +107,22 @@ export const validateModifier = (modifier, document, collection, context) => {
errors.forEach(error => {
// eslint-disable-next-line no-console
// console.log(error);
- validationErrors.push({
- id: `errors.${error.type}`,
- path: error.name,
- properties: error,
- });
+ if (error.type.includes('intlError')) {
+ const intlErrors = JSON.parse(error.type.replace('intlError|', ''));
+ intlErrors.forEach(intlError => {
+ validationErrors.push({
+ id: `errors.required`,
+ path: `${intlError.path}.${intlError.index}`,
+ properties: intlError,
+ });
+ });
+ } else {
+ validationErrors.push({
+ id: `errors.${error.type}`,
+ path: error.name,
+ properties: error,
+ });
+ }
});
}