mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 16:29:34 +00:00
Merge pull request #9642 from strapi/i18n/default-params-locale
I18n/default params locale
This commit is contained in:
commit
e82c7fb611
@ -8,10 +8,7 @@
|
||||
"options": {
|
||||
"draftAndPublish": true,
|
||||
"increments": true,
|
||||
"timestamps": [
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
"timestamps": ["created_at", "updated_at"],
|
||||
"comment": ""
|
||||
},
|
||||
"pluginOptions": {
|
||||
@ -41,13 +38,7 @@
|
||||
}
|
||||
},
|
||||
"price_range": {
|
||||
"enum": [
|
||||
"very_cheap",
|
||||
"cheap",
|
||||
"average",
|
||||
"expensive",
|
||||
"very_expensive"
|
||||
],
|
||||
"enum": ["very_cheap", "cheap", "average", "expensive", "very_expensive"],
|
||||
"type": "enumeration",
|
||||
"pluginOptions": {
|
||||
"i18n": {
|
||||
@ -169,15 +160,6 @@
|
||||
"localized": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parent": {
|
||||
"collection": "restaurant",
|
||||
"via": "children",
|
||||
"dominant": true
|
||||
},
|
||||
"children": {
|
||||
"collection": "restaurant",
|
||||
"via": "parent"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ module.exports = async () => {
|
||||
const { actionProvider } = strapi.admin.services.permission;
|
||||
actionProvider.register(actions);
|
||||
|
||||
getService('entity-service-decorator').decorate();
|
||||
const { decorator } = getService('entity-service-decorator');
|
||||
strapi.entityService.decorate(decorator);
|
||||
|
||||
await getService('locales').initDefaultLocale();
|
||||
|
||||
|
@ -0,0 +1,240 @@
|
||||
'use strict';
|
||||
|
||||
jest.mock('../localizations', () => {
|
||||
return {
|
||||
syncLocalizations: jest.fn(async () => {}),
|
||||
updateNonLocalizedFields: jest.fn(async () => {}),
|
||||
};
|
||||
});
|
||||
|
||||
const { decorator } = require('../entity-service-decorator');
|
||||
const { syncLocalizations, updateNonLocalizedFields } = require('../localizations');
|
||||
|
||||
const model = {
|
||||
pluginOptions: {
|
||||
i18n: {
|
||||
localized: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const nonLocalizedModel = {
|
||||
pluginOptions: {
|
||||
i18n: {
|
||||
localized: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const models = {
|
||||
'test-model': model,
|
||||
'non-localized-model': nonLocalizedModel,
|
||||
};
|
||||
|
||||
describe('Entity service decorator', () => {
|
||||
beforeAll(() => {
|
||||
global.strapi = {
|
||||
query() {
|
||||
return {
|
||||
create() {},
|
||||
update() {},
|
||||
};
|
||||
},
|
||||
db: {
|
||||
getModel(uid) {
|
||||
return models[uid || 'test-model'];
|
||||
},
|
||||
},
|
||||
store: () => ({ get: () => 'en' }),
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
syncLocalizations.mockClear();
|
||||
updateNonLocalizedFields.mockClear();
|
||||
});
|
||||
|
||||
describe('wrapOptions', () => {
|
||||
test('Calls original wrapOptions', async () => {
|
||||
const defaultService = {
|
||||
wrapOptions: jest.fn(() => Promise.resolve('li')),
|
||||
};
|
||||
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { populate: ['test'] };
|
||||
await service.wrapOptions(input, { model: 'test-model' });
|
||||
|
||||
expect(defaultService.wrapOptions).toHaveBeenCalledWith(input, { model: 'test-model' });
|
||||
});
|
||||
|
||||
test('Does not wrap options if model is not localized', async () => {
|
||||
const defaultService = {
|
||||
wrapOptions: jest.fn(opts => Promise.resolve(opts)),
|
||||
};
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { populate: ['test'] };
|
||||
const output = await service.wrapOptions(input, { model: 'non-localized-model' });
|
||||
|
||||
expect(output).toStrictEqual(input);
|
||||
});
|
||||
|
||||
test('does not change non params options', async () => {
|
||||
const defaultService = {
|
||||
wrapOptions: jest.fn(opts => Promise.resolve(opts)),
|
||||
};
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { populate: ['test'] };
|
||||
const output = await service.wrapOptions(input, { model: 'test-model' });
|
||||
|
||||
expect(output.populate).toStrictEqual(input.populate);
|
||||
});
|
||||
|
||||
test('Adds locale param', async () => {
|
||||
const defaultService = {
|
||||
wrapOptions: jest.fn(opts => Promise.resolve(opts)),
|
||||
};
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { populate: ['test'] };
|
||||
const output = await service.wrapOptions(input, { model: 'test-model' });
|
||||
|
||||
expect(output).toMatchObject({ params: { locale: 'en' } });
|
||||
});
|
||||
|
||||
test('Replaces _locale param', async () => {
|
||||
const defaultService = {
|
||||
wrapOptions: jest.fn(opts => Promise.resolve(opts)),
|
||||
};
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = {
|
||||
params: {
|
||||
_locale: 'fr',
|
||||
},
|
||||
populate: ['test'],
|
||||
};
|
||||
const output = await service.wrapOptions(input, { model: 'test-model' });
|
||||
|
||||
expect(output).toMatchObject({ params: { locale: 'fr' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
test('Calls original create', async () => {
|
||||
const entry = {
|
||||
id: 1,
|
||||
};
|
||||
|
||||
const defaultService = {
|
||||
create: jest.fn(() => Promise.resolve(entry)),
|
||||
};
|
||||
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { data: { title: 'title ' } };
|
||||
await service.create(input, { model: 'test-model' });
|
||||
|
||||
expect(defaultService.create).toHaveBeenCalledWith(input, { model: 'test-model' });
|
||||
});
|
||||
|
||||
test('Calls syncLocalizations if model is localized', async () => {
|
||||
const entry = {
|
||||
id: 1,
|
||||
localizations: [{ id: 2 }],
|
||||
};
|
||||
|
||||
const defaultService = {
|
||||
create: jest.fn(() => Promise.resolve(entry)),
|
||||
};
|
||||
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { data: { title: 'title ' } };
|
||||
await service.create(input, { model: 'test-model' });
|
||||
|
||||
expect(defaultService.create).toHaveBeenCalledWith(input, { model: 'test-model' });
|
||||
expect(syncLocalizations).toHaveBeenCalledWith(entry, { model });
|
||||
});
|
||||
|
||||
test('Skip processing if model is not localized', async () => {
|
||||
const entry = {
|
||||
id: 1,
|
||||
localizations: [{ id: 2 }],
|
||||
};
|
||||
|
||||
const defaultService = {
|
||||
create: jest.fn(() => Promise.resolve(entry)),
|
||||
};
|
||||
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { data: { title: 'title ' } };
|
||||
const output = await service.create(input, { model: 'non-localized-model' });
|
||||
|
||||
expect(defaultService.create).toHaveBeenCalledWith(input, { model: 'non-localized-model' });
|
||||
expect(syncLocalizations).not.toHaveBeenCalled();
|
||||
expect(output).toStrictEqual(entry);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
test('Calls original update', async () => {
|
||||
const entry = {
|
||||
id: 1,
|
||||
};
|
||||
|
||||
const defaultService = {
|
||||
update: jest.fn(() => Promise.resolve(entry)),
|
||||
};
|
||||
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { params: { id: 1 }, data: { title: 'title ' } };
|
||||
await service.update(input, { model: 'test-model' });
|
||||
|
||||
expect(defaultService.update).toHaveBeenCalledWith(input, { model: 'test-model' });
|
||||
});
|
||||
|
||||
test('Calls updateNonLocalizedFields if model is localized', async () => {
|
||||
const entry = {
|
||||
id: 1,
|
||||
localizations: [{ id: 2 }],
|
||||
};
|
||||
|
||||
const defaultService = {
|
||||
update: jest.fn(() => Promise.resolve(entry)),
|
||||
};
|
||||
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { params: { id: 1 }, data: { title: 'title ' } };
|
||||
const output = await service.update(input, { model: 'test-model' });
|
||||
|
||||
expect(defaultService.update).toHaveBeenCalledWith(input, { model: 'test-model' });
|
||||
expect(updateNonLocalizedFields).toHaveBeenCalledWith(entry, { model });
|
||||
expect(output).toStrictEqual(entry);
|
||||
});
|
||||
|
||||
test('Skip processing if model is not localized', async () => {
|
||||
const entry = {
|
||||
id: 1,
|
||||
localizations: [{ id: 2 }],
|
||||
};
|
||||
|
||||
const defaultService = {
|
||||
update: jest.fn(() => Promise.resolve(entry)),
|
||||
};
|
||||
|
||||
const service = decorator(defaultService);
|
||||
|
||||
const input = { params: { id: 1 }, data: { title: 'title ' } };
|
||||
await service.update(input, { model: 'non-localized-model' });
|
||||
|
||||
expect(defaultService.update).toHaveBeenCalledWith(input, { model: 'non-localized-model' });
|
||||
expect(updateNonLocalizedFields).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,20 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const { getService } = require('../utils');
|
||||
const { has, omit } = require('lodash/fp');
|
||||
|
||||
module.exports = {
|
||||
decorate() {
|
||||
strapi.entityService.decorate(entityServiceDecorator);
|
||||
},
|
||||
const { getDefaultLocale } = require('./locales');
|
||||
const { isLocalized } = require('./content-types');
|
||||
const { syncLocalizations, updateNonLocalizedFields } = require('./localizations');
|
||||
|
||||
const LOCALE_QUERY_FILTER = '_locale';
|
||||
|
||||
/**
|
||||
* Adds default locale or replaces _locale by locale in query params
|
||||
* @param {object} params - query params
|
||||
*/
|
||||
const wrapParams = async (params = {}) => {
|
||||
if (has(LOCALE_QUERY_FILTER, params)) {
|
||||
return {
|
||||
...omit(LOCALE_QUERY_FILTER, params),
|
||||
locale: params[LOCALE_QUERY_FILTER],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...params,
|
||||
locale: await getDefaultLocale(),
|
||||
};
|
||||
};
|
||||
|
||||
const entityServiceDecorator = service => ({
|
||||
async create(params, ctx) {
|
||||
/**
|
||||
* Decorates the entity service with I18N business logic
|
||||
* @param {object} service - entity service
|
||||
*/
|
||||
const decorator = service => ({
|
||||
/**
|
||||
* Wraps query options. In particular will add default locale to query params
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async wrapOptions(opts = {}, ctx) {
|
||||
const wrappedOptions = await service.wrapOptions(opts, ctx);
|
||||
const model = strapi.db.getModel(ctx.model);
|
||||
const entry = await service.create(params, ctx);
|
||||
|
||||
if (getService('content-types').isLocalized(model)) {
|
||||
await getService('localizations').syncLocalizations(entry, { model });
|
||||
if (!isLocalized(model)) {
|
||||
return wrappedOptions;
|
||||
}
|
||||
|
||||
return {
|
||||
...wrappedOptions,
|
||||
params: await wrapParams(wrappedOptions.params),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an entry & make links between it and its related localizaionts
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async create(opts, ctx) {
|
||||
const model = strapi.db.getModel(ctx.model);
|
||||
const entry = await service.create(opts, ctx);
|
||||
|
||||
if (isLocalized(model)) {
|
||||
await syncLocalizations(entry, { model });
|
||||
}
|
||||
|
||||
return entry;
|
||||
@ -22,17 +70,23 @@ const entityServiceDecorator = service => ({
|
||||
|
||||
/**
|
||||
* Updates an entry & update related localizations fields
|
||||
* @param {obj} params - query params
|
||||
* @param {obj} ctx - query context (model)
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async update(params, ctx) {
|
||||
async update(opts, ctx) {
|
||||
const model = strapi.db.getModel(ctx.model);
|
||||
const entry = await service.update(params, ctx);
|
||||
const entry = await service.update(opts, ctx);
|
||||
|
||||
if (getService('content-types').isLocalized(model)) {
|
||||
await getService('localizations').updateNonLocalizedFields(entry, { model });
|
||||
if (isLocalized(model)) {
|
||||
await updateNonLocalizedFields(entry, { model });
|
||||
}
|
||||
|
||||
return entry;
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
decorator,
|
||||
wrapParams,
|
||||
};
|
||||
|
@ -43,11 +43,25 @@ const createDefaultImplementation = ({ db, eventHub, entityValidator }) => ({
|
||||
uploadFiles,
|
||||
|
||||
/**
|
||||
* Promise to fetch all records
|
||||
*
|
||||
* @return {Promise}
|
||||
* Returns default opt
|
||||
* it is async so decorators can do async processing
|
||||
* @param {object} params - query params to extend
|
||||
* @param {object=} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async find({ params, populate }, { model }) {
|
||||
async wrapOptions(options = {}) {
|
||||
return options;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of entries
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async find(opts, { model }) {
|
||||
const { params, populate } = await this.wrapOptions(opts, { model });
|
||||
|
||||
const { kind } = db.getModel(model);
|
||||
|
||||
// return first element and ignore filters
|
||||
@ -59,41 +73,63 @@ const createDefaultImplementation = ({ db, eventHub, entityValidator }) => ({
|
||||
return db.query(model).find(params, populate);
|
||||
},
|
||||
|
||||
findPage({ params, populate }, { model }) {
|
||||
/**
|
||||
* Returns a paginated list of entries
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async findPage(opts, { model }) {
|
||||
const { params, populate } = await this.wrapOptions(opts, { model });
|
||||
|
||||
return db.query(model).findPage(params, populate);
|
||||
},
|
||||
|
||||
findWithRelationCounts({ params, populate }, { model }) {
|
||||
/**
|
||||
* Returns a list of entries with relation counters
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async findWithRelationCounts(opts, { model }) {
|
||||
const { params, populate } = await this.wrapOptions(opts, { model });
|
||||
|
||||
return db.query(model).findWithRelationCounts(params, populate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to fetch record
|
||||
*
|
||||
* @return {Promise}
|
||||
* Returns one entry
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async findOne(opts, { model }) {
|
||||
const { params, populate } = await this.wrapOptions(opts, { model });
|
||||
|
||||
findOne({ params, populate }, { model }) {
|
||||
return db.query(model).findOne(params, populate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to count record
|
||||
*
|
||||
* @return {Promise}
|
||||
* Returns a count of entries
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async count(opts, { model }) {
|
||||
const { params } = await this.wrapOptions(opts, { model });
|
||||
|
||||
count({ params }, { model }) {
|
||||
return db.query(model).count(params);
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to add record
|
||||
*
|
||||
* @return {Promise}
|
||||
* Creates & returns a new entry
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async create(opts, { model }) {
|
||||
const { data, files } = await this.wrapOptions(opts, { model });
|
||||
|
||||
async create({ data, files }, { model }) {
|
||||
const modelDef = db.getModel(model);
|
||||
|
||||
if (modelDef.kind === 'singleType') {
|
||||
@ -124,12 +160,14 @@ const createDefaultImplementation = ({ db, eventHub, entityValidator }) => ({
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to edit record
|
||||
*
|
||||
* @return {Promise}
|
||||
* Updates & returns an existing entry
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async update(opts, { model }) {
|
||||
const { params, data, files } = await this.wrapOptions(opts, { model });
|
||||
|
||||
async update({ params, data, files }, { model }) {
|
||||
const modelDef = db.getModel(model);
|
||||
const existingEntry = await db.query(model).findOne(params);
|
||||
|
||||
@ -155,12 +193,14 @@ const createDefaultImplementation = ({ db, eventHub, entityValidator }) => ({
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to delete a record
|
||||
*
|
||||
* @return {Promise}
|
||||
* Deletes & returns the entry that was deleted
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async delete(opts, { model }) {
|
||||
const { params } = await this.wrapOptions(opts, { model });
|
||||
|
||||
async delete({ params }, { model }) {
|
||||
const entry = await db.query(model).delete(params);
|
||||
|
||||
const modelDef = db.getModel(model);
|
||||
@ -173,29 +213,50 @@ const createDefaultImplementation = ({ db, eventHub, entityValidator }) => ({
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to search records
|
||||
*
|
||||
* @return {Promise}
|
||||
* Returns a list of matching entries
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async search(opts, { model }) {
|
||||
const { params, populate } = await this.wrapOptions(opts, { model });
|
||||
|
||||
search({ params, populate }, { model }) {
|
||||
return db.query(model).search(params, populate);
|
||||
},
|
||||
|
||||
searchWithRelationCounts({ params, populate }, { model }) {
|
||||
/**
|
||||
* Returns a list of matching entries with relations counters
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async searchWithRelationCounts(opts, { model }) {
|
||||
const { params, populate } = await this.wrapOptions(opts, { model });
|
||||
|
||||
return db.query(model).searchWithRelationCounts(params, populate);
|
||||
},
|
||||
|
||||
searchPage({ params, populate }, { model }) {
|
||||
/**
|
||||
* Returns a paginated list of matching entries
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
async searchPage(opts, { model }) {
|
||||
const { params, populate } = await this.wrapOptions(opts, { model });
|
||||
|
||||
return db.query(model).searchPage(params, populate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Promise to count searched records
|
||||
*
|
||||
* @return {Promise}
|
||||
* @param {object} opts - Query options object (params, data, files, populate)
|
||||
* @param {object} ctx - Query context
|
||||
* @param {object} ctx.model - Model that is being used
|
||||
*/
|
||||
countSearch({ params }, { model }) {
|
||||
async countSearch(opts, { model }) {
|
||||
const { params } = await this.wrapOptions(opts, { model });
|
||||
|
||||
return db.query(model).countSearch(params);
|
||||
},
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user