@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,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { has, omit, isArray } = require('lodash/fp');
|
|
4
|
+
const { getService } = require('../utils');
|
|
5
|
+
|
|
6
|
+
const { syncLocalizations, syncNonLocalizedAttributes } = require('./localizations');
|
|
7
|
+
|
|
8
|
+
const LOCALE_QUERY_FILTER = '_locale';
|
|
9
|
+
const SINGLE_ENTRY_ACTIONS = ['findOne', 'update', 'delete'];
|
|
10
|
+
const BULK_ACTIONS = ['delete'];
|
|
11
|
+
|
|
12
|
+
const paramsContain = (key, params) => {
|
|
13
|
+
return (
|
|
14
|
+
has(key, params) ||
|
|
15
|
+
has(key, params._where) ||
|
|
16
|
+
(isArray(params._where) && params._where.some(clause => has(key, clause)))
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Adds default locale or replaces _locale by locale in query params
|
|
22
|
+
* @param {object} params - query params
|
|
23
|
+
*/
|
|
24
|
+
const wrapParams = async (params = {}, ctx = {}) => {
|
|
25
|
+
const { action } = ctx;
|
|
26
|
+
|
|
27
|
+
if (has(LOCALE_QUERY_FILTER, params)) {
|
|
28
|
+
if (params[LOCALE_QUERY_FILTER] === 'all') {
|
|
29
|
+
return omit(LOCALE_QUERY_FILTER, params);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
...omit(LOCALE_QUERY_FILTER, params),
|
|
34
|
+
locale: params[LOCALE_QUERY_FILTER],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const entityDefinedById = paramsContain('id', params) && SINGLE_ENTRY_ACTIONS.includes(action);
|
|
39
|
+
const entitiesDefinedByIds = paramsContain('id_in', params) && BULK_ACTIONS.includes(action);
|
|
40
|
+
|
|
41
|
+
if (entityDefinedById || entitiesDefinedByIds) {
|
|
42
|
+
return params;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { getDefaultLocale } = getService('locales');
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
...params,
|
|
49
|
+
locale: await getDefaultLocale(),
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Assigns a valid locale or the default one if not define
|
|
55
|
+
* @param {object} data
|
|
56
|
+
*/
|
|
57
|
+
const assignValidLocale = async data => {
|
|
58
|
+
const { getValidLocale } = getService('content-types');
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
data.locale = await getValidLocale(data.locale);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
throw strapi.errors.badRequest("This locale doesn't exist");
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decorates the entity service with I18N business logic
|
|
69
|
+
* @param {object} service - entity service
|
|
70
|
+
*/
|
|
71
|
+
const decorator = service => ({
|
|
72
|
+
/**
|
|
73
|
+
* Wraps query options. In particular will add default locale to query params
|
|
74
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
75
|
+
* @param {object} ctx - Query context
|
|
76
|
+
* @param {object} ctx.model - Model that is being used
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
async wrapOptions(opts = {}, ctx = {}) {
|
|
80
|
+
const wrappedOptions = await service.wrapOptions.call(this, opts, ctx);
|
|
81
|
+
|
|
82
|
+
const model = strapi.db.getModel(ctx.model);
|
|
83
|
+
|
|
84
|
+
const { isLocalizedContentType } = getService('content-types');
|
|
85
|
+
|
|
86
|
+
if (!isLocalizedContentType(model)) {
|
|
87
|
+
return wrappedOptions;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
...wrappedOptions,
|
|
92
|
+
params: await wrapParams(wrappedOptions.params, ctx),
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates an entry & make links between it and its related localizaionts
|
|
98
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
99
|
+
* @param {object} ctx - Query context
|
|
100
|
+
* @param {object} ctx.model - Model that is being used
|
|
101
|
+
*/
|
|
102
|
+
async create(opts, ctx) {
|
|
103
|
+
const model = strapi.db.getModel(ctx.model);
|
|
104
|
+
|
|
105
|
+
const { isLocalizedContentType } = getService('content-types');
|
|
106
|
+
|
|
107
|
+
if (!isLocalizedContentType(model)) {
|
|
108
|
+
return service.create.call(this, opts, ctx);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { data } = opts;
|
|
112
|
+
await assignValidLocale(data);
|
|
113
|
+
|
|
114
|
+
const entry = await service.create.call(this, opts, ctx);
|
|
115
|
+
|
|
116
|
+
await syncLocalizations(entry, { model });
|
|
117
|
+
await syncNonLocalizedAttributes(entry, { model });
|
|
118
|
+
return entry;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Updates an entry & update related localizations fields
|
|
123
|
+
* @param {object} opts - Query options object (params, data, files, populate)
|
|
124
|
+
* @param {object} ctx - Query context
|
|
125
|
+
* @param {object} ctx.model - Model that is being used
|
|
126
|
+
*/
|
|
127
|
+
async update(opts, ctx) {
|
|
128
|
+
const model = strapi.db.getModel(ctx.model);
|
|
129
|
+
|
|
130
|
+
const { isLocalizedContentType } = getService('content-types');
|
|
131
|
+
|
|
132
|
+
if (!isLocalizedContentType(model)) {
|
|
133
|
+
return service.update.call(this, opts, ctx);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const { data, ...restOptions } = opts;
|
|
137
|
+
|
|
138
|
+
const entry = await service.update.call(
|
|
139
|
+
this,
|
|
140
|
+
{
|
|
141
|
+
data: omit(['locale', 'localizations'], data),
|
|
142
|
+
...restOptions,
|
|
143
|
+
},
|
|
144
|
+
ctx
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
await syncNonLocalizedAttributes(entry, { model });
|
|
148
|
+
return entry;
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
decorator,
|
|
154
|
+
wrapParams,
|
|
155
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isNil } = require('lodash/fp');
|
|
4
|
+
const { DEFAULT_LOCALE } = require('../constants');
|
|
5
|
+
const { getService } = require('../utils');
|
|
6
|
+
|
|
7
|
+
const { getCoreStore } = require('../utils');
|
|
8
|
+
|
|
9
|
+
const find = (...args) => strapi.query('locale', 'i18n').find(...args);
|
|
10
|
+
|
|
11
|
+
const findById = id => strapi.query('locale', 'i18n').findOne({ id });
|
|
12
|
+
|
|
13
|
+
const findByCode = code => strapi.query('locale', 'i18n').findOne({ code });
|
|
14
|
+
|
|
15
|
+
const count = params => strapi.query('locale', 'i18n').count(params);
|
|
16
|
+
|
|
17
|
+
const create = async locale => {
|
|
18
|
+
const result = await strapi.query('locale', 'i18n').create(locale);
|
|
19
|
+
|
|
20
|
+
getService('metrics').sendDidUpdateI18nLocalesEvent();
|
|
21
|
+
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const update = async (params, updates) => {
|
|
26
|
+
const result = await strapi.query('locale', 'i18n').update(params, updates);
|
|
27
|
+
|
|
28
|
+
getService('metrics').sendDidUpdateI18nLocalesEvent();
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const deleteFn = async ({ id }) => {
|
|
34
|
+
const localeToDelete = await strapi.query('locale', 'i18n').findOne({ id });
|
|
35
|
+
|
|
36
|
+
if (localeToDelete) {
|
|
37
|
+
await deleteAllLocalizedEntriesFor({ locale: localeToDelete.code });
|
|
38
|
+
const result = await strapi.query('locale', 'i18n').delete({ id });
|
|
39
|
+
|
|
40
|
+
getService('metrics').sendDidUpdateI18nLocalesEvent();
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return localeToDelete;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const setDefaultLocale = ({ code }) => getCoreStore().set({ key: 'default_locale', value: code });
|
|
49
|
+
|
|
50
|
+
const getDefaultLocale = () => getCoreStore().get({ key: 'default_locale' });
|
|
51
|
+
|
|
52
|
+
const setIsDefault = async locales => {
|
|
53
|
+
if (isNil(locales)) {
|
|
54
|
+
return locales;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const actualDefault = await getDefaultLocale();
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(locales)) {
|
|
60
|
+
return locales.map(locale => ({ ...locale, isDefault: actualDefault === locale.code }));
|
|
61
|
+
} else {
|
|
62
|
+
// single locale
|
|
63
|
+
return { ...locales, isDefault: actualDefault === locales.code };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const initDefaultLocale = async () => {
|
|
68
|
+
const existingLocalesNb = await strapi.query('locale', 'i18n').count();
|
|
69
|
+
if (existingLocalesNb === 0) {
|
|
70
|
+
await create(DEFAULT_LOCALE);
|
|
71
|
+
await setDefaultLocale({ code: DEFAULT_LOCALE.code });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const deleteAllLocalizedEntriesFor = async ({ locale }) => {
|
|
76
|
+
const { isLocalizedContentType } = getService('content-types');
|
|
77
|
+
|
|
78
|
+
const localizedModels = Object.values(strapi.contentTypes).filter(isLocalizedContentType);
|
|
79
|
+
|
|
80
|
+
for (const model of localizedModels) {
|
|
81
|
+
await strapi.query(model.uid).delete({ locale }, { returning: false });
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
find,
|
|
87
|
+
findById,
|
|
88
|
+
findByCode,
|
|
89
|
+
create,
|
|
90
|
+
update,
|
|
91
|
+
count,
|
|
92
|
+
setDefaultLocale,
|
|
93
|
+
getDefaultLocale,
|
|
94
|
+
setIsDefault,
|
|
95
|
+
delete: deleteFn,
|
|
96
|
+
initDefaultLocale,
|
|
97
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { prop, isNil, isEmpty } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const { getService } = require('../utils');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adds the default locale to an object if it isn't defined yet
|
|
9
|
+
* @param {Object} data a data object before being persisted into db
|
|
10
|
+
*/
|
|
11
|
+
const assignDefaultLocale = async data => {
|
|
12
|
+
const { getDefaultLocale } = getService('locales');
|
|
13
|
+
|
|
14
|
+
if (isNil(data.locale)) {
|
|
15
|
+
data.locale = await getDefaultLocale();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Syncronize related localizations from a root one
|
|
21
|
+
* @param {Object} entry entry to update
|
|
22
|
+
* @param {Object} options
|
|
23
|
+
* @param {Object} options.model corresponding model
|
|
24
|
+
*/
|
|
25
|
+
const syncLocalizations = async (entry, { model }) => {
|
|
26
|
+
if (Array.isArray(entry.localizations)) {
|
|
27
|
+
const newLocalizations = [entry.id, ...entry.localizations.map(prop('id'))];
|
|
28
|
+
|
|
29
|
+
const updateLocalization = id => {
|
|
30
|
+
const localizations = newLocalizations.filter(localizationId => localizationId !== id);
|
|
31
|
+
|
|
32
|
+
return strapi.query(model.uid).update({ id }, { localizations });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
await Promise.all(entry.localizations.map(({ id }) => updateLocalization(id)));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Update non localized fields of all the related localizations of an entry with the entry values
|
|
41
|
+
* @param {Object} entry entry to update
|
|
42
|
+
* @param {Object} options
|
|
43
|
+
* @param {Object} options.model corresponding model
|
|
44
|
+
*/
|
|
45
|
+
const syncNonLocalizedAttributes = async (entry, { model }) => {
|
|
46
|
+
const { copyNonLocalizedAttributes } = getService('content-types');
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(entry.localizations)) {
|
|
49
|
+
const nonLocalizedAttributes = copyNonLocalizedAttributes(model, entry);
|
|
50
|
+
|
|
51
|
+
if (isEmpty(nonLocalizedAttributes)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const updateLocalization = id => strapi.query(model.uid).update({ id }, nonLocalizedAttributes);
|
|
56
|
+
|
|
57
|
+
await Promise.all(entry.localizations.map(({ id }) => updateLocalization(id)));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
assignDefaultLocale,
|
|
63
|
+
syncLocalizations,
|
|
64
|
+
syncNonLocalizedAttributes,
|
|
65
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// const { reduce } = require('lodash/fp');
|
|
4
|
+
// const { getService } = require('../utils');
|
|
5
|
+
|
|
6
|
+
const sendDidInitializeEvent = async () => {
|
|
7
|
+
// const { isLocalizedContentType } = getService('content-types');
|
|
8
|
+
// const numberOfContentTypes = reduce(
|
|
9
|
+
// (sum, contentType) => (isLocalizedContentType(contentType) ? sum + 1 : sum),
|
|
10
|
+
// 0
|
|
11
|
+
// )(strapi.contentTypes);
|
|
12
|
+
// await strapi.telemetry.send('didInitializeI18n', { numberOfContentTypes });
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const sendDidUpdateI18nLocalesEvent = async () => {
|
|
16
|
+
/* const numberOfLocales = await getService('locales').count();
|
|
17
|
+
|
|
18
|
+
await strapi.telemetry.send('didUpdateI18nLocales', { numberOfLocales }); */
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
sendDidInitializeEvent,
|
|
23
|
+
sendDidUpdateI18nLocalesEvent,
|
|
24
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { capitalize, isArray, getOr, prop } = require('lodash/fp');
|
|
4
|
+
const { getService } = require('../../utils');
|
|
5
|
+
|
|
6
|
+
const actions = ['create', 'read', 'update', 'delete'].map(uid => ({
|
|
7
|
+
section: 'settings',
|
|
8
|
+
category: 'Internationalization',
|
|
9
|
+
subCategory: 'Locales',
|
|
10
|
+
pluginName: 'i18n',
|
|
11
|
+
displayName: capitalize(uid),
|
|
12
|
+
uid: `locale.${uid}`,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const addLocalesPropertyIfNeeded = ({ value: action }) => {
|
|
16
|
+
const {
|
|
17
|
+
section,
|
|
18
|
+
options: { applyToProperties },
|
|
19
|
+
} = action;
|
|
20
|
+
|
|
21
|
+
// Only add the locales property to contentTypes' actions
|
|
22
|
+
if (section !== 'contentTypes') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If the 'locales' property is already declared within the applyToProperties array, then ignore the next steps
|
|
27
|
+
if (isArray(applyToProperties) && applyToProperties.includes('locales')) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Add the 'locales' property to the applyToProperties array (create it if necessary)
|
|
32
|
+
action.options.applyToProperties = isArray(applyToProperties)
|
|
33
|
+
? applyToProperties.concat('locales')
|
|
34
|
+
: ['locales'];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const shouldApplyLocalesPropertyToSubject = ({ property, subject }) => {
|
|
38
|
+
if (property === 'locales') {
|
|
39
|
+
const model = strapi.getModel(subject);
|
|
40
|
+
|
|
41
|
+
return getService('content-types').isLocalizedContentType(model);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const addAllLocalesToPermissions = async permissions => {
|
|
48
|
+
const { actionProvider } = strapi.admin.services.permission;
|
|
49
|
+
const { find: findAllLocales } = getService('locales');
|
|
50
|
+
|
|
51
|
+
const allLocales = await findAllLocales();
|
|
52
|
+
const allLocalesCode = allLocales.map(prop('code'));
|
|
53
|
+
|
|
54
|
+
return Promise.all(
|
|
55
|
+
permissions.map(async permission => {
|
|
56
|
+
const { action, subject } = permission;
|
|
57
|
+
|
|
58
|
+
const appliesToLocalesProperty = await actionProvider.appliesToProperty(
|
|
59
|
+
'locales',
|
|
60
|
+
action,
|
|
61
|
+
subject
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (!appliesToLocalesProperty) {
|
|
65
|
+
return permission;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const oldPermissionProperties = getOr({}, 'properties', permission);
|
|
69
|
+
|
|
70
|
+
return { ...permission, properties: { ...oldPermissionProperties, locales: allLocalesCode } };
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const syncSuperAdminPermissionsWithLocales = async () => {
|
|
76
|
+
const roleService = strapi.admin.services.role;
|
|
77
|
+
const permissionService = strapi.admin.services.permission;
|
|
78
|
+
|
|
79
|
+
const superAdminRole = await roleService.getSuperAdmin();
|
|
80
|
+
|
|
81
|
+
if (!superAdminRole) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const superAdminPermissions = await permissionService.findUserPermissions({
|
|
86
|
+
roles: [superAdminRole],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const newSuperAdminPermissions = await addAllLocalesToPermissions(superAdminPermissions);
|
|
90
|
+
|
|
91
|
+
await roleService.assignPermissions(superAdminRole.id, newSuperAdminPermissions);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const registerI18nActions = async () => {
|
|
95
|
+
const { actionProvider } = strapi.admin.services.permission;
|
|
96
|
+
|
|
97
|
+
await actionProvider.registerMany(actions);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const registerI18nActionsHooks = () => {
|
|
101
|
+
const { actionProvider } = strapi.admin.services.permission;
|
|
102
|
+
const { hooks } = strapi.admin.services.role;
|
|
103
|
+
|
|
104
|
+
actionProvider.hooks.appliesPropertyToSubject.register(shouldApplyLocalesPropertyToSubject);
|
|
105
|
+
hooks.willResetSuperAdminPermissions.register(addAllLocalesToPermissions);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const updateActionsProperties = () => {
|
|
109
|
+
const { actionProvider } = strapi.admin.services.permission;
|
|
110
|
+
|
|
111
|
+
// Register the transformation for every new action
|
|
112
|
+
actionProvider.hooks.willRegister.register(addLocalesPropertyIfNeeded);
|
|
113
|
+
|
|
114
|
+
// Handle already registered actions
|
|
115
|
+
actionProvider.values().forEach(action => addLocalesPropertyIfNeeded({ value: action }));
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
actions,
|
|
120
|
+
registerI18nActions,
|
|
121
|
+
registerI18nActionsHooks,
|
|
122
|
+
updateActionsProperties,
|
|
123
|
+
syncSuperAdminPermissionsWithLocales,
|
|
124
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getService } = require('../../utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} WillRegisterPermissionContext
|
|
7
|
+
* @property {Permission} permission
|
|
8
|
+
* @property {object} user
|
|
9
|
+
* @property {object} condition
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Locales property handler for the permission engine
|
|
14
|
+
* Add the has-locale-access condition if the locales property is defined
|
|
15
|
+
* @param {WillRegisterPermissionContext} context
|
|
16
|
+
*/
|
|
17
|
+
const willRegisterPermission = context => {
|
|
18
|
+
const { permission, condition, user } = context;
|
|
19
|
+
const { subject, properties } = permission;
|
|
20
|
+
|
|
21
|
+
const isSuperAdmin = strapi.admin.services.role.hasSuperAdminRole(user);
|
|
22
|
+
|
|
23
|
+
if (isSuperAdmin) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { locales } = properties || {};
|
|
28
|
+
const { isLocalizedContentType } = getService('content-types');
|
|
29
|
+
|
|
30
|
+
// If there is no subject defined, ignore the permission
|
|
31
|
+
if (!subject) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ct = strapi.contentTypes[subject];
|
|
36
|
+
|
|
37
|
+
// If the subject exists but isn't localized, ignore the permission
|
|
38
|
+
if (!isLocalizedContentType(ct)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If the subject is localized but the locales property is null (access to all locales), ignore the permission
|
|
43
|
+
if (locales === null) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
condition.and({
|
|
48
|
+
locale: {
|
|
49
|
+
$in: locales || [],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const registerI18nPermissionsHandlers = () => {
|
|
55
|
+
const { engine } = strapi.admin.services.permission;
|
|
56
|
+
|
|
57
|
+
engine.hooks.willRegisterPermission.register(willRegisterPermission);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
willRegisterPermission,
|
|
62
|
+
registerI18nPermissionsHandlers,
|
|
63
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isEmpty } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const { getService } = require('../../utils');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handler for the permissions layout (sections builder)
|
|
9
|
+
* Adds the locales property to the subjects
|
|
10
|
+
* @param {Action} action
|
|
11
|
+
* @param {ContentTypesSection} section
|
|
12
|
+
* @return {Promise<void>}
|
|
13
|
+
*/
|
|
14
|
+
const localesPropertyHandler = async ({ action, section }) => {
|
|
15
|
+
const { actionProvider } = strapi.admin.services.permission;
|
|
16
|
+
|
|
17
|
+
const locales = await getService('locales').find();
|
|
18
|
+
|
|
19
|
+
// Do not add the locales property if there is none registered
|
|
20
|
+
if (isEmpty(locales)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const subject of section.subjects) {
|
|
25
|
+
const applies = await actionProvider.appliesToProperty('locales', action.actionId, subject.uid);
|
|
26
|
+
const hasLocalesProperty = subject.properties.find(property => property.value === 'locales');
|
|
27
|
+
|
|
28
|
+
if (applies && !hasLocalesProperty) {
|
|
29
|
+
subject.properties.push({
|
|
30
|
+
label: 'Locales',
|
|
31
|
+
value: 'locales',
|
|
32
|
+
children: locales.map(({ name, code }) => ({ label: name || code, value: code })),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const registerLocalesPropertyHandler = () => {
|
|
39
|
+
const { sectionsBuilder } = strapi.admin.services.permission;
|
|
40
|
+
|
|
41
|
+
sectionsBuilder.addHandler('singleTypes', localesPropertyHandler);
|
|
42
|
+
sectionsBuilder.addHandler('collectionTypes', localesPropertyHandler);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
localesPropertyHandler,
|
|
47
|
+
registerLocalesPropertyHandler,
|
|
48
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const i18nActionsService = require('./permissions/actions');
|
|
4
|
+
const sectionsBuilderService = require('./permissions/sections-builder');
|
|
5
|
+
const engineService = require('./permissions/engine');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
actions: i18nActionsService,
|
|
9
|
+
sectionsBuilder: sectionsBuilderService,
|
|
10
|
+
engine: engineService,
|
|
11
|
+
};
|