@akemona-org/strapi-plugin-i18n 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +19 -0
- package/admin/src/assets/images/logo.svg +1 -0
- package/admin/src/components/CMEditViewCopyLocale/index.js +183 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/cleanData.js +36 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/generateOptions.js +22 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/index.js +2 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/removePasswordAndRelationsFieldFromData.js +54 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/cleanData.test.js +83 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/data.js +219 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/generateOptions.test.js +79 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/removePasswordAndRelationsFieldFromData.test.js +40 -0
- package/admin/src/components/CMEditViewInjectedComponents/index.js +58 -0
- package/admin/src/components/CMEditViewLocalePicker/Option.js +66 -0
- package/admin/src/components/CMEditViewLocalePicker/Wrapper.js +8 -0
- package/admin/src/components/CMEditViewLocalePicker/index.js +160 -0
- package/admin/src/components/CMEditViewLocalePicker/utils/addStatusColorToLocale.js +24 -0
- package/admin/src/components/CMEditViewLocalePicker/utils/createLocalesOption.js +20 -0
- package/admin/src/components/CMEditViewLocalePicker/utils/index.js +2 -0
- package/admin/src/components/CheckboxConfirmation/Wrapper.js +12 -0
- package/admin/src/components/CheckboxConfirmation/index.js +70 -0
- package/admin/src/components/DeleteModalAdditionalInfos/index.js +25 -0
- package/admin/src/components/LocaleList/index.js +101 -0
- package/admin/src/components/LocaleListCell/LocaleListCell.js +90 -0
- package/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js +128 -0
- package/admin/src/components/LocalePicker/index.js +126 -0
- package/admin/src/components/LocaleRow/index.js +77 -0
- package/admin/src/components/ModalCreate/AdvancedForm.js +45 -0
- package/admin/src/components/ModalCreate/BaseForm.js +103 -0
- package/admin/src/components/ModalCreate/index.js +136 -0
- package/admin/src/components/ModalDelete/index.js +49 -0
- package/admin/src/components/ModalEdit/AdvancedForm.js +51 -0
- package/admin/src/components/ModalEdit/BaseForm.js +91 -0
- package/admin/src/components/ModalEdit/index.js +122 -0
- package/admin/src/components/SettingsModal.js +66 -0
- package/admin/src/components/index.js +2 -0
- package/admin/src/containers/Initializer.js +31 -0
- package/admin/src/containers/SettingsPage/LocaleSettingsPage.js +69 -0
- package/admin/src/containers/SettingsPage/index.js +33 -0
- package/admin/src/containers/SettingsPage/tests/SettingsPage.test.js +744 -0
- package/admin/src/containers/SettingsPage/tests/__snapshots__/SettingsPage.test.js.snap +241 -0
- package/admin/src/hooks/constants.js +6 -0
- package/admin/src/hooks/reducers.js +63 -0
- package/admin/src/hooks/tests/reducers.test.js +203 -0
- package/admin/src/hooks/useAddLocale/index.js +60 -0
- package/admin/src/hooks/useContentTypePermissions/index.js +16 -0
- package/admin/src/hooks/useDefaultLocales/index.js +27 -0
- package/admin/src/hooks/useDeleteLocale/index.js +45 -0
- package/admin/src/hooks/useEditLocale/index.js +46 -0
- package/admin/src/hooks/useHasI18n/index.js +13 -0
- package/admin/src/hooks/useLocales/index.js +35 -0
- package/admin/src/index.js +169 -0
- package/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js +83 -0
- package/admin/src/middlewares/addLocaleColumnToListViewMiddleware.js +32 -0
- package/admin/src/middlewares/addLocaleToCollectionTypesMiddleware.js +25 -0
- package/admin/src/middlewares/addLocaleToSingleTypesMiddleware.js +25 -0
- package/admin/src/middlewares/extendCMEditViewLayoutMiddleware.js +159 -0
- package/admin/src/middlewares/extendCTBAttributeInitialDataMiddleware.js +58 -0
- package/admin/src/middlewares/extendCTBInitialDataMiddleware.js +33 -0
- package/admin/src/middlewares/index.js +21 -0
- package/admin/src/middlewares/localePermissionMiddleware.js +39 -0
- package/admin/src/middlewares/tests/addCommonFieldsToInitialDataMiddleware.test.js +97 -0
- package/admin/src/middlewares/tests/addLocaleColumnToListViewMiddleware.test.js +68 -0
- package/admin/src/middlewares/tests/addLocaleToCollectionTypesMiddleware.test.js +200 -0
- package/admin/src/middlewares/tests/addLocaleToSingleTypesMiddleware.test.js +193 -0
- package/admin/src/middlewares/tests/extendCMEditViewLayoutMiddleware.test.js +556 -0
- package/admin/src/middlewares/tests/extendCTBAttrributeInitialDataMiddleware.test.js +124 -0
- package/admin/src/middlewares/tests/extendCTBInitialDataMiddleware.test.js +92 -0
- package/admin/src/middlewares/tests/localePermissionMiddleware.test.js +150 -0
- package/admin/src/middlewares/utils/addLocaleToLinksSearch.js +56 -0
- package/admin/src/middlewares/utils/tests/addLocaleToLinksSearch.test.js +137 -0
- package/admin/src/permissions.js +9 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/schemas.js +7 -0
- package/admin/src/selectors/selectCollectionTypesRelatedPermissions.js +4 -0
- package/admin/src/selectors/selectI18nLocales.js +3 -0
- package/admin/src/translations/en.json +60 -0
- package/admin/src/translations/fr.json +9 -0
- package/admin/src/translations/index.js +11 -0
- package/admin/src/translations/zh-Hans.json +60 -0
- package/admin/src/utils/getDefaultLocale.js +60 -0
- package/admin/src/utils/getInitialLocale.js +14 -0
- package/admin/src/utils/getLocaleFromQuery.js +7 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/admin/src/utils/index.js +2 -0
- package/admin/src/utils/localizedFields.js +23 -0
- package/admin/src/utils/mutateCTBContentTypeSchema.js +66 -0
- package/admin/src/utils/tests/getDefaultLocale.test.js +337 -0
- package/admin/src/utils/tests/getInitialLocale.test.js +106 -0
- package/admin/src/utils/tests/mutateCTBContentTypeSchema.test.js +205 -0
- package/config/functions/bootstrap.js +57 -0
- package/config/functions/migrations/__tests__/content-type.test.js +255 -0
- package/config/functions/migrations/__tests__/field.test.js +150 -0
- package/config/functions/migrations/content-type/disable/index.js +34 -0
- package/config/functions/migrations/content-type/disable/migrate-for-bookshelf.js +58 -0
- package/config/functions/migrations/content-type/disable/migrate-for-mongoose.js +39 -0
- package/config/functions/migrations/content-type/enable/index.js +40 -0
- package/config/functions/migrations/content-type/utils/index.js +27 -0
- package/config/functions/migrations/field/__tests__/utils.test.js +53 -0
- package/config/functions/migrations/field/index.js +37 -0
- package/config/functions/migrations/field/migrate-for-bookshelf.js +72 -0
- package/config/functions/migrations/field/migrate-for-mongoose.js +24 -0
- package/config/functions/migrations/field/migrate.js +55 -0
- package/config/functions/migrations/field/utils.js +58 -0
- package/config/functions/register.js +46 -0
- package/config/policies/validateLocaleCreation.js +68 -0
- package/config/routes.json +64 -0
- package/constants/__tests__/index.test.js +27 -0
- package/constants/index.js +36 -0
- package/constants/iso-locales.json +2002 -0
- package/controllers/__tests__/content-types.test.js +113 -0
- package/controllers/__tests__/iso-locales.test.js +26 -0
- package/controllers/__tests__/locales.test.js +308 -0
- package/controllers/content-types.js +64 -0
- package/controllers/iso-locales.js +11 -0
- package/controllers/locales.js +104 -0
- package/domain/locale.js +10 -0
- package/middlewares/i18n/defaults.json +5 -0
- package/middlewares/i18n/index.js +28 -0
- package/models/Locale.settings.json +31 -0
- package/oas.yml +195 -0
- package/package.json +31 -0
- package/services/__tests__/__snapshots__/iso-locales.test.js.snap +2006 -0
- package/services/__tests__/content-types.test.js +545 -0
- package/services/__tests__/core-api.test.js +106 -0
- package/services/__tests__/entity-service-decorator.test.js +280 -0
- package/services/__tests__/iso-locales.test.js +11 -0
- package/services/__tests__/locales.test.js +237 -0
- package/services/__tests__/localizations.test.js +187 -0
- package/services/__tests__/metrics.test.js +90 -0
- package/services/content-types.js +200 -0
- package/services/core-api.js +296 -0
- package/services/entity-service-decorator.js +155 -0
- package/services/iso-locales.js +9 -0
- package/services/locales.js +97 -0
- package/services/localizations.js +65 -0
- package/services/metrics.js +24 -0
- package/services/permissions/actions.js +124 -0
- package/services/permissions/engine.js +63 -0
- package/services/permissions/sections-builder.js +48 -0
- package/services/permissions.js +11 -0
- package/tests/content-manager/list-relation.test.e2e.js +122 -0
- package/tests/graphql.test.e2e.js +120 -0
- package/tests/locales.test.e2e.js +414 -0
- package/utils/index.js +20 -0
- package/validation/content-types.js +30 -0
- package/validation/locales.js +39 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getNonLocalizedAttributes } = require('../content-types');
|
|
4
|
+
const ctService = require('../../services/content-types');
|
|
5
|
+
|
|
6
|
+
describe('i18n - Controller - content-types', () => {
|
|
7
|
+
describe('getNonLocalizedAttributes', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
const getModel = () => {};
|
|
10
|
+
global.strapi = {
|
|
11
|
+
getModel,
|
|
12
|
+
plugins: { i18n: { services: { 'content-types': ctService } } },
|
|
13
|
+
admin: { services: { constants: { READ_ACTION: 'read', CREATE_ACTION: 'create' } } },
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('model not localized', async () => {
|
|
18
|
+
const badRequest = jest.fn();
|
|
19
|
+
const ctx = {
|
|
20
|
+
state: { user: {} },
|
|
21
|
+
request: {
|
|
22
|
+
body: {
|
|
23
|
+
model: 'application::country.country',
|
|
24
|
+
id: 1,
|
|
25
|
+
locale: 'fr',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
badRequest,
|
|
29
|
+
};
|
|
30
|
+
await getNonLocalizedAttributes(ctx);
|
|
31
|
+
|
|
32
|
+
expect(badRequest).toHaveBeenCalledWith('model.not.localized');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('entity not found', async () => {
|
|
36
|
+
const notFound = jest.fn();
|
|
37
|
+
const findOne = jest.fn(() => Promise.resolve(undefined));
|
|
38
|
+
const getModel = jest.fn(() => ({ pluginOptions: { i18n: { localized: true } } }));
|
|
39
|
+
|
|
40
|
+
global.strapi.query = () => ({ findOne });
|
|
41
|
+
global.strapi.getModel = getModel;
|
|
42
|
+
const ctx = {
|
|
43
|
+
state: { user: {} },
|
|
44
|
+
request: {
|
|
45
|
+
body: {
|
|
46
|
+
model: 'application::country.country',
|
|
47
|
+
id: 1,
|
|
48
|
+
locale: 'fr',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
notFound,
|
|
52
|
+
};
|
|
53
|
+
await getNonLocalizedAttributes(ctx);
|
|
54
|
+
|
|
55
|
+
expect(notFound).toHaveBeenCalledWith();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('returns nonLocalizedFields', async () => {
|
|
59
|
+
const model = {
|
|
60
|
+
pluginOptions: { i18n: { localized: true } },
|
|
61
|
+
attributes: {
|
|
62
|
+
name: { type: 'string' },
|
|
63
|
+
averagePrice: { type: 'integer' },
|
|
64
|
+
description: { type: 'string', pluginOptions: { i18n: { localized: true } } },
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const entity = {
|
|
68
|
+
id: 1,
|
|
69
|
+
name: "Papailhau's Pizza",
|
|
70
|
+
description: 'Best pizza restaurant of the town',
|
|
71
|
+
locale: 'en',
|
|
72
|
+
published_at: '2021-03-30T09:34:54.042Z',
|
|
73
|
+
localizations: [{ id: 2, locale: 'it', published_at: null }],
|
|
74
|
+
};
|
|
75
|
+
const permissions = [
|
|
76
|
+
{ properties: { fields: ['name', 'averagePrice'], locales: ['it'] } },
|
|
77
|
+
{ properties: { fields: ['name', 'description'], locales: ['fr'] } },
|
|
78
|
+
{ properties: { fields: ['name'], locales: ['fr'] } },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const findOne = jest.fn(() => Promise.resolve(entity));
|
|
82
|
+
const find = jest.fn(() => Promise.resolve(permissions));
|
|
83
|
+
const getModel = jest.fn(() => model);
|
|
84
|
+
|
|
85
|
+
global.strapi.query = () => ({ findOne });
|
|
86
|
+
global.strapi.getModel = getModel;
|
|
87
|
+
global.strapi.admin.services.permission = { find };
|
|
88
|
+
const ctx = {
|
|
89
|
+
state: { user: { roles: [{ id: 1 }, { id: 2 }] } },
|
|
90
|
+
request: {
|
|
91
|
+
body: {
|
|
92
|
+
model: 'application::country.country',
|
|
93
|
+
id: 1,
|
|
94
|
+
locale: 'fr',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
await getNonLocalizedAttributes(ctx);
|
|
99
|
+
expect(find).toHaveBeenCalledWith({
|
|
100
|
+
action_in: ['read', 'create'],
|
|
101
|
+
subject: 'application::country.country',
|
|
102
|
+
role_in: [1, 2],
|
|
103
|
+
});
|
|
104
|
+
expect(ctx.body).toEqual({
|
|
105
|
+
nonLocalizedFields: { name: "Papailhau's Pizza" },
|
|
106
|
+
localizations: [
|
|
107
|
+
{ id: 2, locale: 'it', published_at: null },
|
|
108
|
+
{ id: 1, locale: 'en', published_at: '2021-03-30T09:34:54.042Z' },
|
|
109
|
+
],
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { listIsoLocales } = require('../iso-locales');
|
|
4
|
+
|
|
5
|
+
describe('ISO locales', () => {
|
|
6
|
+
test('listIsoLocales', () => {
|
|
7
|
+
const isoLocales = [{ code: 'af', name: 'Afrikaans (af)' }];
|
|
8
|
+
const getIsoLocales = jest.fn(() => isoLocales);
|
|
9
|
+
global.strapi = {
|
|
10
|
+
plugins: {
|
|
11
|
+
i18n: {
|
|
12
|
+
services: {
|
|
13
|
+
'iso-locales': {
|
|
14
|
+
getIsoLocales,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ctx = {};
|
|
22
|
+
listIsoLocales(ctx);
|
|
23
|
+
|
|
24
|
+
expect(ctx.body).toMatchObject(isoLocales);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { listLocales, createLocale, updateLocale, deleteLocale } = require('../locales');
|
|
4
|
+
const localeModel = require('../../models/Locale.settings');
|
|
5
|
+
|
|
6
|
+
describe('Locales', () => {
|
|
7
|
+
describe('listLocales', () => {
|
|
8
|
+
test('can get locales', async () => {
|
|
9
|
+
const locales = [{ code: 'af', name: 'Afrikaans (af)' }];
|
|
10
|
+
const expectedLocales = [{ code: 'af', name: 'Afrikaans (af)', isDefault: true }];
|
|
11
|
+
const setIsDefault = jest.fn(() => expectedLocales);
|
|
12
|
+
const find = jest.fn(() => locales);
|
|
13
|
+
const getModel = jest.fn(() => localeModel);
|
|
14
|
+
global.strapi = {
|
|
15
|
+
getModel,
|
|
16
|
+
plugins: {
|
|
17
|
+
i18n: {
|
|
18
|
+
services: {
|
|
19
|
+
locales: {
|
|
20
|
+
find,
|
|
21
|
+
setIsDefault,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ctx = {};
|
|
29
|
+
await listLocales(ctx);
|
|
30
|
+
|
|
31
|
+
expect(setIsDefault).toHaveBeenCalledWith(locales);
|
|
32
|
+
expect(find).toHaveBeenCalledWith();
|
|
33
|
+
expect(ctx.body).toMatchObject(expectedLocales);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('createLocale', () => {
|
|
38
|
+
test('can create a locale (isDefault: true)', async () => {
|
|
39
|
+
const locale = { code: 'af', name: 'Afrikaans (af)' };
|
|
40
|
+
const expectedLocales = { code: 'af', name: 'Afrikaans (af)', isDefault: true };
|
|
41
|
+
const getDefaultLocale = jest.fn(() => Promise.resolve('af'));
|
|
42
|
+
const setDefaultLocale = jest.fn(() => Promise.resolve());
|
|
43
|
+
|
|
44
|
+
const setIsDefault = jest.fn(() => expectedLocales);
|
|
45
|
+
const findByCode = jest.fn(() => undefined);
|
|
46
|
+
const create = jest.fn(() => Promise.resolve(locale));
|
|
47
|
+
const getModel = jest.fn(() => localeModel);
|
|
48
|
+
global.strapi = {
|
|
49
|
+
getModel,
|
|
50
|
+
plugins: {
|
|
51
|
+
i18n: {
|
|
52
|
+
services: {
|
|
53
|
+
locales: {
|
|
54
|
+
findByCode,
|
|
55
|
+
setIsDefault,
|
|
56
|
+
getDefaultLocale,
|
|
57
|
+
setDefaultLocale,
|
|
58
|
+
create,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const ctx = { request: { body: { ...locale, isDefault: true } }, state: { user: { id: 1 } } };
|
|
66
|
+
await createLocale(ctx);
|
|
67
|
+
|
|
68
|
+
expect(setIsDefault).toHaveBeenCalledWith(locale);
|
|
69
|
+
expect(setDefaultLocale).toHaveBeenCalledWith(locale);
|
|
70
|
+
expect(findByCode).toHaveBeenCalledWith('af');
|
|
71
|
+
expect(create).toHaveBeenCalledWith({ created_by: 1, updated_by: 1, ...locale });
|
|
72
|
+
expect(ctx.body).toMatchObject(expectedLocales);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('can create a locale (isDefault: false)', async () => {
|
|
76
|
+
const locale = { code: 'af', name: 'Afrikaans (af)' };
|
|
77
|
+
const expectedLocale = { code: 'af', name: 'Afrikaans (af)', isDefault: false };
|
|
78
|
+
const getDefaultLocale = jest.fn(() => Promise.resolve('en'));
|
|
79
|
+
|
|
80
|
+
const setIsDefault = jest.fn(() => expectedLocale);
|
|
81
|
+
const findByCode = jest.fn(() => undefined);
|
|
82
|
+
const create = jest.fn(() => Promise.resolve(locale));
|
|
83
|
+
const getModel = jest.fn(() => localeModel);
|
|
84
|
+
global.strapi = {
|
|
85
|
+
getModel,
|
|
86
|
+
plugins: {
|
|
87
|
+
i18n: {
|
|
88
|
+
services: {
|
|
89
|
+
locales: {
|
|
90
|
+
findByCode,
|
|
91
|
+
setIsDefault,
|
|
92
|
+
getDefaultLocale,
|
|
93
|
+
create,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const ctx = {
|
|
101
|
+
request: { body: { ...locale, isDefault: false } },
|
|
102
|
+
state: { user: { id: 1 } },
|
|
103
|
+
};
|
|
104
|
+
await createLocale(ctx);
|
|
105
|
+
|
|
106
|
+
expect(setIsDefault).toHaveBeenCalledWith(locale);
|
|
107
|
+
expect(findByCode).toHaveBeenCalledWith('af');
|
|
108
|
+
expect(create).toHaveBeenCalledWith({ created_by: 1, updated_by: 1, ...locale });
|
|
109
|
+
expect(ctx.body).toMatchObject(expectedLocale);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('cannot create a locale that already exists', async () => {
|
|
113
|
+
const locale = { code: 'af', name: 'Afrikaans (af)' };
|
|
114
|
+
const expectedLocale = { code: 'af', name: 'Afrikaans (af)', isDefault: false };
|
|
115
|
+
const getDefaultLocale = jest.fn(() => Promise.resolve('en'));
|
|
116
|
+
|
|
117
|
+
const setIsDefault = jest.fn(() => expectedLocale);
|
|
118
|
+
const findByCode = jest.fn(() => ({ name: 'other locale', code: 'af' }));
|
|
119
|
+
const create = jest.fn(() => Promise.resolve(locale));
|
|
120
|
+
const badRequest = jest.fn();
|
|
121
|
+
const getModel = jest.fn(() => localeModel);
|
|
122
|
+
global.strapi = {
|
|
123
|
+
getModel,
|
|
124
|
+
plugins: {
|
|
125
|
+
i18n: {
|
|
126
|
+
services: {
|
|
127
|
+
locales: {
|
|
128
|
+
findByCode,
|
|
129
|
+
setIsDefault,
|
|
130
|
+
getDefaultLocale,
|
|
131
|
+
create,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const ctx = {
|
|
139
|
+
badRequest,
|
|
140
|
+
request: { body: { ...locale, isDefault: false } },
|
|
141
|
+
state: { user: { id: 1 } },
|
|
142
|
+
};
|
|
143
|
+
await createLocale(ctx);
|
|
144
|
+
|
|
145
|
+
expect(findByCode).toHaveBeenCalledWith('af');
|
|
146
|
+
expect(badRequest).toHaveBeenCalledWith('This locale already exists');
|
|
147
|
+
expect(create).not.toHaveBeenCalled();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('updateLocale', () => {
|
|
152
|
+
test('can update a locale', async () => {
|
|
153
|
+
const updatedLocale = { name: 'Afrikaans', code: 'af' };
|
|
154
|
+
const existingLocale = { name: 'Afrikaans (af)', code: 'af' };
|
|
155
|
+
const updates = { name: 'Afrikaans' };
|
|
156
|
+
const expectedLocales = { code: 'af', name: 'Afrikaans', isDefault: true };
|
|
157
|
+
const setDefaultLocale = jest.fn(() => Promise.resolve());
|
|
158
|
+
|
|
159
|
+
const setIsDefault = jest.fn(() => expectedLocales);
|
|
160
|
+
const findById = jest.fn(() => existingLocale);
|
|
161
|
+
const update = jest.fn(() => Promise.resolve(updatedLocale));
|
|
162
|
+
const getModel = jest.fn(() => localeModel);
|
|
163
|
+
global.strapi = {
|
|
164
|
+
getModel,
|
|
165
|
+
plugins: {
|
|
166
|
+
i18n: {
|
|
167
|
+
services: {
|
|
168
|
+
locales: {
|
|
169
|
+
findById,
|
|
170
|
+
setIsDefault,
|
|
171
|
+
setDefaultLocale,
|
|
172
|
+
update,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const ctx = {
|
|
180
|
+
params: { id: 1 },
|
|
181
|
+
request: { body: { ...updates, isDefault: true } },
|
|
182
|
+
state: { user: { id: 1 } },
|
|
183
|
+
};
|
|
184
|
+
await updateLocale(ctx);
|
|
185
|
+
|
|
186
|
+
expect(setIsDefault).toHaveBeenCalledWith(updatedLocale);
|
|
187
|
+
expect(setDefaultLocale).toHaveBeenCalledWith(updatedLocale);
|
|
188
|
+
expect(findById).toHaveBeenCalledWith(1);
|
|
189
|
+
expect(update).toHaveBeenCalledWith({ id: 1 }, { updated_by: 1, ...updates });
|
|
190
|
+
expect(ctx.body).toMatchObject(expectedLocales);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('cannot update the code of a locale', async () => {
|
|
194
|
+
const updatedLocale = { name: 'Afrikaans', code: 'af' };
|
|
195
|
+
const existingLocale = { name: 'Afrikaans (af)', code: 'af' };
|
|
196
|
+
const updates = { name: 'Afrikaans', code: 'fr' };
|
|
197
|
+
const expectedLocales = { code: 'af', name: 'Afrikaans', isDefault: true };
|
|
198
|
+
const setDefaultLocale = jest.fn(() => Promise.resolve());
|
|
199
|
+
|
|
200
|
+
const setIsDefault = jest.fn(() => expectedLocales);
|
|
201
|
+
const findById = jest.fn(() => existingLocale);
|
|
202
|
+
const update = jest.fn(() => Promise.resolve(updatedLocale));
|
|
203
|
+
const getModel = jest.fn(() => localeModel);
|
|
204
|
+
const badRequest = jest.fn();
|
|
205
|
+
global.strapi = {
|
|
206
|
+
getModel,
|
|
207
|
+
plugins: {
|
|
208
|
+
i18n: {
|
|
209
|
+
services: {
|
|
210
|
+
locales: {
|
|
211
|
+
findById,
|
|
212
|
+
setIsDefault,
|
|
213
|
+
setDefaultLocale,
|
|
214
|
+
update,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const ctx = {
|
|
222
|
+
badRequest,
|
|
223
|
+
params: { id: 1 },
|
|
224
|
+
request: { body: { ...updates, isDefault: true } },
|
|
225
|
+
state: { user: { id: 1 } },
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
await updateLocale(ctx);
|
|
229
|
+
|
|
230
|
+
expect(badRequest).toHaveBeenCalled();
|
|
231
|
+
expect(findById).not.toHaveBeenCalled();
|
|
232
|
+
expect(update).not.toHaveBeenCalled();
|
|
233
|
+
expect(setIsDefault).not.toHaveBeenCalled();
|
|
234
|
+
expect(setDefaultLocale).not.toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('deleteLocale', () => {
|
|
239
|
+
test('can delete a locale', async () => {
|
|
240
|
+
const locale = { code: 'af', name: 'Afrikaans (af)' };
|
|
241
|
+
const expectedLocales = { code: 'af', name: 'Afrikaans (af)', isDefault: false };
|
|
242
|
+
const getDefaultLocale = jest.fn(() => Promise.resolve('en'));
|
|
243
|
+
|
|
244
|
+
const setIsDefault = jest.fn(() => expectedLocales);
|
|
245
|
+
const findById = jest.fn(() => locale);
|
|
246
|
+
const deleteFn = jest.fn();
|
|
247
|
+
const getModel = jest.fn(() => localeModel);
|
|
248
|
+
global.strapi = {
|
|
249
|
+
getModel,
|
|
250
|
+
plugins: {
|
|
251
|
+
i18n: {
|
|
252
|
+
services: {
|
|
253
|
+
locales: {
|
|
254
|
+
findById,
|
|
255
|
+
setIsDefault,
|
|
256
|
+
getDefaultLocale,
|
|
257
|
+
delete: deleteFn,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const ctx = { params: { id: 1 } };
|
|
265
|
+
await deleteLocale(ctx);
|
|
266
|
+
|
|
267
|
+
expect(setIsDefault).toHaveBeenCalledWith(locale);
|
|
268
|
+
expect(findById).toHaveBeenCalledWith(1);
|
|
269
|
+
expect(deleteFn).toHaveBeenCalledWith({ id: 1 });
|
|
270
|
+
expect(ctx.body).toMatchObject(expectedLocales);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test('cannot delete the default locale', async () => {
|
|
274
|
+
const locale = { code: 'af', name: 'Afrikaans (af)' };
|
|
275
|
+
const expectedLocales = { code: 'af', name: 'Afrikaans (af)', isDefault: false };
|
|
276
|
+
const getDefaultLocale = jest.fn(() => Promise.resolve('af'));
|
|
277
|
+
|
|
278
|
+
const setIsDefault = jest.fn(() => Promise.resolve(expectedLocales));
|
|
279
|
+
const findById = jest.fn(() => Promise.resolve(locale));
|
|
280
|
+
const badRequest = jest.fn();
|
|
281
|
+
const deleteFn = jest.fn();
|
|
282
|
+
const getModel = jest.fn(() => localeModel);
|
|
283
|
+
global.strapi = {
|
|
284
|
+
getModel,
|
|
285
|
+
plugins: {
|
|
286
|
+
i18n: {
|
|
287
|
+
services: {
|
|
288
|
+
locales: {
|
|
289
|
+
findById,
|
|
290
|
+
getDefaultLocale,
|
|
291
|
+
delete: deleteFn,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const ctx = { params: { id: 1 }, badRequest };
|
|
299
|
+
await deleteLocale(ctx);
|
|
300
|
+
|
|
301
|
+
expect(badRequest).toHaveBeenCalledWith('Cannot delete the default locale');
|
|
302
|
+
|
|
303
|
+
expect(findById).toHaveBeenCalledWith(1);
|
|
304
|
+
expect(setIsDefault).not.toHaveBeenCalled();
|
|
305
|
+
expect(deleteFn).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { pick, uniq, prop, getOr, flatten, pipe, map } = require('lodash/fp');
|
|
4
|
+
const { contentTypes: contentTypesUtils } = require('@akemona-org/strapi-utils');
|
|
5
|
+
const { getService } = require('../utils');
|
|
6
|
+
const { validateGetNonLocalizedAttributesInput } = require('../validation/content-types');
|
|
7
|
+
|
|
8
|
+
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
|
9
|
+
|
|
10
|
+
const getLocalesProperty = getOr([], 'properties.locales');
|
|
11
|
+
const getFieldsProperty = prop('properties.fields');
|
|
12
|
+
|
|
13
|
+
const getFirstLevelPath = map((path) => path.split('.')[0]);
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
async getNonLocalizedAttributes(ctx) {
|
|
17
|
+
const { user } = ctx.state;
|
|
18
|
+
const { model, id, locale } = ctx.request.body;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await validateGetNonLocalizedAttributesInput({ model, id, locale });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return ctx.badRequest('ValidationError', err);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const modelDef = strapi.getModel(model);
|
|
27
|
+
const { copyNonLocalizedAttributes, isLocalizedContentType } = getService('content-types');
|
|
28
|
+
const { READ_ACTION, CREATE_ACTION } = strapi.admin.services.constants;
|
|
29
|
+
|
|
30
|
+
if (!isLocalizedContentType(modelDef)) {
|
|
31
|
+
return ctx.badRequest('model.not.localized');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let params = modelDef.kind === 'singleType' ? {} : { id };
|
|
35
|
+
|
|
36
|
+
const entity = await strapi.query(model).findOne(params);
|
|
37
|
+
|
|
38
|
+
if (!entity) {
|
|
39
|
+
return ctx.notFound();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const permissions = await strapi.admin.services.permission.find({
|
|
43
|
+
action_in: [READ_ACTION, CREATE_ACTION],
|
|
44
|
+
subject: model,
|
|
45
|
+
role_in: user.roles.map(prop('id')),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const localePermissions = permissions
|
|
49
|
+
.filter((perm) => getLocalesProperty(perm).includes(locale))
|
|
50
|
+
.map(getFieldsProperty);
|
|
51
|
+
|
|
52
|
+
const permittedFields = pipe(flatten, getFirstLevelPath, uniq)(localePermissions);
|
|
53
|
+
|
|
54
|
+
const nonLocalizedFields = copyNonLocalizedAttributes(modelDef, entity);
|
|
55
|
+
const sanitizedNonLocalizedFields = pick(permittedFields, nonLocalizedFields);
|
|
56
|
+
|
|
57
|
+
ctx.body = {
|
|
58
|
+
nonLocalizedFields: sanitizedNonLocalizedFields,
|
|
59
|
+
localizations: entity.localizations.concat(
|
|
60
|
+
pick(['id', 'locale', PUBLISHED_AT_ATTRIBUTE], entity)
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { setCreatorFields, sanitizeEntity } = require('@akemona-org/strapi-utils');
|
|
4
|
+
const { pick } = require('lodash/fp');
|
|
5
|
+
const { getService } = require('../utils');
|
|
6
|
+
const { validateCreateLocaleInput, validateUpdateLocaleInput } = require('../validation/locales');
|
|
7
|
+
const { formatLocale } = require('../domain/locale');
|
|
8
|
+
|
|
9
|
+
const sanitizeLocale = (locale) => {
|
|
10
|
+
const model = strapi.getModel('locale', 'i18n');
|
|
11
|
+
|
|
12
|
+
return sanitizeEntity(locale, { model });
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
async listLocales(ctx) {
|
|
17
|
+
const localesService = getService('locales');
|
|
18
|
+
|
|
19
|
+
const locales = await localesService.find();
|
|
20
|
+
|
|
21
|
+
ctx.body = await localesService.setIsDefault(sanitizeLocale(locales));
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async createLocale(ctx) {
|
|
25
|
+
const { user } = ctx.state;
|
|
26
|
+
const { body } = ctx.request;
|
|
27
|
+
let { isDefault, ...localeToCreate } = body;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
await validateCreateLocaleInput(body);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
return ctx.badRequest('ValidationError', err);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const localesService = getService('locales');
|
|
36
|
+
|
|
37
|
+
const existingLocale = await localesService.findByCode(body.code);
|
|
38
|
+
if (existingLocale) {
|
|
39
|
+
return ctx.badRequest('This locale already exists');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
localeToCreate = formatLocale(localeToCreate);
|
|
43
|
+
localeToCreate = setCreatorFields({ user })(localeToCreate);
|
|
44
|
+
|
|
45
|
+
const locale = await localesService.create(localeToCreate);
|
|
46
|
+
|
|
47
|
+
if (isDefault) {
|
|
48
|
+
await localesService.setDefaultLocale(locale);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
ctx.body = await localesService.setIsDefault(sanitizeLocale(locale));
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async updateLocale(ctx) {
|
|
55
|
+
const { user } = ctx.state;
|
|
56
|
+
const { id } = ctx.params;
|
|
57
|
+
const { body } = ctx.request;
|
|
58
|
+
let { isDefault, ...updates } = body;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await validateUpdateLocaleInput(body);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return ctx.badRequest('ValidationError', err);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const localesService = getService('locales');
|
|
67
|
+
|
|
68
|
+
const existingLocale = await localesService.findById(id);
|
|
69
|
+
if (!existingLocale) {
|
|
70
|
+
return ctx.notFound('locale.notFound');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const allowedParams = ['name'];
|
|
74
|
+
const cleanUpdates = setCreatorFields({ user, isEdition: true })(pick(allowedParams, updates));
|
|
75
|
+
|
|
76
|
+
const updatedLocale = await localesService.update({ id }, cleanUpdates);
|
|
77
|
+
|
|
78
|
+
if (isDefault) {
|
|
79
|
+
await localesService.setDefaultLocale(updatedLocale);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ctx.body = await localesService.setIsDefault(sanitizeLocale(updatedLocale));
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
async deleteLocale(ctx) {
|
|
86
|
+
const { id } = ctx.params;
|
|
87
|
+
|
|
88
|
+
const localesService = getService('locales');
|
|
89
|
+
|
|
90
|
+
const existingLocale = await localesService.findById(id);
|
|
91
|
+
if (!existingLocale) {
|
|
92
|
+
return ctx.notFound('locale.notFound');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const defaultLocaleCode = await localesService.getDefaultLocale();
|
|
96
|
+
if (existingLocale.code === defaultLocaleCode) {
|
|
97
|
+
return ctx.badRequest('Cannot delete the default locale');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await localesService.delete({ id });
|
|
101
|
+
|
|
102
|
+
ctx.body = await localesService.setIsDefault(sanitizeLocale(existingLocale));
|
|
103
|
+
},
|
|
104
|
+
};
|
package/domain/locale.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getOr, get, isMatch } = require('lodash/fp');
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
|
|
6
|
+
module.exports = strapi => {
|
|
7
|
+
return {
|
|
8
|
+
beforeInitialize() {
|
|
9
|
+
strapi.config.middleware.load.before.unshift('i18n');
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
initialize() {
|
|
13
|
+
const routes = get('plugins.content-manager.config.routes', strapi);
|
|
14
|
+
const routesToAddPolicyTo = routes.filter(
|
|
15
|
+
route =>
|
|
16
|
+
isMatch({ method: 'POST', path: '/collection-types/:model' }, route) ||
|
|
17
|
+
isMatch({ method: 'PUT', path: '/single-types/:model' }, route)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
routesToAddPolicyTo.forEach(route => {
|
|
21
|
+
const policies = getOr([], 'config.policies', route).concat(
|
|
22
|
+
'plugins::i18n.validateLocaleCreation'
|
|
23
|
+
);
|
|
24
|
+
_.set(route, 'config.policies', policies);
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
};
|