Skip to content

Commit 337c380

Browse files
Add localization support (GH-17)
2 parents be4e051 + 1b5b6f1 commit 337c380

13 files changed

+109
-22
lines changed

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,23 @@ export default Demo;
7474
7575
The `valid` function primarily checks if a phone number has a length appropriate for its specified country. In addition, a more comprehensive validation can be performed, including verifying the dial and area codes' accuracy for the selected country. To activate the strict validation, pass `true` as the first argument to the `valid` function.
7676
77+
## Localization
78+
79+
The package provides a built-in localization feature that allows you to change the language of the component. The `locale` function returns the language object that can be passed to the `createTheme` function of Material UI.
80+
81+
```javascript
82+
import {createTheme, ThemeProvider} from "@mui/material/styles";
83+
import PhoneInput, {locale} from "mui-phone-input";
84+
85+
const theme = createTheme({}, locale("frFR"));
86+
87+
<ThemeProvider theme={theme}>
88+
<PhoneInput/>
89+
</ThemeProvider>
90+
```
91+
92+
NOTE: If you use localization in the [documented](https://mui.com/material-ui/guides/localization/) way, you should replace the language object with the `locale` function, specifying the desired language code.
93+
7794
## Props
7895
7996
Apart from the phone-specific properties described below, all [Input](https://mui.com/material-ui/api/input/#props) and [TextField](https://mui.com/material-ui/api/text-field/#props) properties supported by the used Material distribution can be applied to the phone input component.

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.1.2",
2+
"version": "0.1.3",
33
"name": "mui-phone-input",
44
"description": "Advanced, highly customizable phone input component for Material UI.",
55
"keywords": [
@@ -30,14 +30,15 @@
3030
"index*",
3131
"types*",
3232
"styles*",
33+
"locale*",
3334
"LICENSE",
3435
"resources",
3536
"README.md"
3637
],
3738
"scripts": {
3839
"rename": "bash -c 'for file in *.js; do mv $file \"${file%.js}.$0.js\"; done'",
3940
"build": "tsc --module commonjs && npm run rename -- cjs && tsc --declaration",
40-
"prebuild": "rm -r joy base core resources index* types* styles* || true",
41+
"prebuild": "rm -r joy base core resources index* locale* types* styles* || true",
4142
"postpack": "tsx scripts/prepare-package.ts",
4243
"test": "jest --config jestconfig.json",
4344
"postbuild": "cp -r src/resources ."
@@ -47,7 +48,7 @@
4748
"react": "^16.8.6 || ^17.0.0 || ^18.0.0"
4849
},
4950
"dependencies": {
50-
"react-phone-hooks": "^0.1.5"
51+
"react-phone-hooks": "^0.1.6"
5152
},
5253
"devDependencies": {
5354
"@emotion/styled": "^11.11.0",

src/base/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,4 @@ const PhoneInput = forwardRef(({
145145
})
146146

147147
export default PhoneInput;
148+
export {PhoneInputProps, PhoneNumber};

src/core/index.tsx

+12-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import {ChangeEvent, forwardRef, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState} from "react";
44
import {InputAdornment, MenuItem, Select, TextField} from "@material-ui/core";
5+
import {getThemeProps, useTheme} from "@material-ui/styles";
56

67
import {
78
checkValidity,
@@ -16,6 +17,7 @@ import {
1617
usePhone,
1718
} from "react-phone-hooks";
1819

20+
import locale from "./locale";
1921
import {injectMergedStyles} from "./styles";
2022
import {PhoneInputProps, PhoneNumber} from "./types";
2123

@@ -33,8 +35,8 @@ const PhoneInput = forwardRef(({
3335
onlyCountries = [],
3436
excludeCountries = [],
3537
preferredCountries = [],
36-
searchNotFound = "No country found",
37-
searchPlaceholder = "Search country",
38+
searchNotFound: defaultSearchNotFound = "No country found",
39+
searchPlaceholder: defaultSearchPlaceholder = "Search country",
3840
onMount: handleMount = () => null,
3941
onInput: handleInput = () => null,
4042
onChange: handleChange = () => null,
@@ -50,6 +52,12 @@ const PhoneInput = forwardRef(({
5052
const [maxWidth, setMaxWidth] = useState<number>(0);
5153
const [countryCode, setCountryCode] = useState<string>(country);
5254

55+
const {
56+
searchNotFound = defaultSearchNotFound,
57+
searchPlaceholder = defaultSearchPlaceholder,
58+
countries = new Proxy({}, ({get: (_: any, prop: any) => prop})),
59+
} = getThemeProps({props: {}, name: "MuiPhoneInput", theme: useTheme()}) as any;
60+
5361
const {
5462
value,
5563
pattern,
@@ -175,7 +183,7 @@ const PhoneInput = forwardRef(({
175183
children={<div className="mui-phone-input-select-item">
176184
<div className={`flag ${iso}`}/>
177185
<div className="label">
178-
{name}&nbsp;{displayFormat(mask)}
186+
{countries[name]}&nbsp;{displayFormat(mask)}
179187
</div>
180188
</div>}
181189
/>
@@ -213,3 +221,4 @@ const PhoneInput = forwardRef(({
213221
})
214222

215223
export default PhoneInput;
224+
export {PhoneInputProps, PhoneNumber, locale};

src/core/locale.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as locale from "@material-ui/core/locale";
2+
import * as phoneLocale from "react-phone-hooks/locale";
3+
4+
type Locale = keyof typeof locale;
5+
6+
export default (lang: Locale) => ({
7+
props: {
8+
...locale[lang].props,
9+
MuiPhoneInput: (phoneLocale as any)[lang],
10+
}
11+
})

src/index.tsx

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import {ChangeEvent, forwardRef, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState} from "react";
4-
import {InputAdornment, MenuItem, Select, TextField} from "@mui/material";
4+
import {InputAdornment, MenuItem, Select, TextField, useThemeProps} from "@mui/material";
55

66
import {
77
checkValidity,
@@ -16,6 +16,7 @@ import {
1616
usePhone,
1717
} from "react-phone-hooks";
1818

19+
import locale from "./locale";
1920
import {injectMergedStyles} from "./styles";
2021
import {PhoneInputProps, PhoneNumber} from "./types";
2122

@@ -33,8 +34,8 @@ const PhoneInput = forwardRef(({
3334
onlyCountries = [],
3435
excludeCountries = [],
3536
preferredCountries = [],
36-
searchNotFound = "No country found",
37-
searchPlaceholder = "Search country",
37+
searchNotFound: defaultSearchNotFound = "No country found",
38+
searchPlaceholder: defaultSearchPlaceholder = "Search country",
3839
onMount: handleMount = () => null,
3940
onInput: handleInput = () => null,
4041
onChange: handleChange = () => null,
@@ -50,6 +51,12 @@ const PhoneInput = forwardRef(({
5051
const [maxWidth, setMaxWidth] = useState<number>(0);
5152
const [countryCode, setCountryCode] = useState<string>(country);
5253

54+
const {
55+
searchNotFound = defaultSearchNotFound,
56+
searchPlaceholder = defaultSearchPlaceholder,
57+
countries = new Proxy({}, ({get: (_: any, prop: any) => prop})),
58+
} = useThemeProps({props: {}, name: "MuiPhoneInput"}) as any;
59+
5360
const {
5461
value,
5562
pattern,
@@ -171,7 +178,7 @@ const PhoneInput = forwardRef(({
171178
children={<div className="mui-phone-input-select-item">
172179
<div className={`flag ${iso}`}/>
173180
<div className="label">
174-
{name}&nbsp;{displayFormat(mask)}
181+
{countries[name]}&nbsp;{displayFormat(mask)}
175182
</div>
176183
</div>}
177184
/>
@@ -209,3 +216,4 @@ const PhoneInput = forwardRef(({
209216
})
210217

211218
export default PhoneInput;
219+
export {PhoneInputProps, PhoneNumber, locale};

src/joy/index.tsx

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import {ChangeEvent, forwardRef, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState} from "react";
4-
import {Input, Option, Select} from "@mui/joy";
4+
import {Input, Option, Select, useThemeProps} from "@mui/joy";
55

66
import {
77
checkValidity,
@@ -16,6 +16,7 @@ import {
1616
usePhone,
1717
} from "react-phone-hooks";
1818

19+
import locale from "../locale";
1920
import {injectMergedStyles} from "./styles";
2021
import {PhoneInputProps, PhoneNumber} from "./types";
2122

@@ -33,8 +34,8 @@ const PhoneInput = forwardRef(({
3334
onlyCountries = [],
3435
excludeCountries = [],
3536
preferredCountries = [],
36-
searchNotFound = "No country found",
37-
searchPlaceholder = "Search country",
37+
searchNotFound: defaultSearchNotFound = "No country found",
38+
searchPlaceholder: defaultSearchPlaceholder = "Search country",
3839
onMount: handleMount = () => null,
3940
onInput: handleInput = () => null,
4041
onChange: handleChange = () => null,
@@ -50,6 +51,12 @@ const PhoneInput = forwardRef(({
5051
const [maxWidth, setMaxWidth] = useState<number>(0);
5152
const [countryCode, setCountryCode] = useState<string>(country);
5253

54+
const {
55+
searchNotFound = defaultSearchNotFound,
56+
searchPlaceholder = defaultSearchPlaceholder,
57+
countries = new Proxy({}, ({get: (_: any, prop: any) => prop})),
58+
} = useThemeProps({props: {}, name: "MuiPhoneInput"}) as any;
59+
5360
const {
5461
value,
5562
pattern,
@@ -168,7 +175,7 @@ const PhoneInput = forwardRef(({
168175
children={<div className="mui-phone-input-select-item">
169176
<div className={`flag ${iso}`}/>
170177
<div className="label">
171-
{name}&nbsp;{displayFormat(mask)}
178+
{countries[name]}&nbsp;{displayFormat(mask)}
172179
</div>
173180
</div>}
174181
/>
@@ -202,3 +209,4 @@ const PhoneInput = forwardRef(({
202209
})
203210

204211
export default PhoneInput;
212+
export {PhoneInputProps, PhoneNumber, locale};

src/locale.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as locale from "@mui/material/locale";
2+
import * as phoneLocale from "react-phone-hooks/locale";
3+
4+
type Locale = keyof typeof locale;
5+
6+
export default (lang: Locale) => ({
7+
components: {
8+
...locale[lang].components,
9+
MuiPhoneInput: {
10+
defaultProps: (phoneLocale as any)[lang],
11+
}
12+
}
13+
})

tests/base.test.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {Button} from "@mui/base";
2+
import {ThemeProvider} from "@mui/system";
23

34
import commonTests from "./common";
45
import PhoneInput from "../src/base";
56

6-
commonTests(PhoneInput, Button);
7+
commonTests(PhoneInput, Button, null, ThemeProvider);

tests/common.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const FormItem = ({children, register, name, ...props}: any) => {
4141
return cloneElement(children, {...inputProps, onChange, ...props});
4242
}
4343

44-
export default function commonTests(PhoneInput: any, Button: any) {
44+
export default function commonTests(PhoneInput: any, Button: any, theme: any, ThemeProvider: any) {
4545
describe("Checking the basic rendering and functionality", () => {
4646
it("Rendering without crashing", () => {
4747
render(<PhoneInput/>);
@@ -52,6 +52,17 @@ export default function commonTests(PhoneInput: any, Button: any) {
5252
assert(screen.getByDisplayValue("+1 (702) 123 4567"));
5353
})
5454

55+
it("Localization support check", async () => {
56+
if (theme === null) return; // Skip for @mui/base distribution
57+
const {container, getByText} = render(<ThemeProvider theme={theme}>
58+
<PhoneInput onlyCountries={["am"]}/>
59+
</ThemeProvider>);
60+
await act(async () => {
61+
await userEvent.click(container.querySelector(".flag") as any);
62+
});
63+
assert(!!getByText(/Arménie[\S\s]+\+374/));
64+
})
65+
5566
it("Rendering with an initial value", () => {
5667
render(<PhoneInput
5768
onMount={(value: any) => {

tests/core.test.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {Button} from "@material-ui/core";
2+
import {createTheme, ThemeProvider} from "@material-ui/core/styles";
23

34
import commonTests from "./common";
4-
import PhoneInput from "../src/core";
5+
import PhoneInput, {locale} from "../src/core";
56

6-
commonTests(PhoneInput, Button);
7+
const theme = createTheme({}, locale("frFR"));
8+
commonTests(PhoneInput, Button, theme, ThemeProvider);

tests/joy.test.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import {Button} from "@mui/joy";
2+
import {createTheme} from "@mui/system";
3+
import {ThemeProvider} from "@mui/joy/styles";
24

35
import commonTests from "./common";
4-
import PhoneInput from "../src/joy";
6+
import PhoneInput, {locale} from "../src/joy";
57

6-
commonTests(PhoneInput, Button);
8+
const theme = createTheme({}, locale("frFR"));
9+
commonTests(PhoneInput, Button, theme, ThemeProvider);

tests/material.test.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {Button} from "@mui/material";
2+
import {createTheme, ThemeProvider} from "@mui/material/styles";
23

34
import commonTests from "./common";
4-
import PhoneInput from "../src";
5+
import PhoneInput, {locale} from "../src";
56

6-
commonTests(PhoneInput, Button);
7+
const theme = createTheme({}, locale("frFR"));
8+
commonTests(PhoneInput, Button, theme, ThemeProvider);

0 commit comments

Comments
 (0)