@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,122 @@
1
+ 'use strict';
2
+
3
+ const { pick } = require('lodash/fp');
4
+
5
+ const { createTestBuilder } = require('../../../../test/helpers/builder');
6
+ const { createStrapiInstance } = require('../../../../test/helpers/strapi');
7
+ const { createAuthRequest } = require('../../../../test/helpers/request');
8
+
9
+ let strapi;
10
+ let rq;
11
+ let data = {
12
+ products: [],
13
+ shops: [],
14
+ };
15
+
16
+ const productModel = {
17
+ pluginOptions: {
18
+ i18n: {
19
+ localized: true,
20
+ },
21
+ },
22
+ attributes: {
23
+ name: {
24
+ type: 'string',
25
+ },
26
+ },
27
+ connection: 'default',
28
+ name: 'product',
29
+ description: '',
30
+ collectionName: '',
31
+ };
32
+
33
+ const shopModel = {
34
+ pluginOptions: {
35
+ i18n: {
36
+ localized: true,
37
+ },
38
+ },
39
+ attributes: {
40
+ name: {
41
+ type: 'string',
42
+ },
43
+ products: {
44
+ dominant: true,
45
+ nature: 'manyToMany',
46
+ target: 'application::product.product',
47
+ targetAttribute: 'shops',
48
+ },
49
+ },
50
+ connection: 'default',
51
+ name: 'shop',
52
+ };
53
+
54
+ const shops = [
55
+ {
56
+ name: 'market',
57
+ locale: 'en',
58
+ },
59
+ ];
60
+
61
+ const products = ({ shop }) => {
62
+ const shops = [shop[0].id];
63
+
64
+ const entries = [
65
+ {
66
+ name: 'pomodoro',
67
+ shops,
68
+ locale: 'it',
69
+ },
70
+ {
71
+ name: 'apple',
72
+ shops,
73
+ locale: 'en',
74
+ },
75
+ ];
76
+
77
+ return entries;
78
+ };
79
+
80
+ describe('i18n - Relation-list route', () => {
81
+ const builder = createTestBuilder();
82
+
83
+ beforeAll(async () => {
84
+ await builder
85
+ .addContentTypes([productModel, shopModel])
86
+ .addFixtures(shopModel.name, shops)
87
+ .addFixtures(productModel.name, products)
88
+ .build();
89
+
90
+ strapi = await createStrapiInstance();
91
+ rq = await createAuthRequest({ strapi });
92
+
93
+ data.shops = builder.sanitizedFixturesFor(shopModel.name, strapi);
94
+ data.products = builder.sanitizedFixturesFor(productModel.name, strapi);
95
+ });
96
+
97
+ afterAll(async () => {
98
+ await strapi.destroy();
99
+ await builder.cleanup();
100
+ });
101
+
102
+ test('Can filter on default locale', async () => {
103
+ const res = await rq({
104
+ method: 'POST',
105
+ url: '/content-manager/relations/application::shop.shop/products',
106
+ });
107
+
108
+ expect(res.body).toHaveLength(1);
109
+ expect(res.body[0]).toStrictEqual(pick(['_id', 'id', 'name'], data.products[1]));
110
+ });
111
+
112
+ test('Can filter on any locale', async () => {
113
+ const res = await rq({
114
+ method: 'POST',
115
+ url: '/content-manager/relations/application::shop.shop/products',
116
+ qs: { _locale: 'it' },
117
+ });
118
+
119
+ expect(res.body).toHaveLength(1);
120
+ expect(res.body[0]).toStrictEqual(pick(['_id', 'id', 'name'], data.products[0]));
121
+ });
122
+ });
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ // Helpers.
4
+ const { createTestBuilder } = require('../../../test/helpers/builder');
5
+ const { createStrapiInstance } = require('../../../test/helpers/strapi');
6
+ const { createAuthRequest } = require('../../../test/helpers/request');
7
+
8
+ const builder = createTestBuilder();
9
+ let strapi;
10
+ let rq;
11
+ let graphqlQuery;
12
+ let localeId;
13
+
14
+ const recipesModel = {
15
+ attributes: {
16
+ name: {
17
+ type: 'string',
18
+ },
19
+ },
20
+ pluginOptions: {
21
+ i18n: {
22
+ localized: true,
23
+ },
24
+ },
25
+ connection: 'default',
26
+ name: 'recipes',
27
+ description: '',
28
+ collectionName: '',
29
+ };
30
+
31
+ describe('Test Graphql API create localization', () => {
32
+ beforeAll(async () => {
33
+ await builder.addContentType(recipesModel).build();
34
+
35
+ strapi = await createStrapiInstance();
36
+ rq = await createAuthRequest({ strapi });
37
+
38
+ graphqlQuery = body => {
39
+ return rq({
40
+ url: '/graphql',
41
+ method: 'POST',
42
+ body,
43
+ });
44
+ };
45
+
46
+ const locale = await strapi.query('locale', 'i18n').create({
47
+ code: 'fr',
48
+ name: 'French',
49
+ });
50
+
51
+ localeId = locale.id;
52
+ });
53
+
54
+ afterAll(async () => {
55
+ await strapi.query('locale', 'i18n').delete({ id: localeId });
56
+ await strapi.query('recipes').delete();
57
+ await strapi.destroy();
58
+ await builder.cleanup();
59
+ });
60
+
61
+ test('Create localization for a model with plural name', async () => {
62
+ const createResponse = await graphqlQuery({
63
+ query: /* GraphQL */ `
64
+ mutation createRecipe($input: createRecipeInput) {
65
+ createRecipe(input: $input) {
66
+ recipe {
67
+ id
68
+ name
69
+ locale
70
+ }
71
+ }
72
+ }
73
+ `,
74
+ variables: {
75
+ input: {
76
+ data: {
77
+ name: 'Recipe Name',
78
+ },
79
+ },
80
+ },
81
+ });
82
+
83
+ expect(createResponse.statusCode).toBe(200);
84
+ expect(createResponse.body.data.createRecipe.recipe).toMatchObject({
85
+ name: 'Recipe Name',
86
+ locale: 'en',
87
+ });
88
+
89
+ const recipeId = createResponse.body.data.createRecipe.recipe.id;
90
+
91
+ const createLocalizationResponse = await graphqlQuery({
92
+ query: /* GraphQL */ `
93
+ mutation createRecipeLocalization($input: updateRecipeInput!) {
94
+ createRecipeLocalization(input: $input) {
95
+ id
96
+ name
97
+ locale
98
+ }
99
+ }
100
+ `,
101
+ variables: {
102
+ input: {
103
+ where: {
104
+ id: recipeId,
105
+ },
106
+ data: {
107
+ name: 'Recipe Name fr',
108
+ locale: 'fr',
109
+ },
110
+ },
111
+ },
112
+ });
113
+
114
+ expect(createLocalizationResponse.statusCode).toBe(200);
115
+ expect(createLocalizationResponse.body.data.createRecipeLocalization).toMatchObject({
116
+ name: 'Recipe Name fr',
117
+ locale: 'fr',
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,414 @@
1
+ 'use strict';
2
+
3
+ const { omit } = require('lodash/fp');
4
+
5
+ const { createTestBuilder } = require('../../../test/helpers/builder');
6
+ const { createStrapiInstance } = require('../../../test/helpers/strapi');
7
+ const { createAuthRequest } = require('../../../test/helpers/request');
8
+
9
+ const data = {
10
+ locales: [],
11
+ deletedLocales: [],
12
+ };
13
+
14
+ const omitTimestamps = omit(['updatedAt', 'createdAt', 'updated_at', 'created_at']);
15
+ const compareLocales = (a, b) => (a.code < b.code ? -1 : 1);
16
+
17
+ const productModel = {
18
+ pluginOptions: {
19
+ i18n: {
20
+ localized: true,
21
+ },
22
+ },
23
+ attributes: {
24
+ name: {
25
+ type: 'string',
26
+ },
27
+ },
28
+ connection: 'default',
29
+ name: 'product',
30
+ description: '',
31
+ collectionName: '',
32
+ };
33
+
34
+ describe('CRUD locales', () => {
35
+ let rq;
36
+ let strapi;
37
+ const builder = createTestBuilder();
38
+ let localeService;
39
+
40
+ beforeAll(async () => {
41
+ await builder.addContentType(productModel).build();
42
+
43
+ strapi = await createStrapiInstance();
44
+ rq = await createAuthRequest({ strapi });
45
+
46
+ localeService = strapi.plugins.i18n.services.locales;
47
+ });
48
+
49
+ afterAll(async () => {
50
+ await localeService.setDefaultLocale({ code: 'en' });
51
+
52
+ await strapi.destroy();
53
+ await builder.cleanup();
54
+ });
55
+
56
+ describe('Default locale', () => {
57
+ test('Default locale is already created', async () => {
58
+ let res = await rq({
59
+ url: '/i18n/locales',
60
+ method: 'GET',
61
+ });
62
+
63
+ expect(res.statusCode).toBe(200);
64
+ expect(res.body).toHaveLength(1);
65
+ expect(res.body[0].isDefault).toBe(true);
66
+ data.locales.push(res.body[0]);
67
+ });
68
+ });
69
+
70
+ describe('Creation', () => {
71
+ test('Can create a locale', async () => {
72
+ const locale = {
73
+ name: 'French',
74
+ code: 'fr',
75
+ isDefault: false,
76
+ };
77
+
78
+ let res = await rq({
79
+ url: '/i18n/locales',
80
+ method: 'POST',
81
+ body: locale,
82
+ });
83
+
84
+ expect(res.statusCode).toBe(200);
85
+ expect(res.body).toMatchObject({
86
+ id: expect.anything(),
87
+ ...locale,
88
+ });
89
+ data.locales.push(res.body);
90
+ });
91
+
92
+ test('Cannot create a locale if code or isDefault is missing', async () => {
93
+ const locale = {
94
+ name: 'Italian',
95
+ };
96
+
97
+ let res = await rq({
98
+ url: '/i18n/locales',
99
+ method: 'POST',
100
+ body: locale,
101
+ });
102
+
103
+ expect(res.statusCode).toBe(400);
104
+ expect(res.body).toMatchObject({
105
+ data: {
106
+ isDefault: ['isDefault is a required field'],
107
+ code: ['code is a required field'],
108
+ },
109
+ error: 'Bad Request',
110
+ message: 'ValidationError',
111
+ statusCode: 400,
112
+ });
113
+ });
114
+
115
+ test('Cannot create a locale if code already exists', async () => {
116
+ const locale = {
117
+ code: 'fr',
118
+ name: 'random name',
119
+ isDefault: false,
120
+ };
121
+
122
+ let res = await rq({
123
+ url: '/i18n/locales',
124
+ method: 'POST',
125
+ body: locale,
126
+ });
127
+
128
+ expect(res.statusCode).toBe(400);
129
+ expect(res.body).toMatchObject({ message: 'This locale already exists' });
130
+ });
131
+
132
+ test('Can create a locale even if name already exists', async () => {
133
+ const locale = {
134
+ name: 'French',
135
+ code: 'fr-FR',
136
+ isDefault: false,
137
+ };
138
+
139
+ let res = await rq({
140
+ url: '/i18n/locales',
141
+ method: 'POST',
142
+ body: locale,
143
+ });
144
+
145
+ expect(res.statusCode).toBe(200);
146
+ expect(res.body).toMatchObject({
147
+ id: expect.anything(),
148
+ ...locale,
149
+ });
150
+ data.locales.push(res.body);
151
+ });
152
+
153
+ test('Only one locale can be default (POST)', async () => {
154
+ let res = await rq({
155
+ url: '/i18n/locales',
156
+ method: 'POST',
157
+ body: { code: 'bas', name: 'random', isDefault: true },
158
+ });
159
+
160
+ expect(res.statusCode).toBe(200);
161
+ expect(res.body.isDefault).toBe(true);
162
+ data.locales[0].isDefault = false;
163
+
164
+ res = await rq({
165
+ url: '/i18n/locales',
166
+ method: 'POST',
167
+ body: { code: 'en-US', name: 'random', isDefault: true },
168
+ });
169
+ expect(res.statusCode).toBe(200);
170
+ expect(res.body.isDefault).toBe(true);
171
+
172
+ res = await rq({
173
+ url: '/i18n/locales',
174
+ method: 'GET',
175
+ });
176
+
177
+ expect(res.statusCode).toBe(200);
178
+ const enLocale = res.body.find(locale => locale.code === 'bas');
179
+ const enUsLocale = res.body.find(locale => locale.code === 'en-US');
180
+ expect(enLocale.isDefault).toBe(false);
181
+ expect(enUsLocale.isDefault).toBe(true);
182
+
183
+ data.locales.push(enLocale);
184
+ data.locales.push(enUsLocale);
185
+ });
186
+ });
187
+
188
+ describe('Read', () => {
189
+ test('Can list the locales', async () => {
190
+ let res = await rq({
191
+ url: '/i18n/locales',
192
+ method: 'GET',
193
+ });
194
+
195
+ expect(res.statusCode).toBe(200);
196
+ expect(res.body).toHaveLength(data.locales.length);
197
+ expect(res.body.sort(compareLocales)).toMatchObject(
198
+ data.locales.slice().sort(compareLocales)
199
+ );
200
+ });
201
+ });
202
+
203
+ describe('Update', () => {
204
+ test('Can update the name of a locale', async () => {
205
+ const localeUpdate = {
206
+ name: 'French update',
207
+ isDefault: false,
208
+ };
209
+
210
+ let res = await rq({
211
+ url: `/i18n/locales/${data.locales[1].id}`,
212
+ method: 'PUT',
213
+ body: localeUpdate,
214
+ });
215
+
216
+ expect(res.statusCode).toBe(200);
217
+ expect(res.body).toMatchObject({
218
+ ...omitTimestamps(data.locales[1]),
219
+ ...localeUpdate,
220
+ });
221
+ data.locales[1] = res.body;
222
+ });
223
+
224
+ test('Cannot update the code of a locale (without name)', async () => {
225
+ const localeUpdate = {
226
+ code: 'ak',
227
+ };
228
+
229
+ let res = await rq({
230
+ url: `/i18n/locales/${data.locales[0].id}`,
231
+ method: 'PUT',
232
+ body: localeUpdate,
233
+ });
234
+
235
+ expect(res.statusCode).toBe(400);
236
+ expect(res.body).toMatchObject({
237
+ data: {
238
+ '': ['this field has unspecified keys: code'],
239
+ },
240
+ error: 'Bad Request',
241
+ message: 'ValidationError',
242
+ statusCode: 400,
243
+ });
244
+ });
245
+
246
+ test('Cannot update the code of a locale (with name)', async () => {
247
+ const localeUpdate = {
248
+ name: 'French',
249
+ code: 'ak',
250
+ };
251
+
252
+ let res = await rq({
253
+ url: `/i18n/locales/${data.locales[0].id}`,
254
+ method: 'PUT',
255
+ body: localeUpdate,
256
+ });
257
+
258
+ expect(res.statusCode).toBe(400);
259
+ expect(res.body).toMatchObject({
260
+ data: {
261
+ '': ['this field has unspecified keys: code'],
262
+ },
263
+ error: 'Bad Request',
264
+ message: 'ValidationError',
265
+ statusCode: 400,
266
+ });
267
+ });
268
+
269
+ test('Only one locale can be default (PUT)', async () => {
270
+ let res = await rq({
271
+ url: `/i18n/locales/${data.locales[0].id}`,
272
+ method: 'PUT',
273
+ body: { isDefault: true },
274
+ });
275
+
276
+ expect(res.statusCode).toBe(200);
277
+ expect(res.body.isDefault).toBe(true);
278
+
279
+ res = await rq({
280
+ url: `/i18n/locales/${data.locales[1].id}`,
281
+ method: 'PUT',
282
+ body: { isDefault: true },
283
+ });
284
+ expect(res.statusCode).toBe(200);
285
+ expect(res.body.isDefault).toBe(true);
286
+
287
+ res = await rq({
288
+ url: '/i18n/locales',
289
+ method: 'GET',
290
+ });
291
+
292
+ expect(res.statusCode).toBe(200);
293
+ expect(res.body.find(locale => locale.code === data.locales[0].code).isDefault).toBe(false);
294
+ expect(res.body.find(locale => locale.code === data.locales[1].code).isDefault).toBe(true);
295
+ });
296
+
297
+ test('Cannot unselect isDefault', async () => {
298
+ let res = await rq({
299
+ url: `/i18n/locales/${data.locales[0].id}`,
300
+ method: 'PUT',
301
+ body: { isDefault: true },
302
+ });
303
+
304
+ expect(res.statusCode).toBe(200);
305
+ expect(res.body.isDefault).toBe(true);
306
+
307
+ res = await rq({
308
+ url: `/i18n/locales/${data.locales[0].id}`,
309
+ method: 'PUT',
310
+ body: { isDefault: false },
311
+ });
312
+ expect(res.statusCode).toBe(200);
313
+ expect(res.body.isDefault).toBe(true);
314
+ });
315
+ });
316
+
317
+ describe('Delete', () => {
318
+ test('Cannot delete default locale', async () => {
319
+ let res = await rq({
320
+ url: `/i18n/locales/${data.locales[0].id}`,
321
+ method: 'PUT',
322
+ body: { isDefault: true },
323
+ });
324
+
325
+ expect(res.statusCode).toBe(200);
326
+ expect(res.body.isDefault).toBe(true);
327
+ data.locales[1].isDefault = false;
328
+
329
+ res = await rq({
330
+ url: `/i18n/locales/${data.locales[0].id}`,
331
+ method: 'DELETE',
332
+ });
333
+ expect(res.statusCode).toBe(400);
334
+ expect(res.body.message).toBe('Cannot delete the default locale');
335
+ });
336
+
337
+ test('Simply delete a locale', async () => {
338
+ const res = await rq({
339
+ url: `/i18n/locales/${data.locales[1].id}`,
340
+ method: 'DELETE',
341
+ });
342
+
343
+ expect(res.statusCode).toBe(200);
344
+ expect(res.body).toMatchObject(omitTimestamps(data.locales[1]));
345
+ data.deletedLocales.push(res.body);
346
+ data.locales.splice(1, 1);
347
+ });
348
+
349
+ test('Delete a locale and entities in this locale', async () => {
350
+ const { body: frenchProduct } = await rq({
351
+ url: '/content-manager/collection-types/application::product.product',
352
+ method: 'POST',
353
+ qs: { plugins: { i18n: { locale: 'fr-FR' } } },
354
+ body: { name: 'product name' },
355
+ });
356
+
357
+ await rq({
358
+ url: '/content-manager/collection-types/application::product.product',
359
+ method: 'POST',
360
+ qs: { plugins: { i18n: { locale: 'en', relatedEntityId: frenchProduct.id } } },
361
+ body: { name: 'product name' },
362
+ });
363
+
364
+ const {
365
+ body: { results: createdProducts },
366
+ } = await rq({
367
+ url: '/content-manager/collection-types/application::product.product',
368
+ method: 'GET',
369
+ qs: { _locale: 'fr-FR' },
370
+ });
371
+
372
+ expect(createdProducts).toHaveLength(1);
373
+ expect(createdProducts[0].localizations[0].locale).toBe('en');
374
+
375
+ const res = await rq({
376
+ url: `/i18n/locales/${data.locales[1].id}`,
377
+ method: 'DELETE',
378
+ });
379
+
380
+ const {
381
+ body: { results: frenchProducts },
382
+ } = await rq({
383
+ url: '/content-manager/collection-types/application::product.product',
384
+ method: 'GET',
385
+ qs: { _locale: 'fr-FR' },
386
+ });
387
+ expect(frenchProducts).toHaveLength(0);
388
+
389
+ const {
390
+ body: { results: englishProducts },
391
+ } = await rq({
392
+ url: '/content-manager/collection-types/application::product.product',
393
+ method: 'GET',
394
+ qs: { _locale: 'en' },
395
+ });
396
+ expect(englishProducts).toHaveLength(1);
397
+
398
+ expect(res.statusCode).toBe(200);
399
+ expect(res.body).toMatchObject(omitTimestamps(data.locales[1]));
400
+ data.deletedLocales.push(res.body);
401
+ data.locales.splice(1, 1);
402
+ });
403
+
404
+ test('Cannot delete not found locale', async () => {
405
+ let res = await rq({
406
+ url: `/i18n/locales/${data.deletedLocales[0].id}`,
407
+ method: 'DELETE',
408
+ });
409
+
410
+ expect(res.statusCode).toBe(404);
411
+ expect(res.body.message).toBe('locale.notFound');
412
+ });
413
+ });
414
+ });
package/utils/index.js ADDED
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const { prop } = require('lodash/fp');
4
+
5
+ const getCoreStore = () =>
6
+ strapi.store({
7
+ environment: '',
8
+ type: 'plugin',
9
+ name: 'i18n',
10
+ });
11
+
12
+ // retrieve a local service
13
+ const getService = name => {
14
+ return prop(`i18n.services.${name}`, strapi.plugins);
15
+ };
16
+
17
+ module.exports = {
18
+ getService,
19
+ getCoreStore,
20
+ };
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const { yup, formatYupErrors } = require('@akemona-org/strapi-utils');
4
+ const { get } = require('lodash/fp');
5
+
6
+ const handleReject = (error) => Promise.reject(formatYupErrors(error));
7
+
8
+ const validateGetNonLocalizedAttributesSchema = yup
9
+ .object()
10
+ .shape({
11
+ model: yup.string().required(),
12
+ id: yup.mixed().when('model', {
13
+ is: (model) => get('kind', strapi.getModel(model)) === 'singleType',
14
+ then: yup.strapiID().nullable(),
15
+ otherwise: yup.strapiID().required(),
16
+ }),
17
+ locale: yup.string().required(),
18
+ })
19
+ .noUnknown()
20
+ .required();
21
+
22
+ const validateGetNonLocalizedAttributesInput = (data) => {
23
+ return validateGetNonLocalizedAttributesSchema
24
+ .validate(data, { strict: true, abortEarly: false })
25
+ .catch(handleReject);
26
+ };
27
+
28
+ module.exports = {
29
+ validateGetNonLocalizedAttributesInput,
30
+ };