mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 10:01:40 -05:00
180 lines
5.8 KiB
JavaScript
180 lines
5.8 KiB
JavaScript
// see https://github.com/apollographql/graphql-tools/blob/master/docs/source/schema-directives.md#marking-strings-for-internationalization
|
|
|
|
import { addGraphQLDirective, addGraphQLSchema } from '../modules/graphql';
|
|
import { SchemaDirectiveVisitor } from 'graphql-tools';
|
|
import { defaultFieldResolver } from 'graphql';
|
|
import { Collections } from '../modules/collections';
|
|
import { getSetting } from '../modules/settings';
|
|
import { debug } from '../modules/debug';
|
|
import Vulcan from '../modules/config';
|
|
import { isIntlField } from '../modules/intl';
|
|
import { Connectors } from './connectors';
|
|
import pickBy from 'lodash/pickBy';
|
|
|
|
/*
|
|
|
|
Create GraphQL types
|
|
|
|
*/
|
|
const intlValueSchemas = `type IntlValue {
|
|
locale: String
|
|
value: String
|
|
}
|
|
input IntlValueInput{
|
|
locale: String
|
|
value: String
|
|
}`;
|
|
addGraphQLSchema(intlValueSchemas);
|
|
|
|
/*
|
|
|
|
Take an array of translations, a locale, and a default locale, and return a matching string
|
|
|
|
*/
|
|
const getLocaleString = (translations, locale, defaultLocale) => {
|
|
const localeObject = translations.find(translation => translation.locale === locale);
|
|
const defaultLocaleObject = translations.find(
|
|
translation => translation.locale === defaultLocale
|
|
);
|
|
return (localeObject && localeObject.value) || (defaultLocaleObject && defaultLocaleObject.value);
|
|
};
|
|
|
|
/*
|
|
|
|
GraphQL @intl directive resolver
|
|
|
|
*/
|
|
class IntlDirective extends SchemaDirectiveVisitor {
|
|
visitFieldDefinition(field, details) {
|
|
const { resolve = defaultFieldResolver, name } = field;
|
|
field.resolve = async function(...args) {
|
|
const [doc, graphQLArguments, context] = args;
|
|
const fieldValue = await resolve.apply(this, args);
|
|
const locale = graphQLArguments.locale || context.locale;
|
|
const defaultLocale = getSetting('locale');
|
|
const intlField = doc[`${name}_intl`];
|
|
// Return string in requested or default language, or else field's original value
|
|
return (intlField && getLocaleString(intlField, locale, defaultLocale)) || fieldValue;
|
|
};
|
|
}
|
|
}
|
|
|
|
addGraphQLDirective({ intl: IntlDirective });
|
|
|
|
addGraphQLSchema('directive @intl on FIELD_DEFINITION');
|
|
|
|
/*
|
|
|
|
Migration function
|
|
|
|
*/
|
|
const migrateIntlFields = async defaultLocale => {
|
|
if (!defaultLocale) {
|
|
throw new Error(
|
|
"Please pass the id of the locale to which to migrate your current content (e.g. migrateIntlFields('en'))"
|
|
);
|
|
}
|
|
|
|
Collections.forEach(async collection => {
|
|
const schema = collection.simpleSchema()._schema;
|
|
const intlFields = pickBy(schema, isIntlField);
|
|
const intlFieldsNames = Object.keys(intlFields);
|
|
|
|
if (intlFieldsNames.length) {
|
|
// eslint-disable-next-line no-console
|
|
console.log(
|
|
`### Found ${intlFieldsNames.length} field to migrate for collection ${
|
|
collection.options.collectionName
|
|
}: ${intlFieldsNames.join(', ')} ###\n`
|
|
);
|
|
|
|
// const intlFieldsWithLocale = intlFieldsNames.map(f => `${f}_intl`);
|
|
|
|
// find all documents with one or more unmigrated intl fields
|
|
const selector = {
|
|
$or: intlFieldsNames.map(f => {
|
|
return {
|
|
$and: [{ [`${f}`]: { $exists: true } }, { [`${f}_intl`]: { $exists: false } }],
|
|
};
|
|
}),
|
|
};
|
|
const documentsToMigrate = await Connectors.find(collection, selector);
|
|
|
|
if (documentsToMigrate.length) {
|
|
console.log(`-> found ${documentsToMigrate.length} documents to migrate \n`); // eslint-disable-line no-console
|
|
for (const doc of documentsToMigrate) {
|
|
console.log(`// Migrating document ${doc._id}`); // eslint-disable-line no-console
|
|
const modifier = { $push: {} };
|
|
|
|
intlFieldsNames.forEach(f => {
|
|
if (doc[f] && !doc[`${f}_intl`]) {
|
|
const translationObject = { locale: defaultLocale, value: doc[f] };
|
|
console.log(`-> Adding field ${f}_intl: ${JSON.stringify(translationObject)} `); // eslint-disable-line no-console
|
|
modifier.$push[`${f}_intl`] = translationObject;
|
|
}
|
|
});
|
|
|
|
if (!_.isEmpty(modifier.$push)) {
|
|
// update document
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const n = await Connectors.update(collection, { _id: doc._id }, modifier);
|
|
console.log(`-> migrated ${n} documents \n`); // eslint-disable-line no-console
|
|
}
|
|
console.log('\n'); // eslint-disable-line no-console
|
|
}
|
|
} else {
|
|
console.log('-> found no documents to migrate.'); // eslint-disable-line no-console
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Vulcan.migrateIntlFields = migrateIntlFields;
|
|
|
|
/*
|
|
|
|
Take a header object, and figure out the locale
|
|
|
|
Also accepts userLocale to indicate the current user's preferred locale
|
|
|
|
*/
|
|
export const getHeaderLocale = (headers, userLocale) => {
|
|
let cookieLocale, acceptedLocale, locale, localeMethod;
|
|
|
|
// get locale from cookies
|
|
if (headers['cookie']) {
|
|
const cookies = {};
|
|
headers['cookie'].split('; ').forEach(c => {
|
|
const cookieArray = c.split('=');
|
|
cookies[cookieArray[0]] = cookieArray[1];
|
|
});
|
|
cookieLocale = cookies.locale;
|
|
}
|
|
|
|
// get locale from accepted-language header
|
|
if (headers['accept-language']) {
|
|
const acceptedLanguages = headers['accept-language'].split(',').map(l => l.split(';')[0]);
|
|
acceptedLocale = acceptedLanguages[0]; // for now only use the highest-priority accepted language
|
|
}
|
|
|
|
if (headers.locale) {
|
|
locale = headers.locale;
|
|
localeMethod = 'header';
|
|
} else if (cookieLocale) {
|
|
locale = cookieLocale;
|
|
localeMethod = 'cookie';
|
|
} else if (userLocale) {
|
|
locale = userLocale;
|
|
localeMethod = 'user';
|
|
} else if (acceptedLocale) {
|
|
locale = acceptedLocale;
|
|
localeMethod = 'browser';
|
|
} else {
|
|
locale = getSetting('locale', 'en-US');
|
|
localeMethod = 'setting';
|
|
}
|
|
|
|
debug(`// locale: ${locale} (via ${localeMethod})`);
|
|
|
|
return locale;
|
|
};
|