Skip to content

Commit

Permalink
feat(js sdk): Remove key structuring TG-333 (#451)
Browse files Browse the repository at this point in the history
  • Loading branch information
JanCizmar authored Sep 30, 2021
1 parent 07208b1 commit 710b148
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 70 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/TolgeeConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Mode, Translations } from './types';
import { Mode, TreeTranslationsData } from './types';
import { NodeHelper } from './helpers/NodeHelper';
import { ModifierKey } from './Constants/ModifierKey';

Expand Down Expand Up @@ -36,7 +36,7 @@ export class TolgeeConfig {
highlightColor?: string = 'rgb(224 240 255)';
/** localization data to use in production mode */
staticData?: {
[key: string]: Translations | (() => Promise<Translations>);
[key: string]: TreeTranslationsData | (() => Promise<TreeTranslationsData>);
};

/**
Expand Down
25 changes: 7 additions & 18 deletions packages/core/src/services/TranslationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ jest.dontMock('../Errors/ApiHttpError');
jest.dontMock('./DependencyStore');

import { TranslationService } from './TranslationService';
import { Translations } from '../types';
import { getMockedInstance } from '@testFixtures/mocked';
import { ApiHttpService } from './ApiHttpService';
import { Properties } from '../Properties';
Expand Down Expand Up @@ -95,7 +94,7 @@ describe('TranslationService', () => {

test('will get proper translation containing .', async () => {
expect(
await translationService.getTranslation('translation\\.with\\.dots')
await translationService.getTranslation('translation.with.dots')
).toEqual('Translation with dots');
});

Expand All @@ -109,13 +108,7 @@ describe('TranslationService', () => {

test('will get proper translation on strange key', async () => {
expect(
await translationService.getTranslation('key with: \\\\\\\\.t')
).toEqual('Key with strange escapes');
});

test('will get proper translation from cache', async () => {
expect(
await translationService.getTranslation('key with: \\\\\\\\.t')
await translationService.getTranslation('key with: \\\\.t')
).toEqual('Key with strange escapes');
});

Expand Down Expand Up @@ -250,7 +243,7 @@ describe('TranslationService', () => {
test('will use fallback language on missing translation', async () => {
getMockedInstance(Properties).config.fallbackLanguage = 'en';
expect(
await translationService.getTranslation('translation\\.with\\.dots', 'de')
await translationService.getTranslation('translation.with.dots', 'de')
).toEqual('Translation with dots');
});

Expand All @@ -263,12 +256,8 @@ describe('TranslationService', () => {

test('getFromCacheOrCallback will return fallback when message is empty string', async () => {
getMockedInstance(Properties).config.fallbackLanguage = 'en';
const cacheMock = ((translationService as any).translationsCache = new Map<
string,
Translations
>());
cacheMock.set('en', mockedTranslations.en);
cacheMock.set('de', mockedTranslations.de);
(translationService as any).setLanguageData('en', mockedTranslations.en);
(translationService as any).setLanguageData('de', mockedTranslations.de);
expect(
await translationService.getFromCacheOrFallback('just_en', 'de')
).toEqual('Just en.');
Expand All @@ -295,10 +284,10 @@ describe('TranslationService', () => {
).toEqual('');
});

test('will return last chunk of key path when no translation found', async () => {
test('will return key when no translation found', async () => {
expect(
await translationService.getTranslation('test\\.key.this\\.is\\.it', 'en')
).toEqual('this.is.it');
).toEqual('test\\.key.this\\.is\\.it');
});

test('returns default when provided', async () => {
Expand Down
84 changes: 36 additions & 48 deletions packages/core/src/services/TranslationService.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Translations } from '../types';
import { Translations, TreeTranslationsData } from '../types';
import { TranslationData } from '../DTOs/TranslationData';
import { Properties } from '../Properties';
import { CoreService } from './CoreService';
import { ApiHttpService } from './ApiHttpService';
import { TextHelper } from '../helpers/TextHelper';
import { ApiHttpError } from '../Errors/ApiHttpError';
import { EventService } from './EventService';
import { EventEmitterImpl } from './EventEmitter';
Expand Down Expand Up @@ -40,8 +39,7 @@ export class TranslationService {
return '';
}

const path = TextHelper.splitOnNonEscapedDelimiter(key, '.');
return path[path.length - 1];
return key;
}

initStatic() {
Expand All @@ -53,7 +51,7 @@ export class TranslationService {
([language, data]) => {
//if not provider or promise then it is raw data
if (typeof data !== 'function') {
this.translationsCache.set(language, data);
this.setLanguageData(language, data);
}
}
);
Expand Down Expand Up @@ -119,24 +117,9 @@ export class TranslationService {
);

Object.keys(translationData.translations).forEach((lang) => {
if (this.translationsCache.get(lang)) {
// if the language is not loaded, then ignore the change
const path = TextHelper.splitOnNonEscapedDelimiter(
translationData.key,
'.'
);
let root: string | Translations = this.translationsCache.get(lang);
for (let i = 0; i < path.length; i++) {
const item = path[i];
if (root[item] === undefined) {
root[item] = {};
}
if (i === path.length - 1) {
root[item] = translationData.translations[lang];
return;
}
root = root[item];
}
const data = this.translationsCache.get(lang);
if (data) {
data[translationData.key] = translationData.translations[lang];
}
});
return result;
Expand Down Expand Up @@ -196,11 +179,8 @@ export class TranslationService {
);

const firstItem = data._embedded?.keys?.[0];

const fetchedData = firstItem?.translations;

if (fetchedData) {
Object.entries(data._embedded?.keys?.[0]?.translations).forEach(
if (firstItem?.translations) {
Object.entries(firstItem.translations).forEach(
([language, translation]) =>
(translationData[language] = (translation as any).text)
);
Expand All @@ -213,7 +193,8 @@ export class TranslationService {
e.response.status === 404 &&
e.code === 'language_not_found'
) {
//only possible reason for this error is, that languages definition is changed, but the old value is stored in preferred languages
// only possible reason for this error is, that languages definition
// is changed, but the old value is stored in preferred languages
this.properties.preferredLanguages =
await this.coreService.getLanguages();
// eslint-disable-next-line no-console
Expand All @@ -237,10 +218,10 @@ export class TranslationService {

if (typeof langStaticData === 'function') {
const data = await langStaticData();
this.translationsCache.set(language, data);
this.setLanguageData(language, data);
return;
} else if (langStaticData !== undefined) {
this.translationsCache.set(language, langStaticData);
this.setLanguageData(language, langStaticData);
return;
}

Expand All @@ -255,12 +236,12 @@ export class TranslationService {
console.error(
'Server responded with error status while loading localization data.'
);
this.translationsCache.set(language, {});
this.setLanguageData(language, {});
return;
}
try {
const data = await result.json();
this.translationsCache.set(language, data);
this.setLanguageData(language, data);
} catch (e) {
// eslint-disable-next-line no-console
console.error(`Error parsing json retrieved from ${url}.`);
Expand All @@ -279,7 +260,7 @@ export class TranslationService {
const data = await this.apiHttpService.fetchJson(
`v2/projects/translations/${language}`
);
this.translationsCache.set(language, data[language] || {});
this.setLanguageData(language, data[language] || {});
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error while fetching localization data from API.', e);
Expand All @@ -292,29 +273,36 @@ export class TranslationService {
this.translationsCache.set(language, {});
}

private setLanguageData(language: string, data: TreeTranslationsData) {
// recursively walk the tree and make it flat, when tree data are provided
const makeFlat = (data: TreeTranslationsData): Record<string, string> => {
const result: Record<string, string> = {};
Object.entries(data).forEach(([key, value]) => {
if (typeof value === 'object') {
Object.entries(makeFlat(value)).forEach(([flatKey, flatValue]) => {
result[key + '.' + flatKey] = flatValue;
});
return;
}
result[key] = value;
});
return result;
};

this.translationsCache.set(language, makeFlat(data));
}

private getFromCache(
key: string,
lang: string = this.properties.currentLanguage
): string {
const path = TextHelper.splitOnNonEscapedDelimiter(key, '.');
let root: string | Translations = this.translationsCache.get(lang);
const root: string | Translations = this.translationsCache.get(lang);

//if lang is not downloaded or does not exist at all
if (root === undefined) {
return undefined;
}

//if data contains key directly, just return it
if (typeof root[key] === 'string') {
return root[key] as string;
}

for (const item of path) {
if (root[item] === undefined) {
return undefined;
}
root = root[item];
}
return root as string;
return root[key] as string;
}
}
6 changes: 5 additions & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export interface TextInputElementData {
placeholderInputs: string[];
}

export type Translations = { [key: string]: string | Translations };
export type TreeTranslationsData = {
[key: string]: string | TreeTranslationsData;
};

export type Translations = Record<string, string>;
export type TranslationParams = { [key: string]: string | number | bigint };

export type TranslateProps = {
Expand Down
2 changes: 1 addition & 1 deletion packages/react/test/integration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('integration', () => {
});

test('Will not translate string, what is not found', async () => {
await waitFor(() => screen.getByText('not_found'));
await waitFor(() => screen.getByText('sampleApp.not_found'));
});
});
});

0 comments on commit 710b148

Please sign in to comment.