diff --git a/packages/netlify-cms-core/src/__tests__/backend.spec.js b/packages/netlify-cms-core/src/__tests__/backend.spec.js index 24d15df2a447..9e8322766a28 100644 --- a/packages/netlify-cms-core/src/__tests__/backend.spec.js +++ b/packages/netlify-cms-core/src/__tests__/backend.spec.js @@ -657,17 +657,19 @@ describe('Backend', () => { const config = Map({}); const backend = new Backend(implementation, { config, backendName: 'github' }); - it('should combine mutiple content same folder entries', () => { + it('should combine multiple content same folder entries', () => { const entries = [ { path: 'posts/post.en.md', data: { title: 'Title en', content: 'Content en' }, i18nStructure: 'locale_file_extensions', + slugWithLocale: 'post.en', }, { path: 'posts/post.fr.md', data: { title: 'Title fr', content: 'Content fr' }, i18nStructure: 'locale_file_extensions', + slugWithLocale: 'post.fr', }, ]; @@ -683,7 +685,7 @@ describe('Backend', () => { ]); }); - it('should combine mutiple content different folder entries', () => { + it('should combine multiple content different folder entries', () => { const entries = [ { path: 'posts/en/post.md', @@ -714,9 +716,16 @@ describe('Backend', () => { }); describe('listAllMultipleEntires', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + const implementation = { + init: jest.fn(() => implementation), + }; + const config = Map({}); const backend = new Backend(implementation, { config, backendName: 'github' }); - it('should combine mutiple content same folder entries', async () => { + it('should combine multiple content same folder entries', async () => { const entries = [ { slug: 'post.en', @@ -751,7 +760,7 @@ describe('Backend', () => { }); }); - it('should combine mutiple content different folder entries', async () => { + it('should combine multiple content different folder entries', async () => { const entries = [ { slug: 'en/post', @@ -783,4 +792,66 @@ describe('Backend', () => { }); }); }); + + describe('getMultipleEntries', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + const implementation = { + init: jest.fn(() => implementation), + }; + const config = Map({}); + const backend = new Backend(implementation, { config, backendName: 'github' }); + const entryDraft = fromJS({ + entry: { + data: { + en: { title: 'post', content: 'Content en' }, + fr: { title: 'publier', content: 'Content fr' }, + }, + }, + }); + const entryObj = { path: 'posts/post.md', slug: 'post' }; + + it('should split multiple content into different locale file entries', async () => { + const collection = fromJS({ + i18n_structure: 'locale_file_extensions', + fields: [{ name: 'title' }, { name: 'content' }], + extension: 'md', + }); + + expect(backend.getMultipleEntries(collection, entryDraft, entryObj)).toEqual([ + { + slug: 'post', + path: 'posts/post.en.md', + raw: '---\ntitle: post\ncontent: Content en\n---\n', + }, + { + slug: 'post', + path: 'posts/post.fr.md', + raw: '---\ntitle: publier\ncontent: Content fr\n---\n', + }, + ]); + }); + + it('should split multiple content into different locale folder entries', async () => { + const collection = fromJS({ + i18n_structure: 'locale_folders', + fields: [{ name: 'title' }, { name: 'content' }], + extension: 'md', + }); + + expect(backend.getMultipleEntries(collection, entryDraft, entryObj)).toEqual([ + { + slug: 'post', + path: 'posts/en/post.md', + raw: '---\ntitle: post\ncontent: Content en\n---\n', + }, + { + slug: 'post', + path: 'posts/fr/post.md', + raw: '---\ntitle: publier\ncontent: Content fr\n---\n', + }, + ]); + }); + }); }); diff --git a/packages/netlify-cms-core/src/backend.ts b/packages/netlify-cms-core/src/backend.ts index 74fdfb8a0d40..a70ac72e8560 100644 --- a/packages/netlify-cms-core/src/backend.ts +++ b/packages/netlify-cms-core/src/backend.ts @@ -47,6 +47,7 @@ import { FilterRule, Collections, EntryDraft, + Entry, CollectionFile, State, EntryField, @@ -456,12 +457,11 @@ export class Backend { multiEntries = entries .filter(entry => locales.some(l => entry.slug.endsWith(`.${l}`))) .map(entry => { - const path = entry.path.split('.'); - const locale = path.splice(-2, 2)[0]; + const locale = entry.slug.slice(-2); return { ...entry, slug: entry.slug.replace(`.${locale}`, ''), - contentKey: path.join('.'), + contentKey: entry.path.replace(`.${locale}`, ''), i18nStructure, slugWithLocale: entry.slug, }; @@ -668,7 +668,7 @@ export class Backend { loadedEntries = await Promise.all( locales.map(l => this.implementation - .getEntry(path.replace(`${slug}`, `${l}/${slug}`)) + .getEntry(path.replace(`/${slug}`, `/${l}/${slug}`)) .catch(() => undefined), ), ); @@ -879,9 +879,8 @@ export class Backend { let path; entries.forEach(e => { if (i18nStructure === LOCALE_FILE_EXTENSIONS) { - const entryPath = e.path.split('.'); - const locale = entryPath.splice(-2, 1)[0]; - !path && (path = entryPath.join('.')); + const locale = e.slugWithLocale.slice(-2); + !path && (path = e.path.replace(`.${locale}`, '')); data[locale] = e.data; } else if (i18nStructure === LOCALE_FOLDERS) { const locale = e.slugWithLocale.slice(0, 2); @@ -1036,34 +1035,7 @@ export class Backend { let entriesObj = [entryObj]; if (MultiContentDiffFiles) { - const i18nStructure = collection.get('i18n_structure'); - const extension = selectFolderEntryExtension(collection); - const data = entryDraft.getIn(['entry', 'data']).toJS(); - const locales = Object.keys(data); - entriesObj = []; - if (i18nStructure === LOCALE_FILE_EXTENSIONS) { - locales.forEach(l => { - entriesObj.push({ - path: entryObj.path.replace(extension, `${l}.${extension}`), - slug: entryObj.slug, - raw: this.entryToRaw( - collection, - entryDraft.get('entry').set('data', entryDraft.getIn(['entry', 'data', l])), - ), - }); - }); - } else if (i18nStructure === LOCALE_FOLDERS) { - locales.forEach(l => { - entriesObj.push({ - path: entryObj.path.replace(`${entryObj.slug}`, `${l}/${entryObj.slug}`), - slug: entryObj.slug, - raw: this.entryToRaw( - collection, - entryDraft.get('entry').set('data', entryDraft.getIn(['entry', 'data', l])), - ), - }); - }); - } + entriesObj = this.getMultipleEntries(collection, entryDraft, entryObj); } const user = (await this.currentUser()) as User; @@ -1106,6 +1078,39 @@ export class Backend { return entryObj.slug; } + getMultipleEntries(collection: Collection, entryDraft: EntryDraft, entryObj: Entry) { + const i18nStructure = collection.get('i18n_structure'); + const extension = selectFolderEntryExtension(collection); + const data = entryDraft.getIn(['entry', 'data']).toJS(); + const locales = Object.keys(data); + const entriesObj = []; + if (i18nStructure === LOCALE_FILE_EXTENSIONS) { + locales.forEach(l => { + entriesObj.push({ + path: entryObj.path.replace(extension, `${l}.${extension}`), + slug: entryObj.slug, + raw: this.entryToRaw( + collection, + entryDraft.get('entry').set('data', entryDraft.getIn(['entry', 'data', l])), + ), + }); + }); + } else if (i18nStructure === LOCALE_FOLDERS) { + locales.forEach(l => { + entriesObj.push({ + path: entryObj.path.replace(`/${entryObj.slug}`, `/${l}/${entryObj.slug}`), + slug: entryObj.slug, + raw: this.entryToRaw( + collection, + entryDraft.get('entry').set('data', entryDraft.getIn(['entry', 'data', l])), + ), + }); + }); + } + + return entriesObj; + } + async invokeEventWithEntry(event: string, entry: EntryMap) { const { login, name } = (await this.currentUser()) as User; return await invokeEvent({ name: event, data: { entry, author: { login, name } } }); @@ -1191,7 +1196,7 @@ export class Backend { } else if (i18nStructure === LOCALE_FOLDERS) { for (const l of locales) { await this.implementation - .deleteFile(path.replace(`${slug}`, `${l}/${slug}`), commitMessage) + .deleteFile(path.replace(`/${slug}`, `/${l}/${slug}`), commitMessage) .catch(() => undefined); } } diff --git a/packages/netlify-cms-core/src/constants/multiContentTypes.js b/packages/netlify-cms-core/src/constants/multiContentTypes.js index 4d52c49749f7..4759f30bf50f 100644 --- a/packages/netlify-cms-core/src/constants/multiContentTypes.js +++ b/packages/netlify-cms-core/src/constants/multiContentTypes.js @@ -8,120 +8,120 @@ export const locales = [ 'aa', 'af', 'ak', - 'sq', 'am', 'ar', - 'hy', 'as', 'ba', - 'eu', 'be', - 'br', 'bg', - 'my', + 'bo', + 'br', 'ca', 'ce', - 'cu', - 'kw', 'co', - 'hr', 'cs', + 'cu', + 'cy', 'da', + 'de', 'dv', - 'nl', 'dz', + 'ee', 'en', 'eo', + 'es', 'et', - 'ee', - 'fo', + 'eu', + 'fa', + 'ff', 'fi', + 'fo', 'fr', - 'ff', + 'ga', + 'gd', 'gl', - 'lg', - 'ka', - 'de', - 'kl', 'gn', 'gu', + 'gv', 'he', 'hi', + 'hr', 'hu', - 'is', - 'ig', + 'hy', 'id', - 'ga', + 'ig', + 'is', 'it', 'ja', 'jv', - 'kn', - 'ks', - 'kk', + 'ka', 'ki', - 'rw', + 'kk', + 'kl', + 'kn', 'ko', - 'ky', + 'ks', 'ku', - 'lo', - 'lv', + 'kw', + 'ky', + 'lb', + 'lg', 'ln', + 'lo', 'lt', 'lu', - 'lb', - 'mk', + 'lv', 'mg', - 'ms', - 'ml', - 'mt', - 'gv', 'mi', + 'mk', + 'ml', 'mr', - 'ne', - 'nd', + 'ms', + 'mt', + 'my', 'nb', + 'nd', + 'ne', + 'nl', + 'nr', 'om', 'os', - 'ps', - 'fa', + 'pa', 'pl', + 'ps', 'pt', - 'pa', 'qu', - 'ro', 'rm', 'rn', + 'ro', 'ru', - 'sg', + 'rw', 'sa', - 'gd', - 'sn', 'sd', + 'sg', 'si', 'sk', 'sl', + 'sn', 'so', - 'nr', - 'es', + 'sq', 'ss', 'sv', 'ta', - 'tt', 'te', 'th', - 'bo', 'ti', - 'ts', - 'tr', 'tk', + 'tr', + 'ts', + 'tt', + 'ug', 'uk', 'ur', - 'ug', 'uz', 've', 'vi', 'vo', - 'cy', 'wo', 'xh', 'yo',