Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add different i18n path structure #7400

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
10 changes: 4 additions & 6 deletions packages/decap-cms-core/src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
getI18nBackup,
formatI18nBackup,
getI18nInfo,
getI18nFolder,
I18N_STRUCTURE,
} from './lib/i18n';

Expand Down Expand Up @@ -537,11 +538,7 @@ export class Backend {
if (collectionType === FOLDER) {
listMethod = () => {
const depth = collectionDepth(collection);
return this.implementation.entriesByFolder(
collection.get('folder') as string,
extension,
depth,
);
return this.implementation.entriesByFolder(getI18nFolder(collection), extension, depth);
};
} else if (collectionType === FILES) {
const files = collection
Expand Down Expand Up @@ -583,9 +580,10 @@ export class Backend {
if (collection.get('folder') && this.implementation.allEntriesByFolder) {
const depth = collectionDepth(collection);
const extension = selectFolderEntryExtension(collection);

return this.implementation
.allEntriesByFolder(
collection.get('folder') as string,
getI18nFolder(collection),
extension,
depth,
collectionRegex(collection),
Expand Down
223 changes: 221 additions & 2 deletions packages/decap-cms-core/src/lib/__tests__/i18n.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ describe('i18n', () => {
});

describe('getI18nFilesDepth', () => {
it('should increase depth when i18n structure is I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT', () => {
expect(
i18n.getI18nFilesDepth(
fromJS({ i18n: { structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT } }),
5,
),
).toBe(6);
});

it('should increase depth when i18n structure is I18N_STRUCTURE.MULTIPLE_FOLDERS', () => {
expect(
i18n.getI18nFilesDepth(
Expand Down Expand Up @@ -131,6 +140,18 @@ describe('i18n', () => {
});

describe('getFilePath', () => {
it('should return directory path based on locale when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT', () => {
expect(
i18n.getFilePath(
i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
'md',
'src/content/index.md',
'index',
'de',
),
).toEqual('src/de/content/index.md');
});

it('should return directory path based on locale when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS', () => {
expect(
i18n.getFilePath(
Expand Down Expand Up @@ -171,7 +192,21 @@ describe('i18n', () => {
describe('getFilePaths', () => {
const args = ['md', 'src/content/index.md', 'index'];

it('should return file paths for all locales', () => {
it('should return file paths for all locales with MULTIPLE_FOLDERS_I18N_ROOT structure', () => {
expect(
i18n.getFilePaths(
fromJS({
i18n: {
structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
locales: ['en', 'de'],
},
}),
...args,
),
).toEqual(['src/en/content/index.md', 'src/de/content/index.md']);
});

it('should return file paths for all locales with MULTIPLE_FOLDERS structure', () => {
expect(
i18n.getFilePaths(
fromJS({
Expand All @@ -195,6 +230,16 @@ describe('i18n', () => {
});

describe('normalizeFilePath', () => {
it('should remove locale folder from path when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT', () => {
expect(
i18n.normalizeFilePath(
i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
'src/en/content/index.md',
'en',
),
).toEqual('src/content/index.md');
});

it('should remove locale folder from path when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS', () => {
expect(
i18n.normalizeFilePath(
Expand All @@ -219,6 +264,16 @@ describe('i18n', () => {
});

describe('getLocaleFromPath', () => {
it('should return the locale from folder name in the path when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT', () => {
expect(
i18n.getLocaleFromPath(
i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
'md',
'src/en/content/index.md',
),
).toEqual('en');
});

it('should return the locale from folder name in the path when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS', () => {
expect(
i18n.getLocaleFromPath(
Expand Down Expand Up @@ -280,6 +335,37 @@ describe('i18n', () => {
]);
});

it('should return a folder based files when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT', () => {
expect(
i18n.getI18nFiles(
fromJS({
i18n: {
structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
locales,
default_locale,
},
}),
...args,
),
).toEqual([
{
path: 'src/en/content/index.md',
raw: { title: 'en_title' },
slug: 'index',
},
{
path: 'src/de/content/index.md',
raw: { title: 'de_title' },
slug: 'index',
},
{
path: 'src/fr/content/index.md',
raw: { title: 'fr_title' },
slug: 'index',
},
]);
});

it('should return a folder based files when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS', () => {
expect(
i18n.getI18nFiles(
Expand Down Expand Up @@ -340,6 +426,52 @@ describe('i18n', () => {
const default_locale = 'en';
const args = ['md', 'src/content/index.md', 'index'];

it('should return i18n entry content when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT', async () => {
const data = {
'src/en/content/index.md': {
slug: 'index',
path: 'src/en/content/index.md',
data: { title: 'en_title' },
},
'src/de/content/index.md': {
slug: 'index',
path: 'src/de/content/index.md',
data: { title: 'de_title' },
},
'src/fr/content/index.md': {
slug: 'index',
path: 'src/fr/content/index.md',
data: { title: 'fr_title' },
},
};
const getEntryValue = jest.fn(path =>
data[path] ? Promise.resolve(data[path]) : Promise.reject('Not found'),
);

await expect(
i18n.getI18nEntry(
fromJS({
i18n: {
structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
locales,
default_locale,
},
}),
...args,
getEntryValue,
),
).resolves.toEqual({
slug: 'index',
path: 'src/content/index.md',
data: { title: 'en_title' },
i18n: {
de: { data: { title: 'de_title' } },
fr: { data: { title: 'fr_title' } },
},
raw: '',
});
});

it('should return i18n entry content when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS', async () => {
const data = {
'src/content/en/index.md': {
Expand Down Expand Up @@ -493,6 +625,48 @@ describe('i18n', () => {
const default_locale = 'en';
const extension = 'md';

it('should group entries array when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT', () => {
const entries = [
{
slug: 'index',
path: 'src/en/content/index.md',
data: { title: 'en_title' },
},
{
slug: 'index',
path: 'src/de/content/index.md',
data: { title: 'de_title' },
},
{
slug: 'index',
path: 'src/fr/content/index.md',
data: { title: 'fr_title' },
},
];

expect(
i18n.groupEntries(
fromJS({
i18n: {
structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
locales,
default_locale,
},
}),
extension,
entries,
),
).toEqual([
{
slug: 'index',
path: 'src/content/index.md',
data: { title: 'en_title' },
i18n: { de: { data: { title: 'de_title' } }, fr: { data: { title: 'fr_title' } } },
raw: '',
},
]);
});

it('should group entries array when structure is I18N_STRUCTURE.MULTIPLE_FOLDERS', () => {
const entries = [
{
Expand Down Expand Up @@ -610,6 +784,26 @@ describe('i18n', () => {

const args = ['md', 'src/content/index.md', 'index'];

it('should add missing locale files to diff files when structure is MULTIPLE_FOLDERS_I18N_ROOT', () => {
expect(
i18n.getI18nDataFiles(
fromJS({
i18n: {
structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
locales,
default_locale,
},
}),
...args,
[{ path: 'src/fr/content/index.md', id: 'id', newFile: false }],
),
).toEqual([
{ path: 'src/en/content/index.md', id: '', newFile: false },
{ path: 'src/de/content/index.md', id: '', newFile: false },
{ path: 'src/fr/content/index.md', id: 'id', newFile: false },
]);
});

it('should add missing locale files to diff files when structure is MULTIPLE_FOLDERS', () => {
expect(
i18n.getI18nDataFiles(
Expand Down Expand Up @@ -656,7 +850,32 @@ describe('i18n', () => {
});

describe('getI18nBackup', () => {
it('should return i18n with raw data', () => {
it('should return i18n with raw data with MULTIPLE_FOLDERS_I18N_ROOT structure', () => {
const locales = ['en', 'de', 'fr'];
const default_locale = 'en';

expect(
i18n.getI18nBackup(
fromJS({
i18n: {
structure: i18n.I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT,
locales,
default_locale,
},
}),
fromJS({
data: 'raw_en',
i18n: {
de: { data: 'raw_de' },
fr: { data: 'raw_fr' },
},
}),
e => e.get('data'),
),
).toEqual({ de: { raw: 'raw_de' }, fr: { raw: 'raw_fr' } });
});

it('should return i18n with raw data with MULTIPLE FILES structure', () => {
const locales = ['en', 'de', 'fr'];
const default_locale = 'en';

Expand Down
31 changes: 30 additions & 1 deletion packages/decap-cms-core/src/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { EntryValue } from '../valueObjects/Entry';
export const I18N = 'i18n';

export enum I18N_STRUCTURE {
MULTIPLE_FOLDERS_I18N_ROOT = 'multiple_folders_i18n_root',
MULTIPLE_FOLDERS = 'multiple_folders',
MULTIPLE_FILES = 'multiple_files',
SINGLE_FILE = 'single_file',
Expand Down Expand Up @@ -40,7 +41,10 @@ export function getI18nInfo(collection: Collection) {

export function getI18nFilesDepth(collection: Collection, depth: number) {
const { structure } = getI18nInfo(collection) as I18nInfo;
if (structure === I18N_STRUCTURE.MULTIPLE_FOLDERS) {
if (
structure === I18N_STRUCTURE.MULTIPLE_FOLDERS ||
structure === I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT
) {
return depth + 1;
}
return depth;
Expand Down Expand Up @@ -70,6 +74,24 @@ export function getDataPath(locale: string, defaultLocale: string) {
return dataPath;
}

export function getI18nFolder(collection: Collection): string {
let folder = collection.get('folder') as string;

// modify folder in case we want to insert the i18n locale at the root of the file path
const { defaultLocale, structure } = getI18nInfo(collection) as I18nInfo;
if (structure === I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT) {
folder = insertI18nAtRoot(folder, defaultLocale);
}

return folder;
}

export function insertI18nAtRoot(path: string, locale: string): string {
const parts = path.split('/');
parts.splice(1, 0, locale);
return parts.join('/');
}

export function getFilePath(
structure: I18N_STRUCTURE,
extension: string,
Expand All @@ -78,6 +100,8 @@ export function getFilePath(
locale: string,
) {
switch (structure) {
case I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT:
return insertI18nAtRoot(path, locale);
case I18N_STRUCTURE.MULTIPLE_FOLDERS:
return path.replace(`/${slug}`, `/${locale}/${slug}`);
case I18N_STRUCTURE.MULTIPLE_FILES:
Expand All @@ -90,6 +114,10 @@ export function getFilePath(

export function getLocaleFromPath(structure: I18N_STRUCTURE, extension: string, path: string) {
switch (structure) {
case I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT: {
const parts = path.split('/');
return parts[1];
}
case I18N_STRUCTURE.MULTIPLE_FOLDERS: {
const parts = path.split('/');
// filename
Expand Down Expand Up @@ -128,6 +156,7 @@ export function getFilePaths(

export function normalizeFilePath(structure: I18N_STRUCTURE, path: string, locale: string) {
switch (structure) {
case I18N_STRUCTURE.MULTIPLE_FOLDERS_I18N_ROOT:
case I18N_STRUCTURE.MULTIPLE_FOLDERS:
return path.replace(`${locale}/`, '');
case I18N_STRUCTURE.MULTIPLE_FILES:
Expand Down