feat: i18n support for file collections - closes #4483 (#4634)

This commit is contained in:
Erez Rokah 2020-11-26 02:23:53 -08:00 committed by GitHub
parent bbdbae2ea2
commit 53061710a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 150 additions and 31 deletions

View File

@ -636,7 +636,33 @@ describe('config', () => {
).toEqual({ structure: 'multiple_folders', locales: ['en', 'fr'], default_locale: 'fr' });
});
it('should throw when i18n is set on files collection', () => {
it('should throw when i18n structure is not single_file on files collection', () => {
expect(() =>
applyDefaults(
fromJS({
i18n: {
structure: 'multiple_folders',
locales: ['en', 'de'],
},
collections: [
{
files: [
{
name: 'file',
file: 'file',
i18n: true,
fields: [{ name: 'title', widget: 'string', i18n: true }],
},
],
i18n: true,
},
],
}),
),
).toThrow('i18n configuration for files collections is limited to single_file structure');
});
it('should throw when i18n structure is set to multiple_folders and contains a single file collection', () => {
expect(() =>
applyDefaults(
fromJS({
@ -654,7 +680,56 @@ describe('config', () => {
],
}),
),
).toThrow('i18n configuration is not supported for files collection');
).toThrow('i18n configuration for files collections is limited to single_file structure');
});
it('should throw when i18n structure is set to multiple_files and contains a single file collection', () => {
expect(() =>
applyDefaults(
fromJS({
i18n: {
structure: 'multiple_files',
locales: ['en', 'de'],
},
collections: [
{
files: [
{ name: 'file', file: 'file', fields: [{ name: 'title', widget: 'string' }] },
],
i18n: true,
},
],
}),
),
).toThrow('i18n configuration for files collections is limited to single_file structure');
});
it('should set i18n value to translate on field when i18n=true for field in files collection', () => {
expect(
applyDefaults(
fromJS({
i18n: {
structure: 'multiple_folders',
locales: ['en', 'de'],
},
collections: [
{
files: [
{
name: 'file',
file: 'file',
i18n: true,
fields: [{ name: 'title', widget: 'string', i18n: true }],
},
],
i18n: {
structure: 'single_file',
},
},
],
}),
).getIn(['collections', 0, 'files', 0, 'fields', 0, 'i18n']),
).toEqual('translate');
});
it('should set i18n value to translate on field when i18n=true for field', () => {

View File

@ -6,7 +6,7 @@ import * as publishModes from 'Constants/publishModes';
import { validateConfig } from 'Constants/configSchema';
import { selectDefaultSortableFields, traverseFields } from '../reducers/collections';
import { resolveBackend } from 'coreSrc/backend';
import { I18N, I18N_FIELD } from '../lib/i18n';
import { I18N, I18N_FIELD, I18N_STRUCTURE } from '../lib/i18n';
export const CONFIG_REQUEST = 'CONFIG_REQUEST';
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
@ -68,38 +68,53 @@ const setI18nField = field => {
return field;
};
const setI18nDefaults = (i18n, collection) => {
if (i18n && collection.has(I18N)) {
const collectionI18n = collection.get(I18N);
if (collectionI18n === true) {
collection = collection.set(I18N, i18n);
} else if (collectionI18n === false) {
collection = collection.delete(I18N);
const setI18nDefaults = (defaultI18n, collectionOrFile) => {
if (defaultI18n && collectionOrFile.has(I18N)) {
const collectionOrFileI18n = collectionOrFile.get(I18N);
if (collectionOrFileI18n === true) {
collectionOrFile = collectionOrFile.set(I18N, defaultI18n);
} else if (collectionOrFileI18n === false) {
collectionOrFile = collectionOrFile.delete(I18N);
} else {
const locales = collectionI18n.get('locales', i18n.get('locales'));
const defaultLocale = collectionI18n.get(
const locales = collectionOrFileI18n.get('locales', defaultI18n.get('locales'));
const defaultLocale = collectionOrFileI18n.get(
'default_locale',
collectionI18n.has('locales') ? locales.first() : i18n.get('default_locale'),
collectionOrFileI18n.has('locales') ? locales.first() : defaultI18n.get('default_locale'),
);
collection = collection.set(I18N, i18n.merge(collectionI18n));
collection = collection.setIn([I18N, 'locales'], locales);
collection = collection.setIn([I18N, 'default_locale'], defaultLocale);
collectionOrFile = collectionOrFile.set(I18N, defaultI18n.merge(collectionOrFileI18n));
collectionOrFile = collectionOrFile.setIn([I18N, 'locales'], locales);
collectionOrFile = collectionOrFile.setIn([I18N, 'default_locale'], defaultLocale);
throwOnMissingDefaultLocale(collection.get(I18N));
throwOnMissingDefaultLocale(collectionOrFile.get(I18N));
}
if (collectionI18n !== false) {
if (collectionOrFileI18n !== false) {
// set default values for i18n fields
collection = collection.set('fields', traverseFields(collection.get('fields'), setI18nField));
if (collectionOrFile.has('fields')) {
collectionOrFile = collectionOrFile.set(
'fields',
traverseFields(collectionOrFile.get('fields'), setI18nField),
);
}
}
} else {
collection = collection.delete(I18N);
collection = collection.set(
'fields',
traverseFields(collection.get('fields'), field => field.delete(I18N)),
collectionOrFile = collectionOrFile.delete(I18N);
if (collectionOrFile.has('fields')) {
collectionOrFile = collectionOrFile.set(
'fields',
traverseFields(collectionOrFile.get('fields'), field => field.delete(I18N)),
);
}
}
return collectionOrFile;
};
const throwOnInvalidFileCollectionStructure = i18n => {
if (i18n && i18n.get('structure') !== I18N_STRUCTURE.SINGLE_FILE) {
throw new Error(
`i18n configuration for files collections is limited to ${I18N_STRUCTURE.SINGLE_FILE} structure`,
);
}
return collection;
};
const throwOnMissingDefaultLocale = i18n => {
@ -211,6 +226,8 @@ export function applyDefaults(config) {
collection = collection.set('publish', true);
}
collection = setI18nDefaults(i18n, collection);
const folder = collection.get('folder');
if (folder) {
if (collection.has('path') && !collection.has('media_folder')) {
@ -238,15 +255,13 @@ export function applyDefaults(config) {
} else {
collection = collection.set('meta', Map());
}
collection = setI18nDefaults(i18n, collection);
}
const files = collection.get('files');
if (files) {
if (i18n && collection.has(I18N)) {
throw new Error('i18n configuration is not supported for files collection');
}
const collectionI18n = collection.get(I18N);
throwOnInvalidFileCollectionStructure(collectionI18n);
collection = collection.delete('nested');
collection = collection.delete('meta');
collection = collection.set(
@ -258,6 +273,8 @@ export function applyDefaults(config) {
'fields',
traverseFields(file.get('fields'), setDefaultPublicFolder),
);
file = setI18nDefaults(collectionI18n, file);
throwOnInvalidFileCollectionStructure(file.get(I18N));
return file;
}),
);

View File

@ -400,6 +400,33 @@ describe('i18n', () => {
raw: '',
});
});
it('should default to empty data object when file is empty and structure is I18N_STRUCTURE.SINGLE_FILE', async () => {
const data = {
'src/content/index.md': {
slug: 'index',
path: 'src/content/index.md',
data: {},
},
};
const getEntryValue = jest.fn(path => Promise.resolve(data[path]));
await expect(
i18n.getI18nEntry(
fromJS({
i18n: { structure: i18n.I18N_STRUCTURE.SINGLE_FILE, locales, default_locale },
}),
...args,
getEntryValue,
),
).resolves.toEqual({
slug: 'index',
path: 'src/content/index.md',
data: {},
i18n: {},
raw: '',
});
});
});
describe('groupEntries', () => {

View File

@ -244,7 +244,7 @@ const mergeValues = (
};
const mergeSingleFileValue = (entryValue: EntryValue, defaultLocale: string, locales: string[]) => {
const data = entryValue.data[defaultLocale];
const data = entryValue.data[defaultLocale] || {};
const i18n = locales
.filter(l => l !== defaultLocale)
.map(l => ({ locale: l, value: entryValue.data[l] }))

View File

@ -130,7 +130,7 @@ collections:
### Limitations
1. File collections are not supported.
1. File collections support only `structure: single_file`.
2. List widgets only support `i18n: true`. `i18n` configuration on sub fields is ignored.
3. Object widgets only support `i18n: true` and `i18n` configuration should be done per field: