@tolgee/react 5.4.0 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -5
- package/src/GlobalContextPlugin.tsx +19 -0
- package/src/T.tsx +39 -0
- package/src/TolgeeProvider.spec.tsx +106 -0
- package/src/TolgeeProvider.tsx +60 -0
- package/src/__integration/T.spec.tsx +138 -0
- package/src/__integration/TolgeeProvider.spec.tsx +138 -0
- package/src/__integration/namespaces.spec.tsx +119 -0
- package/src/__integration/useTolgee.spec.tsx +151 -0
- package/src/__integration/useTranslation.spec.tsx +123 -0
- package/src/hooks.ts +10 -0
- package/src/index.ts +8 -0
- package/src/tagsTools.tsx +43 -0
- package/src/types.ts +16 -0
- package/src/useTolgee.ts +19 -0
- package/src/useTolgeeContext.ts +13 -0
- package/src/useTranslate.ts +23 -0
- package/src/useTranslateInternal.ts +76 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tolgee/react",
|
|
3
|
-
"version": "5.4.
|
|
3
|
+
"version": "5.4.1",
|
|
4
4
|
"description": "React implementation for tolgee localization framework",
|
|
5
5
|
"main": "./dist/tolgee-react.cjs.js",
|
|
6
6
|
"module": "./dist/tolgee-react.esm.js",
|
|
@@ -32,13 +32,14 @@
|
|
|
32
32
|
"files": [
|
|
33
33
|
"index.js",
|
|
34
34
|
"lib/**/*",
|
|
35
|
-
"dist/**/*"
|
|
35
|
+
"dist/**/*",
|
|
36
|
+
"src/**/*"
|
|
36
37
|
],
|
|
37
38
|
"peerDependencies": {
|
|
38
39
|
"react": "^16.14.0 || ^17.0.1 || ^18.1.0"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@tolgee/web": "5.4.
|
|
42
|
+
"@tolgee/web": "5.4.1"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@rollup/plugin-node-resolve": "^14.1.0",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"@testing-library/jest-dom": "^5.11.4",
|
|
48
49
|
"@testing-library/react": "^12.1.2",
|
|
49
50
|
"@tolgee/format-icu": "5.4.0",
|
|
50
|
-
"@tolgee/testing": "5.1
|
|
51
|
+
"@tolgee/testing": "5.4.1",
|
|
51
52
|
"@types/jest": "^27.0.2",
|
|
52
53
|
"@types/node": "^17.0.8",
|
|
53
54
|
"@types/react": "^17.0.1",
|
|
@@ -80,5 +81,5 @@
|
|
|
80
81
|
"access": "public"
|
|
81
82
|
},
|
|
82
83
|
"sideEffects": false,
|
|
83
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "baf4fbc39ab12e0a8950231525c9322bc7750879"
|
|
84
85
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TolgeePlugin } from '@tolgee/web';
|
|
2
|
+
import { DEFAULT_REACT_OPTIONS } from './TolgeeProvider';
|
|
3
|
+
import type { ReactOptions, TolgeeReactContext } from './types';
|
|
4
|
+
|
|
5
|
+
let globalContext: TolgeeReactContext | undefined;
|
|
6
|
+
|
|
7
|
+
export const GlobalContextPlugin =
|
|
8
|
+
(options?: Partial<ReactOptions>): TolgeePlugin =>
|
|
9
|
+
(tolgee) => {
|
|
10
|
+
globalContext = {
|
|
11
|
+
tolgee,
|
|
12
|
+
options: { ...DEFAULT_REACT_OPTIONS, ...options },
|
|
13
|
+
};
|
|
14
|
+
return tolgee;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function getGlobalContext() {
|
|
18
|
+
return globalContext;
|
|
19
|
+
}
|
package/src/T.tsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NsType, TranslateParams } from '@tolgee/web';
|
|
2
|
+
import React, { FunctionComponent } from 'react';
|
|
3
|
+
import { addReactKeys, wrapTagHandlers } from './tagsTools';
|
|
4
|
+
|
|
5
|
+
import { ParamsTags } from './types';
|
|
6
|
+
import { useTranslateInternal } from './useTranslateInternal';
|
|
7
|
+
|
|
8
|
+
type TProps = {
|
|
9
|
+
params?: TranslateParams<ParamsTags>;
|
|
10
|
+
children?: string;
|
|
11
|
+
noWrap?: boolean;
|
|
12
|
+
keyName?: string;
|
|
13
|
+
ns?: NsType;
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const T: FunctionComponent<TProps> = (props: TProps) => {
|
|
18
|
+
const key = props.keyName || props.children;
|
|
19
|
+
if (key === undefined) {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.error('T component: keyName not defined');
|
|
22
|
+
}
|
|
23
|
+
const defaultValue =
|
|
24
|
+
props.defaultValue || (props.keyName ? props.children : undefined);
|
|
25
|
+
|
|
26
|
+
const { t } = useTranslateInternal();
|
|
27
|
+
|
|
28
|
+
const translation = addReactKeys(
|
|
29
|
+
t({
|
|
30
|
+
key: key!,
|
|
31
|
+
params: wrapTagHandlers(props.params),
|
|
32
|
+
defaultValue,
|
|
33
|
+
noWrap: props.noWrap,
|
|
34
|
+
ns: props.ns,
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return <>{translation}</>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
jest.autoMockOff();
|
|
2
|
+
|
|
3
|
+
import { Tolgee, TolgeeInstance, TolgeePlugin } from '@tolgee/web';
|
|
4
|
+
|
|
5
|
+
import '@testing-library/jest-dom';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { TolgeeProvider } from './TolgeeProvider';
|
|
8
|
+
|
|
9
|
+
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
10
|
+
import { createResolvablePromise } from '@tolgee/testing/createResolvablePromise';
|
|
11
|
+
|
|
12
|
+
describe('Tolgee Provider Component', () => {
|
|
13
|
+
let mockedTolgee: TolgeeInstance;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockedTolgee = {
|
|
17
|
+
...Tolgee().init({ language: 'en' }),
|
|
18
|
+
run: jest.fn(() => new Promise<void>(() => {})),
|
|
19
|
+
stop: jest.fn(),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('renders loadingFallback', async () => {
|
|
24
|
+
act(() => {
|
|
25
|
+
render(
|
|
26
|
+
<TolgeeProvider
|
|
27
|
+
tolgee={mockedTolgee}
|
|
28
|
+
fallback={<>loading</>}
|
|
29
|
+
options={{ useSuspense: false }}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
await waitFor(() => {
|
|
34
|
+
screen.getByText('loading');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('runs tolgee', async () => {
|
|
39
|
+
act(() => {
|
|
40
|
+
render(<TolgeeProvider tolgee={mockedTolgee} fallback="loading" />);
|
|
41
|
+
});
|
|
42
|
+
expect(mockedTolgee.run).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('stops tolgee', () => {
|
|
46
|
+
const { unmount } = render(<TolgeeProvider tolgee={mockedTolgee} />);
|
|
47
|
+
unmount();
|
|
48
|
+
expect(mockedTolgee.stop).toHaveBeenCalledTimes(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("doesn't render when everything is already fetched", async () => {
|
|
52
|
+
const tolgee = Tolgee().init({
|
|
53
|
+
language: 'en',
|
|
54
|
+
staticData: {
|
|
55
|
+
en: {},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
act(() => {
|
|
59
|
+
render(
|
|
60
|
+
<TolgeeProvider tolgee={tolgee} fallback={<>loading</>}>
|
|
61
|
+
It's rendered!
|
|
62
|
+
</TolgeeProvider>
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
await waitFor(async () => {
|
|
66
|
+
screen.getByText("It's rendered!");
|
|
67
|
+
const loading = screen.queryByText('loading');
|
|
68
|
+
expect(loading).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('renders fallback when language is being fetched', async () => {
|
|
73
|
+
const language = createResolvablePromise('en');
|
|
74
|
+
|
|
75
|
+
const storagePlugin: TolgeePlugin = (tolgee, { setLanguageStorage }) => {
|
|
76
|
+
setLanguageStorage({
|
|
77
|
+
getLanguage() {
|
|
78
|
+
return language.promise;
|
|
79
|
+
},
|
|
80
|
+
setLanguage() {},
|
|
81
|
+
});
|
|
82
|
+
return tolgee;
|
|
83
|
+
};
|
|
84
|
+
const tolgee = Tolgee()
|
|
85
|
+
.use(storagePlugin)
|
|
86
|
+
.init({
|
|
87
|
+
defaultLanguage: 'en',
|
|
88
|
+
staticData: {
|
|
89
|
+
en: {},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
render(
|
|
93
|
+
<TolgeeProvider tolgee={tolgee} fallback="Loading...">
|
|
94
|
+
It's rendered!
|
|
95
|
+
</TolgeeProvider>
|
|
96
|
+
);
|
|
97
|
+
await waitFor(async () => {
|
|
98
|
+
expect(screen.getByText('Loading...')).toBeTruthy();
|
|
99
|
+
});
|
|
100
|
+
await act(async () => {
|
|
101
|
+
language.resolve();
|
|
102
|
+
await language.promise;
|
|
103
|
+
});
|
|
104
|
+
expect(screen.getByText("It's rendered!")).toBeTruthy();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { Suspense, useEffect, useState } from 'react';
|
|
2
|
+
import { TolgeeInstance } from '@tolgee/web';
|
|
3
|
+
import { ReactOptions, TolgeeReactContext } from './types';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_REACT_OPTIONS: ReactOptions = {
|
|
6
|
+
useSuspense: true,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const TolgeeProviderContext = React.createContext<
|
|
10
|
+
TolgeeReactContext | undefined
|
|
11
|
+
>(undefined);
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
tolgee: TolgeeInstance;
|
|
16
|
+
options?: ReactOptions;
|
|
17
|
+
fallback?: React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const TolgeeProvider: React.FC<Props> = ({
|
|
21
|
+
tolgee,
|
|
22
|
+
options,
|
|
23
|
+
children,
|
|
24
|
+
fallback,
|
|
25
|
+
}) => {
|
|
26
|
+
const [loading, setLoading] = useState(!tolgee.isLoaded());
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
tolgee.run().then(() => {
|
|
30
|
+
setLoading(false);
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
tolgee.stop();
|
|
34
|
+
};
|
|
35
|
+
}, [tolgee]);
|
|
36
|
+
|
|
37
|
+
const optionsWithDefault = { ...DEFAULT_REACT_OPTIONS, ...options };
|
|
38
|
+
|
|
39
|
+
if (optionsWithDefault.useSuspense) {
|
|
40
|
+
return (
|
|
41
|
+
<TolgeeProviderContext.Provider
|
|
42
|
+
value={{ tolgee, options: optionsWithDefault }}
|
|
43
|
+
>
|
|
44
|
+
{loading ? (
|
|
45
|
+
fallback
|
|
46
|
+
) : (
|
|
47
|
+
<Suspense fallback={fallback || null}>{children}</Suspense>
|
|
48
|
+
)}
|
|
49
|
+
</TolgeeProviderContext.Provider>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<TolgeeProviderContext.Provider
|
|
55
|
+
value={{ tolgee, options: optionsWithDefault }}
|
|
56
|
+
>
|
|
57
|
+
{loading ? fallback : children}
|
|
58
|
+
</TolgeeProviderContext.Provider>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { act } from 'react-dom/test-utils';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
|
+
import { TolgeeProvider, DevTools, TolgeeInstance, Tolgee } from '../index';
|
|
6
|
+
import { FormatIcu } from '@tolgee/format-icu';
|
|
7
|
+
import { mockCoreFetch } from '@tolgee/testing/fetchMock';
|
|
8
|
+
|
|
9
|
+
import { T } from '../index';
|
|
10
|
+
|
|
11
|
+
const API_URL = 'http://localhost';
|
|
12
|
+
const API_KEY = 'dummyApiKey';
|
|
13
|
+
|
|
14
|
+
const fetch = mockCoreFetch();
|
|
15
|
+
|
|
16
|
+
describe('T component integration', () => {
|
|
17
|
+
let tolgee: TolgeeInstance;
|
|
18
|
+
const TestComponent = () => {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<div data-testid="peter_dogs">
|
|
22
|
+
<T keyName="peter_dogs" params={{ dogsCount: 5 }} />
|
|
23
|
+
</div>
|
|
24
|
+
<div data-testid="hello_world">
|
|
25
|
+
<T>hello_world</T>
|
|
26
|
+
</div>
|
|
27
|
+
<div data-testid="hello_world_no_wrap">
|
|
28
|
+
<T noWrap>hello_world</T>
|
|
29
|
+
</div>
|
|
30
|
+
<div data-testid="non_existant">
|
|
31
|
+
<T keyName="non_existant">Non existant</T>
|
|
32
|
+
</div>
|
|
33
|
+
<div data-testid="default_value_in_params">
|
|
34
|
+
<T keyName="non_existant" defaultValue="Non existant" />
|
|
35
|
+
</div>
|
|
36
|
+
<div data-testid="with_tags">
|
|
37
|
+
<T keyName="with_tags" params={{ b: <b />, i: <i /> }} />
|
|
38
|
+
</div>
|
|
39
|
+
<div data-testid="with_tag">
|
|
40
|
+
<T
|
|
41
|
+
keyName="with_tag"
|
|
42
|
+
params={{
|
|
43
|
+
b: (text: string) => <b title={text}>{text}</b>,
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
beforeEach(async () => {
|
|
52
|
+
fetch.enableMocks();
|
|
53
|
+
tolgee = Tolgee().use(DevTools()).use(FormatIcu()).init({
|
|
54
|
+
apiUrl: API_URL,
|
|
55
|
+
apiKey: API_KEY,
|
|
56
|
+
language: 'cs',
|
|
57
|
+
fallbackLanguage: 'en',
|
|
58
|
+
});
|
|
59
|
+
act(() => {
|
|
60
|
+
render(
|
|
61
|
+
<TolgeeProvider tolgee={tolgee} fallback="Loading...">
|
|
62
|
+
<TestComponent />
|
|
63
|
+
</TolgeeProvider>
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
await waitFor(() => {
|
|
67
|
+
expect(screen.queryByTestId('hello_world')?.textContent).toContain(
|
|
68
|
+
'Ahoj světe!'
|
|
69
|
+
);
|
|
70
|
+
expect(screen.queryByTestId('hello_world')).toHaveAttribute(
|
|
71
|
+
'_tolgee',
|
|
72
|
+
'true'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('wraps translation correctly', async () => {
|
|
78
|
+
expect(screen.queryByTestId('hello_world')).toContainHTML('Ahoj světe!');
|
|
79
|
+
expect(screen.queryByTestId('hello_world')).toHaveAttribute('_tolgee');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('works with no wrap', () => {
|
|
83
|
+
expect(screen.queryByTestId('hello_world_no_wrap')).toContainHTML(
|
|
84
|
+
'Ahoj světe!'
|
|
85
|
+
);
|
|
86
|
+
expect(screen.queryByTestId('hello_world_no_wrap')).not.toHaveAttribute(
|
|
87
|
+
'_tolgee'
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('works with parameters', () => {
|
|
92
|
+
expect(screen.queryByTestId('peter_dogs')).toContainHTML('Petr má 5 psů.');
|
|
93
|
+
expect(screen.queryByTestId('peter_dogs')).toHaveAttribute('_tolgee');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('works with default value', async () => {
|
|
97
|
+
expect(screen.queryByTestId('non_existant')).toContainHTML('Non existant');
|
|
98
|
+
expect(screen.queryByTestId('non_existant')).toHaveAttribute('_tolgee');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('works with default value in params', async () => {
|
|
102
|
+
expect(screen.queryByTestId('default_value_in_params')).toContainHTML(
|
|
103
|
+
'Non existant'
|
|
104
|
+
);
|
|
105
|
+
expect(screen.queryByTestId('default_value_in_params')).toHaveAttribute(
|
|
106
|
+
'_tolgee'
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('works with tags', () => {
|
|
111
|
+
expect(screen.queryByTestId('with_tags')).toContainHTML(
|
|
112
|
+
'Tento <b>text <i>je</i> formátovaný</b>'
|
|
113
|
+
);
|
|
114
|
+
expect(screen.queryByTestId('with_tags')).toHaveAttribute('_tolgee');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('works with tag as function', () => {
|
|
118
|
+
expect(screen.queryByTestId('with_tag')).toContainHTML(
|
|
119
|
+
'<b title="bold">bold</b>'
|
|
120
|
+
);
|
|
121
|
+
expect(screen.queryByTestId('with_tag')).toHaveAttribute('_tolgee');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('language switch', () => {
|
|
125
|
+
beforeEach(async () => {
|
|
126
|
+
await act(async () => {
|
|
127
|
+
await tolgee.changeLanguage('en');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('changes translation with tags', () => {
|
|
132
|
+
expect(screen.queryByTestId('with_tags')).toContainHTML(
|
|
133
|
+
'This <b>text <i>is</i> formatted</b>'
|
|
134
|
+
);
|
|
135
|
+
expect(screen.queryByTestId('with_tags')).toHaveAttribute('_tolgee');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { act } from 'react-dom/test-utils';
|
|
4
|
+
import {
|
|
5
|
+
DevTools,
|
|
6
|
+
Tolgee,
|
|
7
|
+
TolgeeInstance,
|
|
8
|
+
TolgeeProvider,
|
|
9
|
+
useTranslate,
|
|
10
|
+
} from '..';
|
|
11
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
12
|
+
import { mockCoreFetchAsync } from '@tolgee/testing/fetchMock';
|
|
13
|
+
|
|
14
|
+
const API_URL = 'http://localhost';
|
|
15
|
+
const API_KEY = 'dummyApiKey';
|
|
16
|
+
|
|
17
|
+
describe('TolgeeProvider integration', () => {
|
|
18
|
+
const TestComponent = () => {
|
|
19
|
+
const { t } = useTranslate();
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<div data-testid="hello_world">{t('hello_world')}</div>
|
|
23
|
+
<div data-testid="english_fallback">
|
|
24
|
+
{t('english_fallback', 'Default value')}
|
|
25
|
+
</div>
|
|
26
|
+
<div data-testid="non_existant">
|
|
27
|
+
{t('non_existant', 'Default value')}
|
|
28
|
+
</div>
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe('regular settings', () => {
|
|
34
|
+
let resolveEnglish: any;
|
|
35
|
+
let resolveCzech: any;
|
|
36
|
+
let tolgee: TolgeeInstance;
|
|
37
|
+
|
|
38
|
+
beforeEach(async () => {
|
|
39
|
+
const fetchMock = mockCoreFetchAsync();
|
|
40
|
+
resolveCzech = fetchMock.csTranslations.resolve;
|
|
41
|
+
resolveEnglish = fetchMock.enTranslations.resolve;
|
|
42
|
+
fetchMock.fetch.enableMocks();
|
|
43
|
+
tolgee = Tolgee().use(DevTools()).init({
|
|
44
|
+
apiUrl: API_URL,
|
|
45
|
+
apiKey: API_KEY,
|
|
46
|
+
language: 'cs',
|
|
47
|
+
fallbackLanguage: 'en',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
act(() => {
|
|
51
|
+
render(
|
|
52
|
+
<TolgeeProvider tolgee={tolgee} fallback="Loading...">
|
|
53
|
+
<TestComponent />
|
|
54
|
+
</TolgeeProvider>
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
tolgee.stop();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('shows correctly loading, fallback and default value', async () => {
|
|
64
|
+
expect(screen.queryByText('Loading...')).toBeInTheDocument();
|
|
65
|
+
act(() => {
|
|
66
|
+
resolveCzech();
|
|
67
|
+
});
|
|
68
|
+
await waitFor(() => {
|
|
69
|
+
expect(screen.queryByText('Loading...')).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
act(() => {
|
|
72
|
+
resolveEnglish();
|
|
73
|
+
});
|
|
74
|
+
await waitFor(() => {
|
|
75
|
+
expect(screen.queryByTestId('hello_world')).toContainHTML(
|
|
76
|
+
'Ahoj světe!'
|
|
77
|
+
);
|
|
78
|
+
expect(screen.queryByTestId('english_fallback')).toContainHTML(
|
|
79
|
+
'English fallback'
|
|
80
|
+
);
|
|
81
|
+
expect(screen.queryByTestId('non_existant')).toContainHTML(
|
|
82
|
+
'Default value'
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('with fallback', () => {
|
|
89
|
+
let resolveEnglish: any;
|
|
90
|
+
let resolveCzech: any;
|
|
91
|
+
let tolgee: TolgeeInstance;
|
|
92
|
+
|
|
93
|
+
beforeEach(async () => {
|
|
94
|
+
const fetchMock = mockCoreFetchAsync();
|
|
95
|
+
resolveCzech = fetchMock.csTranslations.resolve;
|
|
96
|
+
resolveEnglish = fetchMock.enTranslations.resolve;
|
|
97
|
+
fetchMock.fetch.enableMocks();
|
|
98
|
+
tolgee = Tolgee().use(DevTools()).init({
|
|
99
|
+
apiUrl: API_URL,
|
|
100
|
+
apiKey: API_KEY,
|
|
101
|
+
language: 'cs',
|
|
102
|
+
fallbackLanguage: 'en',
|
|
103
|
+
});
|
|
104
|
+
act(() => {
|
|
105
|
+
render(
|
|
106
|
+
<TolgeeProvider tolgee={tolgee} fallback="Loading...">
|
|
107
|
+
<TestComponent />
|
|
108
|
+
</TolgeeProvider>
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('shows correctly loading, fallback and default value', async () => {
|
|
114
|
+
expect(screen.queryByText('Loading...')).toBeInTheDocument();
|
|
115
|
+
act(() => {
|
|
116
|
+
resolveCzech();
|
|
117
|
+
});
|
|
118
|
+
await waitFor(() => {
|
|
119
|
+
expect(screen.queryByText('Loading...')).toBeInTheDocument();
|
|
120
|
+
expect(screen.queryByTestId('hello_world')).not.toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
act(() => {
|
|
123
|
+
resolveEnglish();
|
|
124
|
+
});
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(screen.queryByTestId('hello_world')).toContainHTML(
|
|
127
|
+
'Ahoj světe!'
|
|
128
|
+
);
|
|
129
|
+
expect(screen.queryByTestId('english_fallback')).toContainHTML(
|
|
130
|
+
'English fallback'
|
|
131
|
+
);
|
|
132
|
+
expect(screen.queryByTestId('non_existant')).toContainHTML(
|
|
133
|
+
'Default value'
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { mockStaticDataAsync } from '@tolgee/testing/mockStaticData';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { DevTools, useTranslate } from '..';
|
|
5
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
6
|
+
import { act } from 'react-dom/test-utils';
|
|
7
|
+
import { Tolgee, TolgeeInstance } from '@tolgee/web';
|
|
8
|
+
import { FormatIcu } from '@tolgee/format-icu';
|
|
9
|
+
import { GlobalContextPlugin } from '../GlobalContextPlugin';
|
|
10
|
+
|
|
11
|
+
jest.autoMockOff();
|
|
12
|
+
|
|
13
|
+
const API_URL = 'http://localhost';
|
|
14
|
+
|
|
15
|
+
describe('useTranslations namespaces', () => {
|
|
16
|
+
let tolgee: TolgeeInstance;
|
|
17
|
+
const TestComponent = () => {
|
|
18
|
+
const { t, isLoading } = useTranslate('test');
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
{isLoading && <div data-testid="loading">Loading...</div>}
|
|
23
|
+
<div data-testid="test">{t('test')}</div>
|
|
24
|
+
<div data-testid="test_english_fallback">
|
|
25
|
+
{t('test_english_fallback')}
|
|
26
|
+
</div>
|
|
27
|
+
<div data-testid="non_existant">
|
|
28
|
+
{t('non_existant', 'Non existant')}
|
|
29
|
+
</div>
|
|
30
|
+
<div data-testid="ns_fallback">{t('fallback', { ns: 'invalid' })}</div>
|
|
31
|
+
</>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
let staticDataMock: ReturnType<typeof mockStaticDataAsync>;
|
|
36
|
+
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
staticDataMock = mockStaticDataAsync();
|
|
39
|
+
tolgee = Tolgee()
|
|
40
|
+
.use(GlobalContextPlugin({ useSuspense: false }))
|
|
41
|
+
.use(DevTools())
|
|
42
|
+
.use(FormatIcu())
|
|
43
|
+
.init({
|
|
44
|
+
apiUrl: API_URL,
|
|
45
|
+
language: 'cs',
|
|
46
|
+
fallbackLanguage: 'en',
|
|
47
|
+
fallbackNs: 'fallback',
|
|
48
|
+
staticData: staticDataMock.promises,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await act(async () => {
|
|
52
|
+
const runPromise = tolgee.run();
|
|
53
|
+
staticDataMock.resolvablePromises.cs.resolve();
|
|
54
|
+
staticDataMock.resolvablePromises.en.resolve();
|
|
55
|
+
staticDataMock.resolvablePromises['en:fallback'].resolve();
|
|
56
|
+
await runPromise;
|
|
57
|
+
render(<TestComponent />);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
tolgee.stop();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('loads namespace after render', async () => {
|
|
66
|
+
expect(screen.queryByTestId('loading')).toContainHTML('Loading...');
|
|
67
|
+
staticDataMock.resolveAll();
|
|
68
|
+
await waitFor(() => {
|
|
69
|
+
expect(screen.queryByTestId('loading')).toBeFalsy();
|
|
70
|
+
expect(screen.queryByTestId('test')).toContainHTML('Český test');
|
|
71
|
+
expect(screen.queryByTestId('test')).toHaveAttribute('_tolgee');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('works with english fallback', async () => {
|
|
76
|
+
staticDataMock.resolveAll();
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(screen.queryByTestId('test_english_fallback')).toContainHTML(
|
|
79
|
+
'Test english fallback'
|
|
80
|
+
);
|
|
81
|
+
expect(screen.queryByTestId('test_english_fallback')).toHaveAttribute(
|
|
82
|
+
'_tolgee'
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('works with ns fallback', async () => {
|
|
88
|
+
expect(screen.queryByTestId('ns_fallback')).toContainHTML('fallback');
|
|
89
|
+
staticDataMock.resolveAll();
|
|
90
|
+
await waitFor(() => {
|
|
91
|
+
expect(screen.queryByTestId('ns_fallback')).toContainHTML('Fallback');
|
|
92
|
+
expect(screen.queryByTestId('ns_fallback')).toHaveAttribute('_tolgee');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('works with language and ns fallback', async () => {
|
|
97
|
+
expect(screen.queryByTestId('ns_fallback')).toContainHTML(
|
|
98
|
+
'Fallback fallback'
|
|
99
|
+
);
|
|
100
|
+
await act(async () => {
|
|
101
|
+
const changePromise = tolgee.changeLanguage('en');
|
|
102
|
+
staticDataMock.resolveAll();
|
|
103
|
+
await changePromise;
|
|
104
|
+
});
|
|
105
|
+
expect(screen.queryByTestId('ns_fallback')).toContainHTML(
|
|
106
|
+
'Fallback fallback'
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('works with default value', async () => {
|
|
111
|
+
staticDataMock.resolveAll();
|
|
112
|
+
await waitFor(() => {
|
|
113
|
+
expect(screen.queryByTestId('non_existant')).toContainHTML(
|
|
114
|
+
'Non existant'
|
|
115
|
+
);
|
|
116
|
+
expect(screen.queryByTestId('non_existant')).toHaveAttribute('_tolgee');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { DevTools, useTolgee, GlobalContextPlugin } from '..';
|
|
4
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
|
+
import { act } from 'react-dom/test-utils';
|
|
6
|
+
import { Tolgee, TolgeeEvent, TolgeeInstance } from '@tolgee/web';
|
|
7
|
+
import { FormatIcu } from '@tolgee/format-icu';
|
|
8
|
+
import { mockStaticDataAsync } from '@tolgee/testing/mockStaticData';
|
|
9
|
+
|
|
10
|
+
const API_URL = 'http://localhost';
|
|
11
|
+
|
|
12
|
+
type CheckStateProps = Partial<Record<TolgeeEvent, string>>;
|
|
13
|
+
|
|
14
|
+
const checkState = (props: CheckStateProps) => {
|
|
15
|
+
Object.entries(props).forEach(([event, value]) => {
|
|
16
|
+
expect(screen.queryByTestId(event)).toContainHTML(value);
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe('useTranslation hook integration', () => {
|
|
21
|
+
let tolgee: TolgeeInstance;
|
|
22
|
+
let runPromise: Promise<void>;
|
|
23
|
+
let staticDataMock: ReturnType<typeof mockStaticDataAsync>;
|
|
24
|
+
const TestComponent = ({ events }: { events?: TolgeeEvent[] }) => {
|
|
25
|
+
const {
|
|
26
|
+
getLanguage,
|
|
27
|
+
getPendingLanguage,
|
|
28
|
+
isFetching,
|
|
29
|
+
isLoading,
|
|
30
|
+
isRunning,
|
|
31
|
+
isInitialLoading,
|
|
32
|
+
} = useTolgee(events);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<div data-testid="language">{String(getLanguage())}</div>
|
|
37
|
+
<div data-testid="pendingLanguage">{String(getPendingLanguage())}</div>
|
|
38
|
+
<div data-testid="fetching">{String(isFetching())}</div>
|
|
39
|
+
<div data-testid="loading">{String(isLoading())}</div>
|
|
40
|
+
<div data-testid="initialLoad">{String(isInitialLoading())}</div>
|
|
41
|
+
<div data-testid="running">{String(isRunning())}</div>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
beforeEach(async () => {
|
|
47
|
+
staticDataMock = mockStaticDataAsync();
|
|
48
|
+
|
|
49
|
+
tolgee = Tolgee()
|
|
50
|
+
.use(DevTools())
|
|
51
|
+
.use(GlobalContextPlugin())
|
|
52
|
+
.use(FormatIcu())
|
|
53
|
+
.init({
|
|
54
|
+
apiUrl: API_URL,
|
|
55
|
+
language: 'cs',
|
|
56
|
+
staticData: staticDataMock.promises,
|
|
57
|
+
});
|
|
58
|
+
runPromise = tolgee.run();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
tolgee.stop();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('updates initialLoading', async () => {
|
|
66
|
+
render(<TestComponent events={['initialLoad']} />);
|
|
67
|
+
checkState({ initialLoad: 'true' });
|
|
68
|
+
|
|
69
|
+
await act(async () => {
|
|
70
|
+
staticDataMock.resolvablePromises.cs.resolve();
|
|
71
|
+
await runPromise;
|
|
72
|
+
});
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
checkState({ initialLoad: 'false' });
|
|
75
|
+
});
|
|
76
|
+
await act(async () => {
|
|
77
|
+
tolgee.changeLanguage('en');
|
|
78
|
+
staticDataMock.resolvablePromises.en.resolve();
|
|
79
|
+
});
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
checkState({ initialLoad: 'false' });
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('updates language', async () => {
|
|
86
|
+
render(<TestComponent events={['language']} />);
|
|
87
|
+
await act(async () => {
|
|
88
|
+
staticDataMock.resolvablePromises.cs.resolve();
|
|
89
|
+
await runPromise;
|
|
90
|
+
});
|
|
91
|
+
checkState({ language: 'cs' });
|
|
92
|
+
await act(async () => {
|
|
93
|
+
tolgee.changeLanguage('en');
|
|
94
|
+
staticDataMock.resolvablePromises.en.resolve();
|
|
95
|
+
});
|
|
96
|
+
checkState({ language: 'en' });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('updates pending language', async () => {
|
|
100
|
+
render(<TestComponent events={['pendingLanguage']} />);
|
|
101
|
+
await act(async () => {
|
|
102
|
+
staticDataMock.resolvablePromises.cs.resolve();
|
|
103
|
+
await runPromise;
|
|
104
|
+
});
|
|
105
|
+
checkState({ language: 'cs', pendingLanguage: 'cs' });
|
|
106
|
+
await act(async () => {
|
|
107
|
+
tolgee.changeLanguage('en');
|
|
108
|
+
staticDataMock.resolvablePromises.en.resolve();
|
|
109
|
+
});
|
|
110
|
+
await waitFor(async () => {
|
|
111
|
+
checkState({ language: 'cs', pendingLanguage: 'en' });
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('updates fetching and loading', async () => {
|
|
116
|
+
render(<TestComponent events={['fetching', 'loading']} />);
|
|
117
|
+
checkState({ loading: 'true', fetching: 'true' });
|
|
118
|
+
|
|
119
|
+
await act(async () => {
|
|
120
|
+
staticDataMock.resolvablePromises.cs.resolve();
|
|
121
|
+
await runPromise;
|
|
122
|
+
});
|
|
123
|
+
await waitFor(async () => {
|
|
124
|
+
checkState({ loading: 'false', fetching: 'false' });
|
|
125
|
+
});
|
|
126
|
+
await act(async () => {
|
|
127
|
+
tolgee.changeLanguage('en');
|
|
128
|
+
});
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
checkState({ loading: 'false', fetching: 'true' });
|
|
131
|
+
});
|
|
132
|
+
await act(async () => {
|
|
133
|
+
staticDataMock.resolvablePromises.en.resolve();
|
|
134
|
+
});
|
|
135
|
+
await waitFor(async () => {
|
|
136
|
+
checkState({ loading: 'false', fetching: 'false' });
|
|
137
|
+
});
|
|
138
|
+
await act(async () => {
|
|
139
|
+
tolgee.addActiveNs('test');
|
|
140
|
+
});
|
|
141
|
+
await waitFor(() => {
|
|
142
|
+
checkState({ loading: 'true', fetching: 'true' });
|
|
143
|
+
});
|
|
144
|
+
await act(async () => {
|
|
145
|
+
staticDataMock.resolvablePromises['en:test'].resolve();
|
|
146
|
+
});
|
|
147
|
+
await waitFor(async () => {
|
|
148
|
+
checkState({ loading: 'false', fetching: 'false' });
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { DevTools, useTranslate, GlobalContextPlugin } from '..';
|
|
4
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
|
+
import { act } from 'react-dom/test-utils';
|
|
6
|
+
import { Tolgee, TolgeeInstance } from '@tolgee/web';
|
|
7
|
+
import { FormatIcu } from '@tolgee/format-icu';
|
|
8
|
+
import { mockCoreFetch } from '@tolgee/testing/fetchMock';
|
|
9
|
+
|
|
10
|
+
const API_URL = 'http://localhost';
|
|
11
|
+
const API_KEY = 'dummyApiKey';
|
|
12
|
+
|
|
13
|
+
const fetch = mockCoreFetch();
|
|
14
|
+
|
|
15
|
+
describe('useTranslation hook integration', () => {
|
|
16
|
+
let tolgee: TolgeeInstance;
|
|
17
|
+
const TestComponent = () => {
|
|
18
|
+
const { t } = useTranslate();
|
|
19
|
+
|
|
20
|
+
expect(typeof t('peter_dogs', { dogsCount: '5' })).toEqual('string');
|
|
21
|
+
expect(typeof t('non_existant', '<i>non_formatted</i>')).toEqual('string');
|
|
22
|
+
expect(
|
|
23
|
+
typeof t('non_existant', '<i>{parameter}</i>', { parameter: 'test' })
|
|
24
|
+
).toEqual('string');
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<div data-testid="peter_dogs">
|
|
29
|
+
{t('peter_dogs', { dogsCount: '5' })}
|
|
30
|
+
</div>
|
|
31
|
+
<div data-testid="hello_world">{t('hello_world')}</div>
|
|
32
|
+
<div data-testid="hello_world_no_wrap">
|
|
33
|
+
{t({ key: 'hello_world', noWrap: true })}
|
|
34
|
+
</div>
|
|
35
|
+
<div data-testid="non_existant">
|
|
36
|
+
{t('non_existant', 'Non existant')}
|
|
37
|
+
</div>
|
|
38
|
+
<div data-testid="non_formatted">
|
|
39
|
+
{t('non_existant', '<i>non_formatted</i>')}
|
|
40
|
+
</div>
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
fetch.enableMocks();
|
|
47
|
+
tolgee = Tolgee()
|
|
48
|
+
.use(DevTools())
|
|
49
|
+
.use(GlobalContextPlugin({ useSuspense: false }))
|
|
50
|
+
.use(FormatIcu())
|
|
51
|
+
.init({
|
|
52
|
+
apiUrl: API_URL,
|
|
53
|
+
apiKey: API_KEY,
|
|
54
|
+
language: 'cs',
|
|
55
|
+
fallbackLanguage: 'en',
|
|
56
|
+
});
|
|
57
|
+
tolgee.run();
|
|
58
|
+
act(() => {
|
|
59
|
+
render(<TestComponent />);
|
|
60
|
+
});
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(screen.queryByTestId('hello_world')).toContainHTML('Ahoj světe!');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
tolgee.stop();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('wraps translation correctly', async () => {
|
|
71
|
+
await waitFor(() => {
|
|
72
|
+
expect(screen.queryByTestId('hello_world')).toContainHTML('Ahoj světe!');
|
|
73
|
+
expect(screen.queryByTestId('hello_world')).toHaveAttribute('_tolgee');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('works with noWrap', async () => {
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
expect(screen.queryByTestId('hello_world_no_wrap')).toContainHTML(
|
|
80
|
+
'Ahoj světe!'
|
|
81
|
+
);
|
|
82
|
+
expect(screen.queryByTestId('hello_world_no_wrap')).not.toHaveAttribute(
|
|
83
|
+
'_tolgee'
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('works with parameters', async () => {
|
|
89
|
+
await waitFor(() => {
|
|
90
|
+
expect(screen.queryByTestId('peter_dogs')).toContainHTML(
|
|
91
|
+
'Petr má 5 psů.'
|
|
92
|
+
);
|
|
93
|
+
expect(screen.queryByTestId('peter_dogs')).toHaveAttribute('_tolgee');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('works with default value', async () => {
|
|
98
|
+
expect(screen.queryByTestId('non_existant')).toContainHTML('Non existant');
|
|
99
|
+
expect(screen.queryByTestId('non_existant')).toHaveAttribute('_tolgee');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('works with tags in default value', async () => {
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
expect(screen.queryByTestId('non_formatted')?.textContent).toContain(
|
|
105
|
+
'<i>non_formatted</i>'
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
expect(screen.queryByTestId('non_formatted')).toHaveAttribute('_tolgee');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('language switch', () => {
|
|
112
|
+
beforeEach(async () => {
|
|
113
|
+
await act(async () => {
|
|
114
|
+
await tolgee.changeLanguage('en');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('changes translation', async () => {
|
|
119
|
+
expect(screen.queryByTestId('hello_world')).toContainHTML('Hello world!');
|
|
120
|
+
expect(screen.queryByTestId('hello_world')).toHaveAttribute('_tolgee');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useRerender = () => {
|
|
4
|
+
const [instance, setCounter] = useState(0);
|
|
5
|
+
|
|
6
|
+
const rerender = useCallback(() => {
|
|
7
|
+
setCounter((num) => num + 1);
|
|
8
|
+
}, [setCounter]);
|
|
9
|
+
return { instance, rerender };
|
|
10
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { useTranslate } from './useTranslate';
|
|
2
|
+
export { TolgeeProvider, TolgeeProviderContext } from './TolgeeProvider';
|
|
3
|
+
export { T } from './T';
|
|
4
|
+
export { useTolgee } from './useTolgee';
|
|
5
|
+
export { GlobalContextPlugin } from './GlobalContextPlugin';
|
|
6
|
+
export * from './types';
|
|
7
|
+
|
|
8
|
+
export * from '@tolgee/web';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { TranslateParams } from '@tolgee/web';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
import { ParamsTags } from './types';
|
|
5
|
+
|
|
6
|
+
export const wrapTagHandlers = (
|
|
7
|
+
params: TranslateParams<ParamsTags> | undefined
|
|
8
|
+
) => {
|
|
9
|
+
if (!params) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const result: any = {};
|
|
14
|
+
|
|
15
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
16
|
+
if (typeof value === 'function') {
|
|
17
|
+
result[key] = (chunk: any) => {
|
|
18
|
+
return value(addReactKeys(chunk));
|
|
19
|
+
};
|
|
20
|
+
} else if (React.isValidElement(value as any)) {
|
|
21
|
+
const el = value as React.ReactElement;
|
|
22
|
+
result[key] = (chunk: any) => {
|
|
23
|
+
return el.props.children !== undefined
|
|
24
|
+
? React.cloneElement(el)
|
|
25
|
+
: React.cloneElement(el, {}, addReactKeys(chunk));
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
result[key] = value;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const addReactKeys = (
|
|
36
|
+
val: React.ReactNode | React.ReactNode[] | undefined
|
|
37
|
+
) => {
|
|
38
|
+
if (Array.isArray(val)) {
|
|
39
|
+
return React.Children.toArray(val);
|
|
40
|
+
} else {
|
|
41
|
+
return val;
|
|
42
|
+
}
|
|
43
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DefaultParamType, TolgeeInstance } from '@tolgee/web';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export type ParamsTags =
|
|
5
|
+
| DefaultParamType
|
|
6
|
+
| ((value: any) => JSX.Element | React.ReactElement | null)
|
|
7
|
+
| React.ReactNode;
|
|
8
|
+
|
|
9
|
+
export type ReactOptions = {
|
|
10
|
+
useSuspense: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type TolgeeReactContext = {
|
|
14
|
+
tolgee: TolgeeInstance;
|
|
15
|
+
options: ReactOptions;
|
|
16
|
+
};
|
package/src/useTolgee.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TolgeeEvent, TolgeeInstance } from '@tolgee/web';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { useRerender } from './hooks';
|
|
4
|
+
import { useTolgeeContext } from './useTolgeeContext';
|
|
5
|
+
|
|
6
|
+
export const useTolgee = (events?: TolgeeEvent[]): TolgeeInstance => {
|
|
7
|
+
const { tolgee } = useTolgeeContext();
|
|
8
|
+
|
|
9
|
+
const { rerender } = useRerender();
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const listeners = events?.map((e) => tolgee.on(e, rerender));
|
|
13
|
+
return () => {
|
|
14
|
+
listeners?.forEach((listener) => listener.unsubscribe());
|
|
15
|
+
};
|
|
16
|
+
}, [events?.join(':')]);
|
|
17
|
+
|
|
18
|
+
return tolgee;
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
import { getGlobalContext } from './GlobalContextPlugin';
|
|
3
|
+
import { TolgeeProviderContext } from './TolgeeProvider';
|
|
4
|
+
|
|
5
|
+
export const useTolgeeContext = () => {
|
|
6
|
+
const context = useContext(TolgeeProviderContext) || getGlobalContext();
|
|
7
|
+
if (!context) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
"Couldn't find tolgee instance, did you forgot to use `TolgeeProvider`?"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { TFnType, getTranslateProps, DefaultParamType } from '@tolgee/web';
|
|
3
|
+
|
|
4
|
+
import { useTranslateInternal } from './useTranslateInternal';
|
|
5
|
+
import { ReactOptions } from './types';
|
|
6
|
+
|
|
7
|
+
export const useTranslate = (
|
|
8
|
+
ns?: string[] | string,
|
|
9
|
+
options?: ReactOptions
|
|
10
|
+
) => {
|
|
11
|
+
const { t: tInternal, isLoading } = useTranslateInternal(ns, options);
|
|
12
|
+
|
|
13
|
+
const t: TFnType<DefaultParamType, string> = useCallback(
|
|
14
|
+
(...params: any) => {
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
const props = getTranslateProps(...params);
|
|
17
|
+
return tInternal(props);
|
|
18
|
+
},
|
|
19
|
+
[tInternal]
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
return { t, isLoading };
|
|
23
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
SubscriptionSelective,
|
|
4
|
+
TranslateProps,
|
|
5
|
+
NsFallback,
|
|
6
|
+
getFallbackArray,
|
|
7
|
+
getFallback,
|
|
8
|
+
} from '@tolgee/web';
|
|
9
|
+
|
|
10
|
+
import { useTolgeeContext } from './useTolgeeContext';
|
|
11
|
+
import { ReactOptions } from './types';
|
|
12
|
+
import { useRerender } from './hooks';
|
|
13
|
+
|
|
14
|
+
export const useTranslateInternal = (
|
|
15
|
+
ns?: NsFallback,
|
|
16
|
+
options?: ReactOptions
|
|
17
|
+
) => {
|
|
18
|
+
const { tolgee, options: defaultOptions } = useTolgeeContext();
|
|
19
|
+
const namespaces = getFallback(ns);
|
|
20
|
+
const namespacesJoined = getFallbackArray(namespaces).join(':');
|
|
21
|
+
|
|
22
|
+
const currentOptions = {
|
|
23
|
+
...defaultOptions,
|
|
24
|
+
...options,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// dummy state to enable re-rendering
|
|
28
|
+
const { rerender, instance } = useRerender();
|
|
29
|
+
|
|
30
|
+
const subscriptionRef = useRef<SubscriptionSelective>();
|
|
31
|
+
|
|
32
|
+
const subscriptionQueue = useRef([] as NsFallback[]);
|
|
33
|
+
subscriptionQueue.current = [];
|
|
34
|
+
|
|
35
|
+
const subscribeToNs = (ns: NsFallback) => {
|
|
36
|
+
subscriptionQueue.current.push(ns);
|
|
37
|
+
subscriptionRef.current?.subscribeNs(ns);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isLoaded = tolgee.isLoaded(namespaces);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const subscription = tolgee.onNsUpdate(rerender);
|
|
44
|
+
subscriptionRef.current = subscription;
|
|
45
|
+
if (!isLoaded) {
|
|
46
|
+
subscription.subscribeNs(namespaces);
|
|
47
|
+
}
|
|
48
|
+
subscriptionQueue.current.forEach((ns) => {
|
|
49
|
+
subscription!.subscribeNs(ns);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return () => {
|
|
53
|
+
subscription.unsubscribe();
|
|
54
|
+
};
|
|
55
|
+
}, [isLoaded, namespacesJoined, tolgee]);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
tolgee.addActiveNs(namespaces);
|
|
59
|
+
return () => tolgee.removeActiveNs(namespaces);
|
|
60
|
+
}, [namespacesJoined, tolgee]);
|
|
61
|
+
|
|
62
|
+
const t = useCallback(
|
|
63
|
+
(props: TranslateProps<any>) => {
|
|
64
|
+
const fallbackNs = props.ns ?? namespaces?.[0];
|
|
65
|
+
subscribeToNs(fallbackNs);
|
|
66
|
+
return tolgee.t({ ...props, ns: fallbackNs }) as any;
|
|
67
|
+
},
|
|
68
|
+
[tolgee, instance]
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (currentOptions.useSuspense && !isLoaded) {
|
|
72
|
+
throw tolgee.addActiveNs(namespaces, true);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { t, isLoading: !isLoaded };
|
|
76
|
+
};
|