Merge pull request #19058 from strapi/v5/extend-locale-attr-to-all-ct

feat: extend i18n attributes to every content type
This commit is contained in:
Marc Roig 2024-01-12 11:22:53 +01:00 committed by GitHub
commit d39441e3c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 162 additions and 32 deletions

View File

@ -181,7 +181,10 @@ describe('Search query', () => {
expect(Array.isArray(res.body.results)).toBe(true); expect(Array.isArray(res.body.results)).toBe(true);
expect(res.body.results.length).toBe(data.beds.length); expect(res.body.results.length).toBe(data.beds.length);
expect(res.body.results.map(omit(CREATOR_FIELDS))).toEqual(expect.arrayContaining(data.beds)); // TODO V5: Filter out i18n fields if content type is not localized
expect(res.body.results.map(omit([...CREATOR_FIELDS, 'localizations']))).toEqual(
expect.arrayContaining(data.beds)
);
}); });
test('search with special characters', async () => { test('search with special characters', async () => {

View File

@ -745,6 +745,7 @@ describe('Core API - Validate', () => {
const allDocumentFields = [ const allDocumentFields = [
// TODO: Document id should not be in attributes // TODO: Document id should not be in attributes
'documentId', 'documentId',
'locale',
'name', 'name',
'name_non_searchable', 'name_non_searchable',
'misc', 'misc',

View File

@ -2,7 +2,7 @@ import { LoadedStrapi } from '@strapi/types';
import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper'; import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper';
import { testInTransaction } from '../../../utils/index'; import { testInTransaction } from '../../../utils/index';
import resources from './resources/index'; import resources from './resources/index';
import { ARTICLE_UID, findArticlesDb } from './utils'; import { ARTICLE_UID, findArticlesDb, AUTHOR_UID } from './utils';
describe('Document Service', () => { describe('Document Service', () => {
let testUtils; let testUtils;
@ -118,5 +118,22 @@ describe('Document Service', () => {
}); });
}) })
); );
it(
'ignores locale parameter on non-localized content type',
testInTransaction(async () => {
const author = await strapi.documents(AUTHOR_UID).create({
// Should be ignored on non-localized content types
locale: 'fr',
data: { name: 'Author' },
});
// verify that the returned document was updated
expect(author).toMatchObject({
name: 'Author',
locale: null, // should be null, as it is not a localized content type
});
})
);
}); });
}); });

View File

@ -1,7 +1,7 @@
import { LoadedStrapi } from '@strapi/types'; import { LoadedStrapi } from '@strapi/types';
import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper'; import { createTestSetup, destroyTestSetup } from '../../../utils/builder-helper';
import resources from './resources/index'; import resources from './resources/index';
import { ARTICLE_UID, findArticleDb } from './utils'; import { ARTICLE_UID, findArticleDb, AUTHOR_UID, findAuthorDb } from './utils';
describe('Document Service', () => { describe('Document Service', () => {
let testUtils; let testUtils;
@ -55,6 +55,17 @@ describe('Document Service', () => {
expect(article).toMatchObject(articleDb); expect(article).toMatchObject(articleDb);
}); });
it('ignores locale parameter on non-localized content type', async () => {
const authorDb = await findAuthorDb({ name: 'Author1-Draft' });
// Locale should be ignored on non-localized content types
const author = await strapi.documents(AUTHOR_UID).findOne(authorDb.documentId, {
locale: 'en',
});
expect(author).toMatchObject(authorDb);
});
it.todo('ignores pagination parameters'); it.todo('ignores pagination parameters');
}); });
}); });

View File

@ -0,0 +1,16 @@
[
{
"id": 1,
"documentId": "Author1",
"name": "Author1-Draft",
"publishedAt": null,
"locale": null
},
{
"id": 2,
"documentId": "Author2",
"name": "Author2-Draft",
"publishedAt": null,
"locale": null
}
]

View File

@ -5,5 +5,6 @@ module.exports = {
// Make sure this is sorted by order to create them // Make sure this is sorted by order to create them
'api::category.category': require('./category.json'), 'api::category.category': require('./category.json'),
'api::article.article': require('./article.json'), 'api::article.article': require('./article.json'),
'api::author.author': require('./author.json'),
}, },
}; };

View File

@ -0,0 +1,26 @@
'use strict';
module.exports = {
kind: 'collectionType',
collectionName: 'authors',
singularName: 'author',
pluralName: 'authors',
displayName: 'Author',
description: '',
draftAndPublish: true,
pluginOptions: {
i18n: {
localized: false,
},
},
attributes: {
name: {
type: 'string',
pluginOptions: {
i18n: {
localized: false,
},
},
},
},
};

View File

@ -4,6 +4,7 @@ module.exports = {
'content-types': { 'content-types': {
'api::category.category': require('./category'), 'api::category.category': require('./category'),
'api::article.article': require('./article'), 'api::article.article': require('./article'),
'api::author.author': require('./author'),
}, },
components: { components: {
'article.comp': require('./comp'), 'article.comp': require('./comp'),

View File

@ -618,6 +618,38 @@ export interface ApiArticleArticle extends Schema.CollectionType {
}; };
} }
export interface ApiAuthorAuthor extends Schema.CollectionType {
collectionName: 'authors';
info: {
singularName: 'author';
pluralName: 'authors';
displayName: 'Author';
description: '';
};
pluginOptions: {
i18n: {
localized: false;
};
};
attributes: {
name: Attribute.String &
Attribute.SetPluginOptions<{
i18n: {
localized: false;
};
}>;
createdAt: Attribute.DateTime;
updatedAt: Attribute.DateTime;
publishedAt: Attribute.DateTime;
createdBy: Attribute.Relation<'api::author.author', 'oneToOne', 'admin::user'> &
Attribute.Private;
updatedBy: Attribute.Relation<'api::author.author', 'oneToOne', 'admin::user'> &
Attribute.Private;
localizations: Attribute.Relation<'api::author.author', 'oneToMany', 'api::author.author'>;
locale: Attribute.String;
};
}
export interface ApiCategoryCategory extends Schema.CollectionType { export interface ApiCategoryCategory extends Schema.CollectionType {
collectionName: 'categories'; collectionName: 'categories';
info: { info: {
@ -674,6 +706,7 @@ declare module '@strapi/types' {
'plugin::users-permissions.role': PluginUsersPermissionsRole; 'plugin::users-permissions.role': PluginUsersPermissionsRole;
'plugin::users-permissions.user': PluginUsersPermissionsUser; 'plugin::users-permissions.user': PluginUsersPermissionsUser;
'api::article.article': ApiArticleArticle; 'api::article.article': ApiArticleArticle;
'api::author.author': ApiAuthorAuthor;
'api::category.category': ApiCategoryCategory; 'api::category.category': ApiCategoryCategory;
} }
} }

View File

@ -1,5 +1,8 @@
import { Attribute } from '@strapi/strapi'; import { Attribute } from '@strapi/strapi';
export const AUTHOR_UID = 'api::author.author';
export type Author = Attribute.GetAll<typeof AUTHOR_UID> & { documentId: string; id: number };
export const ARTICLE_UID = 'api::article.article'; export const ARTICLE_UID = 'api::article.article';
export type Article = Attribute.GetAll<typeof ARTICLE_UID> & { documentId: string; id: number }; export type Article = Attribute.GetAll<typeof ARTICLE_UID> & { documentId: string; id: number };
@ -14,3 +17,15 @@ export const findArticlesDb = async (where: any) => {
export const findPublishedArticlesDb = async (documentId) => { export const findPublishedArticlesDb = async (documentId) => {
return findArticlesDb({ documentId, publishedAt: { $notNull: true } }); return findArticlesDb({ documentId, publishedAt: { $notNull: true } });
}; };
export const findAuthorDb = async (where: any) => {
return (await strapi.query(AUTHOR_UID).findOne({ where })) as Author | undefined;
};
export const findAuthorsDb = async (where: any) => {
return (await strapi.query(AUTHOR_UID).findMany({ where })) as Author[];
};
export const findPublishedAuthorsDb = async (documentId) => {
return findAuthorsDb({ documentId, publishedAt: { $notNull: true } });
};

View File

@ -276,10 +276,8 @@ export const CMReleasesContainer = () => {
/** /**
* - Impossible to add entry to release before it exists * - Impossible to add entry to release before it exists
* - Content types without draft and publish cannot add entries to release
* TODO v5: All contentTypes will have draft and publish enabled
*/ */
if (isCreatingEntry || !contentType?.options?.draftAndPublish) { if (isCreatingEntry) {
return null; return null;
} }

View File

@ -1,13 +1,19 @@
// TODO: Move to i18n // TODO: Move to i18n
import { Documents } from '@strapi/types'; import { Documents, Common } from '@strapi/types';
type Middleware = Documents.Middleware.Middleware<any, any>; type Middleware = Documents.Middleware.Middleware<any, any>;
const isLocalizedContentType = (uid: Common.UID.Schema) => {
const model = strapi.getModel(uid);
return strapi.plugin('i18n').service('content-types').isLocalizedContentType(model);
};
export const defaultLocale: Middleware = async (ctx, next) => { export const defaultLocale: Middleware = async (ctx, next) => {
if (!isLocalizedContentType(ctx.uid)) return next(ctx);
if (!ctx.params) ctx.params = {}; if (!ctx.params) ctx.params = {};
// Default to en (TODO: Load default locale from db in i18n)
if (!ctx.params.locale) { if (!ctx.params.locale) {
// Default to en (TODO: Load default locale from db in i18n)
ctx.params.locale = 'en'; ctx.params.locale = 'en';
} }
@ -18,6 +24,7 @@ export const defaultLocale: Middleware = async (ctx, next) => {
* Add locale lookup query to the params * Add locale lookup query to the params
*/ */
export const localeToLookup: Middleware = async (ctx, next) => { export const localeToLookup: Middleware = async (ctx, next) => {
if (!isLocalizedContentType(ctx.uid)) return next(ctx);
if (!ctx.params) ctx.params = {}; if (!ctx.params) ctx.params = {};
const lookup = ctx.params.lookup || {}; const lookup = ctx.params.lookup || {};
@ -34,6 +41,7 @@ export const localeToLookup: Middleware = async (ctx, next) => {
* Translate locale status parameter into the data that will be saved * Translate locale status parameter into the data that will be saved
*/ */
export const localeToData: Middleware = async (ctx, next) => { export const localeToData: Middleware = async (ctx, next) => {
if (!isLocalizedContentType(ctx.uid)) return next(ctx);
if (!ctx.params) ctx.params = {}; if (!ctx.params) ctx.params = {};
const data = ctx.params.data || {}; const data = ctx.params.data || {};

View File

@ -9,7 +9,7 @@ import enableContentType from './migrations/content-type/enable';
import disableContentType from './migrations/content-type/disable'; import disableContentType from './migrations/content-type/disable';
export default ({ strapi }: { strapi: Strapi }) => { export default ({ strapi }: { strapi: Strapi }) => {
extendLocalizedContentTypes(strapi); extendContentTypes(strapi);
addContentManagerLocaleMiddleware(strapi); addContentManagerLocaleMiddleware(strapi);
addContentTypeSyncHooks(strapi); addContentTypeSyncHooks(strapi);
}; };
@ -46,37 +46,35 @@ const addContentTypeSyncHooks = (strapi: Strapi) => {
}; };
/** /**
* Adds locale and localization fields to localized content types * Adds locale and localization fields to all content types
* Even if content type is not localized, it will have these fields
* @param {Strapi} strapi * @param {Strapi} strapi
*/ */
const extendLocalizedContentTypes = (strapi: Strapi) => { const extendContentTypes = (strapi: Strapi) => {
const contentTypeService = getService('content-types');
const coreApiService = getService('core-api'); const coreApiService = getService('core-api');
Object.values(strapi.contentTypes).forEach((contentType) => { Object.values(strapi.contentTypes).forEach((contentType) => {
if (contentTypeService.isLocalizedContentType(contentType)) { const { attributes } = contentType;
const { attributes } = contentType;
_.set(attributes, 'localizations', { _.set(attributes, 'localizations', {
writable: true, writable: true,
private: false, private: false,
configurable: false, configurable: false,
visible: false, visible: false,
type: 'relation', type: 'relation',
relation: 'oneToMany', relation: 'oneToMany',
target: contentType.uid, target: contentType.uid,
}); });
_.set(attributes, 'locale', { _.set(attributes, 'locale', {
writable: true, writable: true,
private: false, private: false,
configurable: false, configurable: false,
visible: false, visible: false,
type: 'string', type: 'string',
}); });
coreApiService.addCreateLocalizationAction(contentType); coreApiService.addCreateLocalizationAction(contentType);
}
}); });
if (strapi.plugin('graphql')) { if (strapi.plugin('graphql')) {

View File

@ -115,6 +115,8 @@ const userSchemaAdditions = () => {
'publishedAt', 'publishedAt',
'strapi_stage', 'strapi_stage',
'strapi_assignee', 'strapi_assignee',
'locale',
'localizations',
]; ];
return currentSchema.filter((key) => !(ignoreDiffs.includes(key) || defaultSchema.includes(key))); return currentSchema.filter((key) => !(ignoreDiffs.includes(key) || defaultSchema.includes(key)));