Skip to content

Commit 970ee6a

Browse files
feat(js): introduce Translations API (#581)
Co-authored-by: François Chalifour <francoischalifour@users.noreply.github.com>
1 parent 2c10ede commit 970ee6a

File tree

8 files changed

+207
-3
lines changed

8 files changed

+207
-3
lines changed

packages/autocomplete-js/src/__tests__/api.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { createCollection } from '../../../../test/utils';
55
import { autocomplete } from '../autocomplete';
66

77
describe('api', () => {
8+
afterEach(() => {
9+
document.body.innerHTML = '';
10+
});
11+
812
describe('setActiveItemId', () => {
913
test('sets `activeItemId` value in the state', () => {
1014
const onStateChange = jest.fn();
@@ -271,6 +275,29 @@ describe('api', () => {
271275
).toBeInTheDocument();
272276
});
273277
});
278+
279+
test('overrides the default translations', () => {
280+
const container = document.createElement('div');
281+
document.body.appendChild(container);
282+
283+
const { update } = autocomplete<{ label: string }>({
284+
container,
285+
});
286+
287+
expect(
288+
document.querySelector<HTMLButtonElement>('.aa-SubmitButton')
289+
).toHaveAttribute('title', 'Submit');
290+
291+
update({
292+
translations: {
293+
submitButtonTitle: 'Envoyer',
294+
},
295+
});
296+
297+
expect(
298+
document.querySelector<HTMLButtonElement>('.aa-SubmitButton')
299+
).toHaveAttribute('title', 'Envoyer');
300+
});
274301
});
275302

276303
describe('destroy', () => {
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { waitFor } from '@testing-library/dom';
2+
3+
import { autocomplete } from '../autocomplete';
4+
5+
describe('translations', () => {
6+
afterEach(() => {
7+
document.body.innerHTML = '';
8+
});
9+
10+
describe('regular DOM', () => {
11+
test('provides default translations', () => {
12+
const container = document.createElement('div');
13+
document.body.appendChild(container);
14+
15+
autocomplete<{ label: string }>({
16+
container,
17+
});
18+
19+
expect(
20+
document.querySelector<HTMLButtonElement>('.aa-ClearButton')
21+
).toHaveAttribute('title', 'Clear');
22+
expect(
23+
document.querySelector<HTMLButtonElement>('.aa-SubmitButton')
24+
).toHaveAttribute('title', 'Submit');
25+
});
26+
27+
test('allows custom translations', () => {
28+
const container = document.createElement('div');
29+
document.body.appendChild(container);
30+
31+
autocomplete<{ label: string }>({
32+
container,
33+
translations: {
34+
clearButtonTitle: 'Effacer',
35+
submitButtonTitle: 'Envoyer',
36+
},
37+
});
38+
39+
expect(
40+
document.querySelector<HTMLButtonElement>('.aa-ClearButton')
41+
).toHaveAttribute('title', 'Effacer');
42+
expect(
43+
document.querySelector<HTMLButtonElement>('.aa-SubmitButton')
44+
).toHaveAttribute('title', 'Envoyer');
45+
});
46+
});
47+
48+
describe('detached DOM', () => {
49+
const originalMatchMedia = window.matchMedia;
50+
51+
beforeAll(() => {
52+
Object.defineProperty(window, 'matchMedia', {
53+
writable: true,
54+
value: jest.fn((query) => ({
55+
matches: true,
56+
media: query,
57+
onchange: null,
58+
addListener: jest.fn(),
59+
removeListener: jest.fn(),
60+
addEventListener: jest.fn(),
61+
removeEventListener: jest.fn(),
62+
dispatchEvent: jest.fn(),
63+
})),
64+
});
65+
});
66+
67+
afterAll(() => {
68+
Object.defineProperty(window, 'matchMedia', {
69+
writable: true,
70+
value: originalMatchMedia,
71+
});
72+
});
73+
74+
test('provides default translations', async () => {
75+
const container = document.createElement('div');
76+
document.body.appendChild(container);
77+
78+
const { destroy } = autocomplete<{ label: string }>({
79+
container,
80+
detachedMediaQuery: '',
81+
});
82+
83+
const searchButton = container.querySelector<HTMLButtonElement>(
84+
'.aa-DetachedSearchButton'
85+
);
86+
87+
searchButton.click();
88+
89+
await waitFor(() => {
90+
expect(
91+
document.querySelector('.aa-DetachedOverlay')
92+
).toBeInTheDocument();
93+
});
94+
95+
expect(
96+
document.querySelector<HTMLButtonElement>('.aa-ClearButton')
97+
).toHaveAttribute('title', 'Clear');
98+
expect(
99+
document.querySelector<HTMLButtonElement>('.aa-SubmitButton')
100+
).toHaveAttribute('title', 'Submit');
101+
expect(
102+
document.querySelector<HTMLButtonElement>('.aa-DetachedCancelButton')
103+
).toHaveTextContent('Cancel');
104+
105+
destroy();
106+
});
107+
108+
test('allows custom translations', async () => {
109+
const container = document.createElement('div');
110+
document.body.appendChild(container);
111+
112+
const { destroy } = autocomplete({
113+
container,
114+
detachedMediaQuery: '',
115+
translations: {
116+
clearButtonTitle: 'Effacer',
117+
detachedCancelButtonText: 'Annuler',
118+
submitButtonTitle: 'Envoyer',
119+
},
120+
});
121+
122+
const searchButton = container.querySelector<HTMLButtonElement>(
123+
'.aa-DetachedSearchButton'
124+
);
125+
126+
searchButton.click();
127+
128+
await waitFor(() => {
129+
expect(
130+
document.querySelector('.aa-DetachedOverlay')
131+
).toBeInTheDocument();
132+
});
133+
134+
expect(
135+
document.querySelector<HTMLButtonElement>('.aa-ClearButton')
136+
).toHaveAttribute('title', 'Effacer');
137+
expect(
138+
document.querySelector<HTMLButtonElement>('.aa-SubmitButton')
139+
).toHaveAttribute('title', 'Envoyer');
140+
expect(
141+
document.querySelector<HTMLButtonElement>('.aa-DetachedCancelButton')
142+
).toHaveTextContent('Annuler');
143+
144+
destroy();
145+
});
146+
});
147+
});

packages/autocomplete-js/src/autocomplete.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export function autocomplete<TItem extends BaseItem>(
118118
propGetters,
119119
setIsModalOpen,
120120
state: lastStateRef.current,
121+
translations: props.value.renderer.translations,
121122
})
122123
);
123124

packages/autocomplete-js/src/createAutocompleteDom.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
AutocompleteDom,
1313
AutocompletePropGetters,
1414
AutocompleteState,
15+
AutocompleteTranslations,
1516
} from './types';
1617
import { setProperties } from './utils';
1718

@@ -25,6 +26,7 @@ type CreateDomProps<TItem extends BaseItem> = {
2526
propGetters: AutocompletePropGetters<TItem>;
2627
setIsModalOpen(value: boolean): void;
2728
state: AutocompleteState<TItem>;
29+
translations: AutocompleteTranslations;
2830
};
2931

3032
export function createAutocompleteDom<TItem extends BaseItem>({
@@ -37,6 +39,7 @@ export function createAutocompleteDom<TItem extends BaseItem>({
3739
propGetters,
3840
setIsModalOpen,
3941
state,
42+
translations,
4043
}: CreateDomProps<TItem>): AutocompleteDom {
4144
const createDomElement = getCreateDomElement(environment);
4245

@@ -72,7 +75,7 @@ export function createAutocompleteDom<TItem extends BaseItem>({
7275
const submitButton = createDomElement('button', {
7376
class: classNames.submitButton,
7477
type: 'submit',
75-
title: 'Submit',
78+
title: translations.submitButtonTitle,
7679
children: [SearchIcon({ environment })],
7780
});
7881
const label = createDomElement('label', {
@@ -83,7 +86,7 @@ export function createAutocompleteDom<TItem extends BaseItem>({
8386
const clearButton = createDomElement('button', {
8487
class: classNames.clearButton,
8588
type: 'reset',
86-
title: 'Clear',
89+
title: translations.clearButtonTitle,
8790
children: [ClearIcon({ environment })],
8891
});
8992
const loadingIndicator = createDomElement('div', {
@@ -164,7 +167,7 @@ export function createAutocompleteDom<TItem extends BaseItem>({
164167
});
165168
const detachedCancelButton = createDomElement('button', {
166169
class: classNames.detachedCancelButton,
167-
textContent: 'Cancel',
170+
textContent: translations.detachedCancelButtonText,
168171
onClick() {
169172
autocomplete.setIsOpen(false);
170173
setIsModalOpen(false);

packages/autocomplete-js/src/getDefaultOptions.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
AutocompleteOptions,
2222
AutocompleteRender,
2323
AutocompleteRenderer,
24+
AutocompleteTranslations,
2425
} from './types';
2526
import { getHTMLElement, mergeClassNames } from './utils';
2627

@@ -82,6 +83,7 @@ export function getDefaultOptions<TItem extends BaseItem>(
8283
renderer,
8384
detachedMediaQuery,
8485
components,
86+
translations,
8587
...core
8688
} = options;
8789

@@ -104,6 +106,11 @@ export function getDefaultOptions<TItem extends BaseItem>(
104106
ReverseSnippet: createReverseSnippetComponent(defaultedRenderer),
105107
Snippet: createSnippetComponent(defaultedRenderer),
106108
};
109+
const defaultTranslations: AutocompleteTranslations = {
110+
clearButtonTitle: 'Clear',
111+
detachedCancelButtonText: 'Cancel',
112+
submitButtonTitle: 'Submit',
113+
};
107114

108115
return {
109116
renderer: {
@@ -136,6 +143,10 @@ export function getDefaultOptions<TItem extends BaseItem>(
136143
...defaultComponents,
137144
...components,
138145
},
146+
translations: {
147+
...defaultTranslations,
148+
...translations,
149+
},
139150
},
140151
core: {
141152
...core,

packages/autocomplete-js/src/types/AutocompleteOptions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { AutocompleteRender } from './AutocompleteRender';
1414
import { AutocompleteRenderer } from './AutocompleteRenderer';
1515
import { AutocompleteSource } from './AutocompleteSource';
1616
import { AutocompleteState } from './AutocompleteState';
17+
import { AutocompleteTranslations } from './AutocompleteTranslations';
1718

1819
export interface OnStateChangeProps<TItem extends BaseItem>
1920
extends AutocompleteScopeApi<TItem> {
@@ -108,4 +109,12 @@ export interface AutocompleteOptions<TItem extends BaseItem>
108109
* @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-components
109110
*/
110111
components?: PublicAutocompleteComponents;
112+
/**
113+
* A mapping of translation strings.
114+
*
115+
* Defaults to English values.
116+
*
117+
* @link https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/#param-translations
118+
*/
119+
translations?: Partial<AutocompleteTranslations>;
111120
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type AutocompleteTranslations = {
2+
detachedCancelButtonText: string;
3+
clearButtonTitle: string;
4+
submitButtonTitle: string;
5+
};

packages/autocomplete-js/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export * from './AutocompleteRender';
1010
export * from './AutocompleteRenderer';
1111
export * from './AutocompleteSource';
1212
export * from './AutocompleteState';
13+
export * from './AutocompleteTranslations';
1314
export * from './HighlightHitParams';

0 commit comments

Comments
 (0)