Skip to content

Commit

Permalink
Merge branch 'development' into 248i-cors-imgs
Browse files Browse the repository at this point in the history
  • Loading branch information
konzz authored Jan 31, 2025
2 parents e887cb2 + 7aa47b2 commit 5280aab
Show file tree
Hide file tree
Showing 155 changed files with 3,127 additions and 2,300 deletions.
7 changes: 3 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ updates:
- dependency-name: flowbite-react #Flowbite & flowbite-datepicker upgrade #6993
versions: ['>= 0.10.1']
- dependency-name: Mongoose #Mongoose upgrade #7017
versions: ['>= 8.4.3']
- dependency-name: cookie
- dependency-name: '@socket.io*'
- dependency-name: bootstrap #we have decided to dont update bootstrap #7489
versions: [">= 8.4.3"]
- dependency-name: "@socket.io*"
- dependency-name: bootstrap
- dependency-name: express-prom-bundle
- dependency-name: immutable
- dependency-name: otplib
Expand Down
1 change: 1 addition & 0 deletions app/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default (app, server) => {
require('./files/ocrRoutes').ocrRoutes(app);
require('./settings/routes').default(app);
require('./i18n/routes').default(app);
require('./i18n.v2/routes').translationsRoutes(app);
require('./sync/routes').default(app);
require('./tasks/routes').default(app);
require('./usergroups/routes').default(app);
Expand Down
54 changes: 54 additions & 0 deletions app/api/i18n.v2/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Application, Request } from 'express';
import { needsAuthorization } from 'api/auth';
import { validation } from 'api/utils';
import translations from 'api/i18n';
import { getTranslationsEntriesV2 } from 'api/i18n/v2_support';

const translationsRoutes = (app: Application) => {
app.get('/api/v2/translations', async (_req: Request, res) => {
const translationsV2 = await getTranslationsEntriesV2();
const translationList = await translationsV2.all();
res.json(translationList);
});

app.post(
'/api/v2/translations',
needsAuthorization(),
validation.validateRequest({
type: 'object',
properties: {
body: {
type: 'array',
items: {
type: 'object',
properties: {
_id: { type: 'string' },
language: { type: 'string' },
key: { type: 'string' },
value: { type: 'string' },
context: {
type: 'object',
properties: {
id: { type: 'string' },
label: { type: 'string' },
type: { type: 'string' },
},
required: ['id', 'label', 'type'],
},
},
required: ['language', 'key', 'value', 'context'],
},
},
},
required: ['body'],
}),
async (req, res) => {
await translations.v2StructureSave(req.body);
req.sockets.emitToCurrentTenant('translationKeysChange', req.body);
res.status(200);
res.json({ success: true });
}
);
};

export { translationsRoutes };
102 changes: 102 additions & 0 deletions app/api/i18n.v2/routes/specs/routes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'isomorphic-fetch';
import request from 'supertest';

import { TranslationDBO } from 'api/i18n.v2/schemas/TranslationDBO';
import { getFixturesFactory } from 'api/utils/fixturesFactory';
import { testingEnvironment } from 'api/utils/testingEnvironment';
import { TestEmitSources, iosocket, setUpApp } from 'api/utils/testingRoutes';
import { UserRole } from 'shared/types/userSchema';
import { translationsRoutes } from '..';

describe('i18n translations V2 routes', () => {
const createTranslationDBO = getFixturesFactory().v2.database.translationDBO;
const app = setUpApp(translationsRoutes, (req, _res, next) => {
req.user = {
username: 'admin',
role: UserRole.ADMIN,
email: '[email protected]',
};
// @ts-ignore
req.file = { path: 'filder/filename.ext' };
next();
});

beforeEach(async () => {
const translationsV2: TranslationDBO[] = [
createTranslationDBO('Search', 'Buscar', 'es', {
id: 'System',
type: 'Entity',
label: 'User Interface',
}),
createTranslationDBO('Search', 'Search', 'en', {
id: 'System',
type: 'Uwazi UI',
label: 'User Interface',
}),
];
await testingEnvironment.setUp(
{
settings: [
{
languages: [
{ key: 'en', label: 'English', default: true },
{ key: 'es', label: 'Spanish', default: false },
],
},
],
translationsV2,
},
'index_i18n_v2_routes'
);
});

afterEach(() => {
iosocket.emit.mockReset();
});

afterAll(async () => {
await testingEnvironment.tearDown();
});

describe('/api/v2/translations', () => {
it('should update the translations and emit translationKeysChange event', async () => {
const response = await request(app)
.post('/api/v2/translations')
.send([
{
language: 'es',
key: 'Search',
value: 'Búsqueda',
context: {
id: 'System',
label: 'User Interface',
type: 'Uwazi UI',
},
},
]);
expect(response.status).toEqual(200);
expect(iosocket.emit).toHaveBeenCalledWith(
'translationKeysChange',
TestEmitSources.currentTenant,
[
{
context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' },
key: 'Search',
language: 'es',
value: 'Búsqueda',
},
]
);
});

it('should handle invalid POST request payload', async () => {
const response = await request(app)
.post('/api/v2/translations')
.send({ invalidKey: 'value' }); // Invalid payload
expect(response.status).toBe(400);
expect(response.body).toEqual(
expect.objectContaining({ prettyMessage: 'validation failed' })
);
});
});
});
2 changes: 2 additions & 0 deletions app/api/i18n/routes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
import { createError, validation } from 'api/utils';
import settings from 'api/settings';
import entities from 'api/entities';
Expand Down Expand Up @@ -59,6 +60,7 @@ async function deleteLanguage(key: LanguageISO6391, req: Request) {

type TranslationsRequest = Request & { query: { context: string } };

// eslint-disable-next-line max-statements
export default (app: Application) => {
app.get(
'/api/translations',
Expand Down
22 changes: 22 additions & 0 deletions app/api/i18n/specs/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ const fixtures: DBFixture = {
{
_id: entityTemplateId,
type: 'template',
properties: [
{
type: 'select',
name: 'Dictionary',
content: dictionaryId.toString(),
},
],
},
{
_id: documentTemplateId,
Expand All @@ -214,6 +221,21 @@ const fixtures: DBFixture = {
published: false,
metadata: {},
},
{
language: 'es',
sharedId: 'entity1',
title: '1',
template: entityTemplateId,
published: false,
metadata: {
Dictionary: [
{
value: '1',
label: 'Password',
},
],
},
},
],
pages: [
{
Expand Down
34 changes: 33 additions & 1 deletion app/api/i18n/specs/translations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import thesauri from 'api/thesauri/thesauri.js';
import { ContextType } from 'shared/translationSchema';
// eslint-disable-next-line node/no-restricted-import
import * as fs from 'fs';
import { TranslationSyO } from 'api/i18n.v2/schemas/TranslationSyO';
import { UITranslationNotAvailable } from '../defaultTranslations';
import translations from '../translations';
import fixtures, { dictionaryId } from './fixtures';
import { sortByLocale } from './sortByLocale';
import { addLanguage } from '../routes';
import { getTranslationsV2ByContext } from '../v2_support';

describe('translations', () => {
beforeEach(async () => {
Expand All @@ -29,7 +31,7 @@ describe('translations', () => {
expect(result).toMatchObject({
contexts: [
{
type: 'Thesaurus',
type: 'Thesaurus' as 'Thesaurus',
values: {
Account: 'Account',
Age: 'Age',
Expand All @@ -56,6 +58,36 @@ describe('translations', () => {
});
});

describe('v2StructureSave', () => {
it('should save changed translations and propagate the changes', async () => {
const initialTranslations = await getTranslationsV2ByContext(dictionaryId.toString());
const initialEntity = (await entities.get({ language: 'es', sharedId: 'entity1' }))[0];
const translationsToSave = [
{
_id: '1',
language: initialTranslations[0].locale!,
key: 'Password',
value: 'Changed Password ES',
context: {
id: dictionaryId.toString(),
type: 'Thesaurus' as TranslationSyO['context']['type'],
label: '',
},
},
];

await translations.v2StructureSave(translationsToSave);
const updatedTranslations = await getTranslationsV2ByContext(dictionaryId.toString());
initialTranslations![0]!.contexts![0]!.values!.find(v => v.key === 'Password')!.value =
'Changed Password ES';
expect(updatedTranslations).toEqual(initialTranslations);

const updatedEntity = (await entities.get({ language: 'es', sharedId: 'entity1' }))[0];
initialEntity.metadata.Dictionary[0].label = 'Changed Password ES';
expect(updatedEntity).toEqual(initialEntity);
});
});

describe('save()', () => {
it('should save the translation and return it', async () => {
const result = await translations.save({ locale: 'fr' });
Expand Down
Loading

0 comments on commit 5280aab

Please sign in to comment.