Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions api/src/controllers/exports.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@
import { ApiOAuth2, ApiTags, ApiOperation, ApiProduces, ApiResponse } from '@nestjs/swagger';
import { androidXmlExporter } from '../formatters/android-xml';
import { resXExporter } from '../formatters/resx';
import { merge } from 'lodash';
import { resolveColumnName } from '../utils/alias-helper';
import { getLexicalOrderClause } from '../utils/database-type-helper';

@Controller('api/v1/projects/:projectId/exports')
export class ExportsController {

Check warning on line 29 in api/src/controllers/exports.controller.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark these members as `readonly`.

See more on https://sonarcloud.io/project/issues?id=ever-co_ever-traduora&issues=AZ_CU75DCO3a6BsbPOnd&open=AZ_CU75DCO3a6BsbPOnd&pullRequest=552
constructor(
private auth: AuthorizationService,
@InjectRepository(Term) private termRepo: Repository<Term>,
Expand Down Expand Up @@ -129,9 +128,17 @@
translation: t.translations.length === 1 ? t.translations[0].value : '',
}));

// Merge by term. The requested locale only contains the translated
// terms at this point, so a positional merge would pair unrelated
// terms and drop entries (#420).
const translated = new Map(data.translations.map(t => [t.term, t.translation]));

const dataWithFallback: IntermediateTranslationFormat = {
iso: query.locale,
translations: merge(fallbackTermsWithTranslationsMapped, data.translations),
translations: fallbackTermsWithTranslationsMapped.map(t => ({
term: t.term,
translation: translated.get(t.term) ?? t.translation,
})),
};

serialized = await this.dump(query.format, dataWithFallback);
Expand Down
39 changes: 39 additions & 0 deletions api/test/export.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,45 @@ describe('ExportController (e2e)', () => {
});
});

it('/api/v1/projects/:projectId/exports (GET) should fill untranslated terms from the fallback locale', async () => {
const authorized = (req: request.Test) => req.set('Authorization', `Bearer ${testingUser.accessToken}`);

const createTerm = async (value: string): Promise<string> => {
const res = await authorized(request(app.getHttpServer()).post(`/api/v1/projects/${testProject.id}/terms`))
.send({ value })
.expect(201);
return res.body.data.id;
};

const translate = (localeCode: string, termId: string, value: string) =>
authorized(request(app.getHttpServer()).patch(`/api/v1/projects/${testProject.id}/translations/${localeCode}`))
.send({ termId, value })
.expect(200);

const exportLocale = async (params: string): Promise<any> => {
const res = await authorized(request(app.getHttpServer()).get(`/api/v1/projects/${testProject.id}/exports?${params}`)).expect(200);
return JSON.parse(Buffer.from(res.body).toString('utf-8'));
};

const nestedTermId = await createTerm('group.term');
await authorized(request(app.getHttpServer()).post(`/api/v1/projects/${testProject.id}/translations`))
.send({ code: 'nl' })
.expect(201);
await translate('nl', termTwoId, 'twee');
await translate('de_DE', nestedTermId, 'gruppe');

const flat = await exportLocale('locale=nl&format=jsonflat&fallbackLocale=de_DE');
expect(Object.keys(flat)).toEqual(['group.term', 'term.one', 'term.two']);
expect(flat['group.term']).toEqual('gruppe');
expect(flat['term.one']).toEqual('eins');
expect(flat['term.two']).toEqual('twee');

const nested = await exportLocale('locale=nl&format=jsonnested&fallbackLocale=de_DE');
expect(nested.group.term).toEqual('gruppe');
expect(nested.term.one).toEqual('eins');
expect(nested.term.two).toEqual('twee');
});

Comment thread
coderabbitai[bot] marked this conversation as resolved.
it('/api/v1/projects/:projectId/exports (GET) should export terms in lexical order', async () => {
const input = ['app.login', 'should be before base terms', '1 goes first', 'app.logout', 'app.exit', 'menu.start', 'a term', '2 goes second'];

Expand Down