@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.
Files changed (147) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +19 -0
  3. package/admin/src/assets/images/logo.svg +1 -0
  4. package/admin/src/components/CMEditViewCopyLocale/index.js +183 -0
  5. package/admin/src/components/CMEditViewCopyLocale/utils/cleanData.js +36 -0
  6. package/admin/src/components/CMEditViewCopyLocale/utils/generateOptions.js +22 -0
  7. package/admin/src/components/CMEditViewCopyLocale/utils/index.js +2 -0
  8. package/admin/src/components/CMEditViewCopyLocale/utils/removePasswordAndRelationsFieldFromData.js +54 -0
  9. package/admin/src/components/CMEditViewCopyLocale/utils/tests/cleanData.test.js +83 -0
  10. package/admin/src/components/CMEditViewCopyLocale/utils/tests/data.js +219 -0
  11. package/admin/src/components/CMEditViewCopyLocale/utils/tests/generateOptions.test.js +79 -0
  12. package/admin/src/components/CMEditViewCopyLocale/utils/tests/removePasswordAndRelationsFieldFromData.test.js +40 -0
  13. package/admin/src/components/CMEditViewInjectedComponents/index.js +58 -0
  14. package/admin/src/components/CMEditViewLocalePicker/Option.js +66 -0
  15. package/admin/src/components/CMEditViewLocalePicker/Wrapper.js +8 -0
  16. package/admin/src/components/CMEditViewLocalePicker/index.js +160 -0
  17. package/admin/src/components/CMEditViewLocalePicker/utils/addStatusColorToLocale.js +24 -0
  18. package/admin/src/components/CMEditViewLocalePicker/utils/createLocalesOption.js +20 -0
  19. package/admin/src/components/CMEditViewLocalePicker/utils/index.js +2 -0
  20. package/admin/src/components/CheckboxConfirmation/Wrapper.js +12 -0
  21. package/admin/src/components/CheckboxConfirmation/index.js +70 -0
  22. package/admin/src/components/DeleteModalAdditionalInfos/index.js +25 -0
  23. package/admin/src/components/LocaleList/index.js +101 -0
  24. package/admin/src/components/LocaleListCell/LocaleListCell.js +90 -0
  25. package/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js +128 -0
  26. package/admin/src/components/LocalePicker/index.js +126 -0
  27. package/admin/src/components/LocaleRow/index.js +77 -0
  28. package/admin/src/components/ModalCreate/AdvancedForm.js +45 -0
  29. package/admin/src/components/ModalCreate/BaseForm.js +103 -0
  30. package/admin/src/components/ModalCreate/index.js +136 -0
  31. package/admin/src/components/ModalDelete/index.js +49 -0
  32. package/admin/src/components/ModalEdit/AdvancedForm.js +51 -0
  33. package/admin/src/components/ModalEdit/BaseForm.js +91 -0
  34. package/admin/src/components/ModalEdit/index.js +122 -0
  35. package/admin/src/components/SettingsModal.js +66 -0
  36. package/admin/src/components/index.js +2 -0
  37. package/admin/src/containers/Initializer.js +31 -0
  38. package/admin/src/containers/SettingsPage/LocaleSettingsPage.js +69 -0
  39. package/admin/src/containers/SettingsPage/index.js +33 -0
  40. package/admin/src/containers/SettingsPage/tests/SettingsPage.test.js +744 -0
  41. package/admin/src/containers/SettingsPage/tests/__snapshots__/SettingsPage.test.js.snap +241 -0
  42. package/admin/src/hooks/constants.js +6 -0
  43. package/admin/src/hooks/reducers.js +63 -0
  44. package/admin/src/hooks/tests/reducers.test.js +203 -0
  45. package/admin/src/hooks/useAddLocale/index.js +60 -0
  46. package/admin/src/hooks/useContentTypePermissions/index.js +16 -0
  47. package/admin/src/hooks/useDefaultLocales/index.js +27 -0
  48. package/admin/src/hooks/useDeleteLocale/index.js +45 -0
  49. package/admin/src/hooks/useEditLocale/index.js +46 -0
  50. package/admin/src/hooks/useHasI18n/index.js +13 -0
  51. package/admin/src/hooks/useLocales/index.js +35 -0
  52. package/admin/src/index.js +169 -0
  53. package/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js +83 -0
  54. package/admin/src/middlewares/addLocaleColumnToListViewMiddleware.js +32 -0
  55. package/admin/src/middlewares/addLocaleToCollectionTypesMiddleware.js +25 -0
  56. package/admin/src/middlewares/addLocaleToSingleTypesMiddleware.js +25 -0
  57. package/admin/src/middlewares/extendCMEditViewLayoutMiddleware.js +159 -0
  58. package/admin/src/middlewares/extendCTBAttributeInitialDataMiddleware.js +58 -0
  59. package/admin/src/middlewares/extendCTBInitialDataMiddleware.js +33 -0
  60. package/admin/src/middlewares/index.js +21 -0
  61. package/admin/src/middlewares/localePermissionMiddleware.js +39 -0
  62. package/admin/src/middlewares/tests/addCommonFieldsToInitialDataMiddleware.test.js +97 -0
  63. package/admin/src/middlewares/tests/addLocaleColumnToListViewMiddleware.test.js +68 -0
  64. package/admin/src/middlewares/tests/addLocaleToCollectionTypesMiddleware.test.js +200 -0
  65. package/admin/src/middlewares/tests/addLocaleToSingleTypesMiddleware.test.js +193 -0
  66. package/admin/src/middlewares/tests/extendCMEditViewLayoutMiddleware.test.js +556 -0
  67. package/admin/src/middlewares/tests/extendCTBAttrributeInitialDataMiddleware.test.js +124 -0
  68. package/admin/src/middlewares/tests/extendCTBInitialDataMiddleware.test.js +92 -0
  69. package/admin/src/middlewares/tests/localePermissionMiddleware.test.js +150 -0
  70. package/admin/src/middlewares/utils/addLocaleToLinksSearch.js +56 -0
  71. package/admin/src/middlewares/utils/tests/addLocaleToLinksSearch.test.js +137 -0
  72. package/admin/src/permissions.js +9 -0
  73. package/admin/src/pluginId.js +5 -0
  74. package/admin/src/schemas.js +7 -0
  75. package/admin/src/selectors/selectCollectionTypesRelatedPermissions.js +4 -0
  76. package/admin/src/selectors/selectI18nLocales.js +3 -0
  77. package/admin/src/translations/en.json +60 -0
  78. package/admin/src/translations/fr.json +9 -0
  79. package/admin/src/translations/index.js +11 -0
  80. package/admin/src/translations/zh-Hans.json +60 -0
  81. package/admin/src/utils/getDefaultLocale.js +60 -0
  82. package/admin/src/utils/getInitialLocale.js +14 -0
  83. package/admin/src/utils/getLocaleFromQuery.js +7 -0
  84. package/admin/src/utils/getTrad.js +5 -0
  85. package/admin/src/utils/index.js +2 -0
  86. package/admin/src/utils/localizedFields.js +23 -0
  87. package/admin/src/utils/mutateCTBContentTypeSchema.js +66 -0
  88. package/admin/src/utils/tests/getDefaultLocale.test.js +337 -0
  89. package/admin/src/utils/tests/getInitialLocale.test.js +106 -0
  90. package/admin/src/utils/tests/mutateCTBContentTypeSchema.test.js +205 -0
  91. package/config/functions/bootstrap.js +57 -0
  92. package/config/functions/migrations/__tests__/content-type.test.js +255 -0
  93. package/config/functions/migrations/__tests__/field.test.js +150 -0
  94. package/config/functions/migrations/content-type/disable/index.js +34 -0
  95. package/config/functions/migrations/content-type/disable/migrate-for-bookshelf.js +58 -0
  96. package/config/functions/migrations/content-type/disable/migrate-for-mongoose.js +39 -0
  97. package/config/functions/migrations/content-type/enable/index.js +40 -0
  98. package/config/functions/migrations/content-type/utils/index.js +27 -0
  99. package/config/functions/migrations/field/__tests__/utils.test.js +53 -0
  100. package/config/functions/migrations/field/index.js +37 -0
  101. package/config/functions/migrations/field/migrate-for-bookshelf.js +72 -0
  102. package/config/functions/migrations/field/migrate-for-mongoose.js +24 -0
  103. package/config/functions/migrations/field/migrate.js +55 -0
  104. package/config/functions/migrations/field/utils.js +58 -0
  105. package/config/functions/register.js +46 -0
  106. package/config/policies/validateLocaleCreation.js +68 -0
  107. package/config/routes.json +64 -0
  108. package/constants/__tests__/index.test.js +27 -0
  109. package/constants/index.js +36 -0
  110. package/constants/iso-locales.json +2002 -0
  111. package/controllers/__tests__/content-types.test.js +113 -0
  112. package/controllers/__tests__/iso-locales.test.js +26 -0
  113. package/controllers/__tests__/locales.test.js +308 -0
  114. package/controllers/content-types.js +64 -0
  115. package/controllers/iso-locales.js +11 -0
  116. package/controllers/locales.js +104 -0
  117. package/domain/locale.js +10 -0
  118. package/middlewares/i18n/defaults.json +5 -0
  119. package/middlewares/i18n/index.js +28 -0
  120. package/models/Locale.settings.json +31 -0
  121. package/oas.yml +195 -0
  122. package/package.json +31 -0
  123. package/services/__tests__/__snapshots__/iso-locales.test.js.snap +2006 -0
  124. package/services/__tests__/content-types.test.js +545 -0
  125. package/services/__tests__/core-api.test.js +106 -0
  126. package/services/__tests__/entity-service-decorator.test.js +280 -0
  127. package/services/__tests__/iso-locales.test.js +11 -0
  128. package/services/__tests__/locales.test.js +237 -0
  129. package/services/__tests__/localizations.test.js +187 -0
  130. package/services/__tests__/metrics.test.js +90 -0
  131. package/services/content-types.js +200 -0
  132. package/services/core-api.js +296 -0
  133. package/services/entity-service-decorator.js +155 -0
  134. package/services/iso-locales.js +9 -0
  135. package/services/locales.js +97 -0
  136. package/services/localizations.js +65 -0
  137. package/services/metrics.js +24 -0
  138. package/services/permissions/actions.js +124 -0
  139. package/services/permissions/engine.js +63 -0
  140. package/services/permissions/sections-builder.js +48 -0
  141. package/services/permissions.js +11 -0
  142. package/tests/content-manager/list-relation.test.e2e.js +122 -0
  143. package/tests/graphql.test.e2e.js +120 -0
  144. package/tests/locales.test.e2e.js +414 -0
  145. package/utils/index.js +20 -0
  146. package/validation/content-types.js +30 -0
  147. 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,9 @@
1
+ 'use strict';
2
+
3
+ const { isoLocales } = require('../constants');
4
+
5
+ const getIsoLocales = () => isoLocales;
6
+
7
+ module.exports = {
8
+ getIsoLocales,
9
+ };
@@ -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
+ };