Skip to content

Commit

Permalink
fix: Fix useTranslate types, react warnings, core observer edge case …
Browse files Browse the repository at this point in the history
…error
  • Loading branch information
stepan662 committed May 25, 2022
1 parent 4e9c31c commit 1295d36
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 107 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/Tolgee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class Tolgee {
}

/**
* Is emitted when language is changed and loaded (including initial load)
* Is emitted when language is loaded for the first time
*/
public get onLangLoaded() {
return this.dependencyService.eventService.LANGUAGE_LOADED;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/services/TranslationService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ describe('TranslationService', () => {
await translationService.loadTranslations('de');
await translationService.loadTranslations('en');

expect(languageLoadedEmitMock).toBeCalledTimes(3);
expect(languageLoadedEmitMock).toBeCalledTimes(2);
expect(languageLoadedEmitMock).toHaveBeenNthCalledWith(1, 'en');
expect(languageLoadedEmitMock).toHaveBeenNthCalledWith(2, 'de');
});
Expand Down
16 changes: 13 additions & 3 deletions packages/core/src/services/TranslationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ export class TranslationService {
this.fetchPromises[lang] = this.fetchTranslations(lang);
}
await this.fetchPromises[lang];
(this.eventService.LANGUAGE_LOADED as EventEmitterImpl<string>).emit(
lang
);
}
(this.eventService.LANGUAGE_LOADED as EventEmitterImpl<string>).emit(lang);
this.fetchPromises[lang] = undefined;
return this.translationsCache.get(lang);
}
Expand Down Expand Up @@ -230,9 +232,13 @@ export class TranslationService {
lang: string = this.properties.currentLanguage,
defaultValue?: string
): string {
const fallbackLang = this.properties.config.fallbackLanguage;
const message =
this.getFromCache(key, lang) ||
this.getFromCache(key, this.properties.config.fallbackLanguage);
this.getFromCache(key, lang) || this.getFromCache(key, fallbackLang);

if (!message && (!this.isLoaded(lang) || !this.isLoaded(fallbackLang))) {
return undefined;
}
return TranslationService.translationByValue(message, defaultValue);
}

Expand Down Expand Up @@ -297,6 +303,10 @@ export class TranslationService {
return (isDevMode && !devFetched) || !dataPresent;
}

private isLoaded(lang: string) {
return this.translationsCache.get(lang) !== undefined;
}

private async fetchTranslations(lang: string) {
const isDevMode = this.properties.mode === 'development';
if (isDevMode) {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/wrappers/NodeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export class NodeHandler {
const restrictedElements = this.properties.config.restrictedElements;
return nodes.filter((n) => {
const e = NodeHelper.closestElement(n);
if (!e) {
return false;
}
return (
restrictedElements.indexOf(e.tagName.toLowerCase()) === -1 &&
e.closest(`[${RESTRICTED_ASCENDANT_ATTRIBUTE}="true"]`) === null
Expand Down
6 changes: 4 additions & 2 deletions packages/react/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { TranslationParamsTags } from '@tolgee/core';
import { TranslationParams } from '@tolgee/core';
import React from 'react';

export type ParamsTags = {
[key: string]:
| TranslationParamsTags<React.ReactNode>['a']
| TranslationParams['a']
| ((value: any) => JSX.Element | React.ReactElement | null)
| React.ReactElement;
};
8 changes: 2 additions & 6 deletions packages/react/src/useTranslate.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,18 @@ describe('useTranslate hook', function () {
defaultValue: undefined,
key: 'hello',
noWrap: undefined,
orEmpty: true,
params: undefined,
});
expect(instantMock).toHaveBeenCalledWith({
defaultValue: undefined,
key: 'hello2',
noWrap: undefined,
orEmpty: true,
params: { name: 'test' },
});
expect(instantMock).toHaveBeenCalledWith({
defaultValue: undefined,
defaultValue: 'Default',
key: 'hello3',
noWrap: true,
orEmpty: true,
params: undefined,
});
});
Expand Down Expand Up @@ -209,10 +206,9 @@ describe('useTranslate hook', function () {

test('calls instant function with proper params', async () => {
expect(instantMock).toHaveBeenCalledWith({
defaultValue: undefined,
defaultValue: 'Default',
key: 'hello',
noWrap: false,
orEmpty: true,
params: { name: 'test' },
});
});
Expand Down
25 changes: 14 additions & 11 deletions packages/react/src/useTranslate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,29 @@ type ReturnFnType = {

export const useTranslate: () => ReturnFnType = () => {
const { tolgee } = useTolgeeContext();
const isMounted = useRef(false);

// dummy state to enable re-rendering
const [instance, setInstance] = useState(0);

const forceRerender = () => {
setInstance((v) => v + 1);
};
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);

const forceRerender = useCallback(() => {
if (isMounted.current) {
setInstance((v) => v + 1);
}
}, [setInstance, isMounted]);

// cache of translations translated with this useTranslate
const keysRef = useRef<string[]>([]);
const keysReadyRef = useRef<string[]>([]);

const resetMemory = (key?: string) => {
keysRef.current = key ? keysRef.current.filter((k) => k !== key) : [];
keysReadyRef.current = key
? keysReadyRef.current.filter((k) => k !== key)
: [];
};

useEffect(() => {
Expand Down Expand Up @@ -89,13 +95,11 @@ export const useTranslate: () => ReturnFnType = () => {
defaultValue?: string
) => {
const firstRender = !keysRef.current.includes(key);
const ready = keysReadyRef.current.includes(key);
const translation = tolgee.instant({
key,
params: wrapTagHandlers(params),
noWrap,
defaultValue: !ready ? undefined : defaultValue,
orEmpty: !ready,
defaultValue: defaultValue,
});

if (firstRender) {
Expand All @@ -108,7 +112,6 @@ export const useTranslate: () => ReturnFnType = () => {
defaultValue,
})
.then((value) => {
keysReadyRef.current.push(key);
if (value !== translation) {
forceRerender();
}
Expand Down
81 changes: 12 additions & 69 deletions packages/vue/src/T.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,30 @@
import { defineComponent, PropType } from 'vue';
import { Subscription } from '@tolgee/core/lib/services/Subscription';
import { TolgeeContext } from './types';
import { useTranslate } from './useTranslate';
import { TranslateFnProps } from '.';

export const T = defineComponent({
name: 'T',
props: {
keyName: { type: String, required: true },
parameters: Object as PropType<{ [key: string]: string }>,
defaultValue: String as PropType<string>,
/** @deprecated */
strategy: {
type: String as PropType<'ELEMENT_WRAP' | 'NO_WRAP'>,
default: 'ELEMENT_WRAP',
},
noWrap: {
type: Boolean,
default: false,
},
},
inject: ['tolgeeContext'],
data() {
const tolgeeContext = this.tolgeeContext as unknown as TolgeeContext;
if (!tolgeeContext) {
throw new Error('T component used outside of TolgeeProvider');
}
return {
translation:
(this.$props.keyName &&
tolgeeContext.tolgee.instant({
key: this.$props.keyName || '',
noWrap: this.$props.noWrap,
params: this.$props.parameters,
orEmpty: true,
})) ||
('' as string),
translationSubscription: null as Subscription | null,
langSubscription: null as Subscription | null,
};
},
methods: {
translate() {
const tolgeeContext = this.tolgeeContext as TolgeeContext;

tolgeeContext.tolgee
.translate({
key: this.$props.keyName || '',
noWrap: this.$props.noWrap,
params: this.$props.parameters,
defaultValue: this.$props.defaultValue,
})
.then((t) => {
this.$data.translation = t;
});
},
unsubscribe() {
this.$data.translationSubscription?.unsubscribe();
this.$data.langSubscription?.unsubscribe();
},
subscribe() {
const tolgeeContext = this.tolgeeContext as TolgeeContext;

this.translate();

this.$data.translationSubscription =
tolgeeContext.tolgee.onTranslationChange.subscribe(this.translate);

this.$data.langSubscription = tolgeeContext.tolgee.onLangChange.subscribe(
this.translate
);
},
},
created() {
this.subscribe();
},
beforeUpdate() {
this.unsubscribe();
this.subscribe();
},
beforeUnmount() {
this.unsubscribe();
setup() {
const t = useTranslate();
return { t };
},
render() {
const content = this.$data.translation;
const params: TranslateFnProps = {
key: this.$props.keyName,
parameters: this.$props.parameters,
defaultValue: this.$props.defaultValue,
noWrap: this.noWrap,
};
const content = this.t(params);
return content;
},
});
4 changes: 3 additions & 1 deletion packages/vue/src/__integration/TolgeeComposition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ describe('mixin integration', () => {
expect(screen.queryByTestId('non_existant').innerHTML).toContain(
'Non existant'
);
expect(screen.queryByTestId('non_existant')).toHaveProperty('_tolgee');
waitFor(() => {
expect(screen.queryByTestId('non_existant')).toHaveProperty('_tolgee');
});
});

describe('language switch', () => {
Expand Down
39 changes: 34 additions & 5 deletions packages/vue/src/useTranslate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import { TolgeeContext, TranslateFnProps } from './types';
export const useTranslate = () => {
const tolgeeContext = inject('tolgeeContext') as TolgeeContext;

let keysRef = [];

const resetMemory = (key?: string) => {
keysRef = key ? keysRef.filter((k) => k !== key) : [];
};

const createTFunction = () => {
return (
keyOrProps: string | TranslateFnProps,
Expand Down Expand Up @@ -38,8 +44,25 @@ export const useTranslate = () => {
key: key,
params: parameters,
noWrap,
defaultValue: defaultValue,
defaultValue,
});

const firstRender = !keysRef.includes(key);
if (firstRender) {
keysRef.push(key);
tolgeeContext?.tolgee
.translate({
key,
params: parameters,
noWrap,
defaultValue,
})
.then((value) => {
if (value !== result) {
t.value = createTFunction();
}
});
}
return result;
};
};
Expand All @@ -50,11 +73,17 @@ export const useTranslate = () => {
let allTranslationsSub: any;
onMounted(() => {
const tolgee = tolgeeContext.tolgee;
translationSub = tolgee.onTranslationChange.subscribe(() => {
t.value = createTFunction();
translationSub = tolgee.onTranslationChange.subscribe(({ key }) => {
if (keysRef.includes(key)) {
resetMemory(key);
t.value = createTFunction();
}
});
allTranslationsSub = tolgee.onLangLoaded.subscribe(() => {
t.value = createTFunction();
allTranslationsSub = tolgee.onLangChange.subscribe(() => {
if (keysRef.length) {
resetMemory();
t.value = createTFunction();
}
});
});

Expand Down
1 change: 0 additions & 1 deletion testapps/react/src/Todos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export const Todos = () => {
<T keyName="menu-item-translation-methods">Translation methods</T>
</a>
</Navbar>

<header>
<h1 className="header__title">
<T keyName="on-the-road-title">On the road</T>
Expand Down
Loading

0 comments on commit 1295d36

Please sign in to comment.