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' }); ).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(() => expect(() =>
applyDefaults( applyDefaults(
fromJS({ 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', () => { 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 { validateConfig } from 'Constants/configSchema';
import { selectDefaultSortableFields, traverseFields } from '../reducers/collections'; import { selectDefaultSortableFields, traverseFields } from '../reducers/collections';
import { resolveBackend } from 'coreSrc/backend'; 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_REQUEST = 'CONFIG_REQUEST';
export const CONFIG_SUCCESS = 'CONFIG_SUCCESS'; export const CONFIG_SUCCESS = 'CONFIG_SUCCESS';
@ -68,38 +68,53 @@ const setI18nField = field => {
return field; return field;
}; };
const setI18nDefaults = (i18n, collection) => { const setI18nDefaults = (defaultI18n, collectionOrFile) => {
if (i18n && collection.has(I18N)) { if (defaultI18n && collectionOrFile.has(I18N)) {
const collectionI18n = collection.get(I18N); const collectionOrFileI18n = collectionOrFile.get(I18N);
if (collectionI18n === true) { if (collectionOrFileI18n === true) {
collection = collection.set(I18N, i18n); collectionOrFile = collectionOrFile.set(I18N, defaultI18n);
} else if (collectionI18n === false) { } else if (collectionOrFileI18n === false) {
collection = collection.delete(I18N); collectionOrFile = collectionOrFile.delete(I18N);
} else { } else {
const locales = collectionI18n.get('locales', i18n.get('locales')); const locales = collectionOrFileI18n.get('locales', defaultI18n.get('locales'));
const defaultLocale = collectionI18n.get( const defaultLocale = collectionOrFileI18n.get(
'default_locale', '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)); collectionOrFile = collectionOrFile.set(I18N, defaultI18n.merge(collectionOrFileI18n));
collection = collection.setIn([I18N, 'locales'], locales); collectionOrFile = collectionOrFile.setIn([I18N, 'locales'], locales);
collection = collection.setIn([I18N, 'default_locale'], defaultLocale); 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 // 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 { } else {
collection = collection.delete(I18N); collectionOrFile = collectionOrFile.delete(I18N);
collection = collection.set( if (collectionOrFile.has('fields')) {
'fields', collectionOrFile = collectionOrFile.set(
traverseFields(collection.get('fields'), field => field.delete(I18N)), '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 => { const throwOnMissingDefaultLocale = i18n => {
@ -211,6 +226,8 @@ export function applyDefaults(config) {
collection = collection.set('publish', true); collection = collection.set('publish', true);
} }
collection = setI18nDefaults(i18n, collection);
const folder = collection.get('folder'); const folder = collection.get('folder');
if (folder) { if (folder) {
if (collection.has('path') && !collection.has('media_folder')) { if (collection.has('path') && !collection.has('media_folder')) {
@ -238,15 +255,13 @@ export function applyDefaults(config) {
} else { } else {
collection = collection.set('meta', Map()); collection = collection.set('meta', Map());
} }
collection = setI18nDefaults(i18n, collection);
} }
const files = collection.get('files'); const files = collection.get('files');
if (files) { if (files) {
if (i18n && collection.has(I18N)) { const collectionI18n = collection.get(I18N);
throw new Error('i18n configuration is not supported for files collection'); throwOnInvalidFileCollectionStructure(collectionI18n);
}
collection = collection.delete('nested'); collection = collection.delete('nested');
collection = collection.delete('meta'); collection = collection.delete('meta');
collection = collection.set( collection = collection.set(
@ -258,6 +273,8 @@ export function applyDefaults(config) {
'fields', 'fields',
traverseFields(file.get('fields'), setDefaultPublicFolder), traverseFields(file.get('fields'), setDefaultPublicFolder),
); );
file = setI18nDefaults(collectionI18n, file);
throwOnInvalidFileCollectionStructure(file.get(I18N));
return file; return file;
}), }),
); );

View File

@ -400,6 +400,33 @@ describe('i18n', () => {
raw: '', 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', () => { describe('groupEntries', () => {

View File

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

View File

@ -130,7 +130,7 @@ collections:
### Limitations ### 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. 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: 3. Object widgets only support `i18n: true` and `i18n` configuration should be done per field: