Skip to content

Commit 9166dad

Browse files
committed
support localizations in dropdown component
1 parent 09e5504 commit 9166dad

File tree

4 files changed

+156
-9
lines changed

4 files changed

+156
-9
lines changed

components/dash-core-components/src/components/Dropdown.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
import React, {Component, lazy, Suspense} from 'react';
1+
import React, {lazy, Suspense} from 'react';
22
import {DropdownProps, PersistedProps, PersistenceTypes} from '../types';
33
import dropdown from '../utils/LazyLoader/dropdown';
44

55
const RealDropdown = lazy(dropdown);
66

7+
const defaultLocalizations: DropdownProps['localizations'] = {
8+
select_all: 'Select All',
9+
deselect_all: 'Deselect All',
10+
selected_count: '{num_selected} selected',
11+
search: 'Search',
12+
clear_search: 'Clear search',
13+
clear_selection: 'Clear selection',
14+
no_options_found: 'No options found',
15+
};
16+
717
/**
818
* Dropdown is an interactive dropdown element for selecting one or more
919
* items.
@@ -19,6 +29,7 @@ export default function Dropdown({
1929
disabled = false,
2030
multi = false,
2131
searchable = true,
32+
localizations = defaultLocalizations,
2233
// eslint-disable-next-line no-magic-numbers
2334
optionHeight = 36,
2435
// eslint-disable-next-line no-magic-numbers
@@ -30,11 +41,17 @@ export default function Dropdown({
3041
persistence_type = PersistenceTypes.local,
3142
...props
3243
}: DropdownProps) {
44+
localizations = {
45+
...defaultLocalizations,
46+
...localizations,
47+
};
48+
3349
return (
3450
<Suspense fallback={null}>
3551
<RealDropdown
3652
clearable={clearable}
3753
disabled={disabled}
54+
localizations={localizations}
3855
multi={multi}
3956
searchable={searchable}
4057
optionHeight={optionHeight}

components/dash-core-components/src/fragments/Dropdown.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ const Dropdown = (props: DropdownProps) => {
8787
closeOnSelect,
8888
clearable,
8989
disabled,
90+
localizations,
9091
maxHeight,
9192
multi,
9293
options,
@@ -459,7 +460,10 @@ const Dropdown = (props: DropdownProps) => {
459460
)}
460461
{sanitizedValues.length > 1 && (
461462
<span className="dash-dropdown-value-count">
462-
{sanitizedValues.length} selected
463+
{localizations?.selected_count?.replace(
464+
'{num_selected}',
465+
`${sanitizedValues.length}`
466+
)}
463467
</span>
464468
)}
465469
{clearable && !disabled && !!sanitizedValues.length && (
@@ -469,8 +473,8 @@ const Dropdown = (props: DropdownProps) => {
469473
e.preventDefault();
470474
handleClear();
471475
}}
472-
title="Clear selection"
473-
aria-label="Clear selection"
476+
title={localizations?.clear_selection}
477+
aria-label={localizations?.clear_selection}
474478
>
475479
<Cross1Icon />
476480
</a>
@@ -497,7 +501,7 @@ const Dropdown = (props: DropdownProps) => {
497501
<input
498502
type="search"
499503
className="dash-dropdown-search"
500-
placeholder="Search"
504+
placeholder={localizations?.search}
501505
value={search_value || ''}
502506
autoComplete="off"
503507
onChange={e =>
@@ -510,7 +514,7 @@ const Dropdown = (props: DropdownProps) => {
510514
type="button"
511515
className="dash-dropdown-clear"
512516
onClick={handleClearSearch}
513-
aria-label="Clear search"
517+
aria-label={localizations?.clear_search}
514518
>
515519
<Cross1Icon />
516520
</button>
@@ -524,15 +528,15 @@ const Dropdown = (props: DropdownProps) => {
524528
className="dash-dropdown-action-button"
525529
onClick={handleSelectAll}
526530
>
527-
Select All
531+
{localizations?.select_all}
528532
</button>
529533
{canDeselectAll && (
530534
<button
531535
type="button"
532536
className="dash-dropdown-action-button"
533537
onClick={handleDeselectAll}
534538
>
535-
Deselect All
539+
{localizations?.deselect_all}
536540
</button>
537541
)}
538542
</div>
@@ -562,7 +566,7 @@ const Dropdown = (props: DropdownProps) => {
562566
{search_value &&
563567
displayOptions.length === 0 && (
564568
<span className="dash-dropdown-option">
565-
No options found
569+
{localizations?.no_options_found}
566570
</span>
567571
)}
568572
</div>

components/dash-core-components/src/types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,19 @@ export interface DropdownProps {
467467
*/
468468
id?: string;
469469

470+
/**
471+
* Translations for customizing text contained within this component.
472+
*/
473+
localizations?: {
474+
select_all?: string;
475+
deselect_all?: string;
476+
selected_count?: string;
477+
search?: string;
478+
clear_search?: string;
479+
clear_selection?: string;
480+
no_options_found?: string;
481+
};
482+
470483
/**
471484
* Dash-assigned callback that gets fired when the input changes
472485
*/
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from dash import Dash
2+
from dash.dcc import Dropdown
3+
from dash.html import Div
4+
5+
6+
def test_ddlo001_translations(dash_duo):
7+
app = Dash(__name__)
8+
app.layout = Div(
9+
[
10+
Dropdown(
11+
id="dropdown",
12+
options=[1, 2, 3],
13+
multi=True,
14+
localizations={
15+
"select_all": "Sélectionner tout",
16+
"deselect_all": "Désélectionner tout",
17+
"selected_count": "{num_selected} sélections",
18+
"search": "Rechercher",
19+
"clear_search": "Annuler",
20+
"clear_selection": "Effacer les sélections",
21+
"no_options_found": "Aucun d'options",
22+
},
23+
),
24+
]
25+
)
26+
27+
dash_duo.start_server(app)
28+
29+
dash_duo.find_element("#dropdown").click()
30+
dash_duo.wait_for_contains_text(
31+
"#dropdown .dash-dropdown-action-button:first-child", "Sélectionner tout"
32+
)
33+
dash_duo.wait_for_contains_text(
34+
"#dropdown .dash-dropdown-action-button:last-child", "Désélectionner tout"
35+
)
36+
37+
search_input = dash_duo.find_element("#dropdown .dash-dropdown-search")
38+
assert search_input.accessible_name == "Rechercher"
39+
40+
search_input.send_keys(1)
41+
assert (
42+
dash_duo.find_element("#dropdown .dash-dropdown-clear").accessible_name
43+
== "Annuler"
44+
)
45+
46+
dash_duo.find_element("#dropdown .dash-dropdown-action-button:first-child").click()
47+
48+
search_input.send_keys(9)
49+
assert (
50+
dash_duo.find_element("#dropdown .dash-dropdown-option").text
51+
== "Aucun d'options"
52+
)
53+
54+
assert (
55+
dash_duo.find_element(
56+
"#dropdown .dash-dropdown-trigger .dash-dropdown-clear"
57+
).accessible_name
58+
== "Effacer les sélections"
59+
)
60+
61+
assert dash_duo.get_logs() == []
62+
63+
64+
def test_ddlo002_partial_translations(dash_duo):
65+
app = Dash(__name__)
66+
app.layout = Div(
67+
[
68+
Dropdown(
69+
id="dropdown",
70+
options=[1, 2, 3],
71+
multi=True,
72+
localizations={
73+
"search": "Lookup",
74+
},
75+
),
76+
]
77+
)
78+
79+
dash_duo.start_server(app)
80+
81+
dash_duo.find_element("#dropdown").click()
82+
dash_duo.wait_for_contains_text(
83+
"#dropdown .dash-dropdown-action-button:first-child", "Select All"
84+
)
85+
dash_duo.wait_for_contains_text(
86+
"#dropdown .dash-dropdown-action-button:last-child", "Deselect All"
87+
)
88+
89+
search_input = dash_duo.find_element("#dropdown .dash-dropdown-search")
90+
assert search_input.accessible_name == "Lookup"
91+
92+
search_input.send_keys(1)
93+
assert (
94+
dash_duo.find_element("#dropdown .dash-dropdown-clear").accessible_name
95+
== "Clear search"
96+
)
97+
98+
dash_duo.find_element("#dropdown .dash-dropdown-action-button:first-child").click()
99+
100+
search_input.send_keys(9)
101+
assert (
102+
dash_duo.find_element("#dropdown .dash-dropdown-option").text
103+
== "No options found"
104+
)
105+
106+
assert (
107+
dash_duo.find_element(
108+
"#dropdown .dash-dropdown-trigger .dash-dropdown-clear"
109+
).accessible_name
110+
== "Clear selection"
111+
)
112+
113+
assert dash_duo.get_logs() == []

0 commit comments

Comments
 (0)