Skip to content

Commit 162e03c

Browse files
committed
experiment with react-intl
1 parent 2ed5d26 commit 162e03c

File tree

10 files changed

+221
-26
lines changed

10 files changed

+221
-26
lines changed

interface/.env.development

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Change the IP address to that of your ESP device to enable local development of the UI.
22
# Remember to also enable CORS in platformio.ini before uploading the code to the device.
3-
REACT_APP_HTTP_ROOT=http://192.168.0.88
4-
REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.88
3+
REACT_APP_HTTP_ROOT=http://192.168.0.7
4+
REACT_APP_WEB_SOCKET_ROOT=ws://192.168.0.7

interface/package-lock.json

Lines changed: 97 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

interface/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"react-dom": "^16.13.1",
2424
"react-dropzone": "^11.0.1",
2525
"react-form-validator-core": "^0.6.4",
26+
"react-intl": "^5.8.1",
2627
"react-material-ui-form-validator": "^2.0.10",
2728
"react-router": "^5.1.2",
2829
"react-router-dom": "^5.1.2",

interface/src/App.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import AppRouting from './AppRouting';
99
import CustomMuiTheme from './CustomMuiTheme';
1010
import { PROJECT_NAME } from './api';
1111
import FeaturesWrapper from './features/FeaturesWrapper';
12+
import I18n from './i18n/I18n';
1213

1314
// this redirect forces a call to authenticationContext.refresh() which invalidates the JWT if it is invalid.
1415
const unauthorizedRedirect = () => <Redirect to="/" />;
@@ -35,12 +36,14 @@ class App extends Component {
3536
<CloseIcon />
3637
</IconButton>
3738
)}>
38-
<FeaturesWrapper>
39-
<Switch>
40-
<Route exact path="/unauthorized" component={unauthorizedRedirect} />
41-
<Route component={AppRouting} />
42-
</Switch>
43-
</FeaturesWrapper>
39+
<I18n>
40+
<FeaturesWrapper>
41+
<Switch>
42+
<Route exact path="/unauthorized" component={unauthorizedRedirect} />
43+
<Route component={AppRouting} />
44+
</Switch>
45+
</FeaturesWrapper>
46+
</I18n>
4447
</SnackbarProvider>
4548
</CustomMuiTheme>
4649
);

interface/src/SignIn.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { Paper, Typography, Fab } from '@material-ui/core';
77
import ForwardIcon from '@material-ui/icons/Forward';
88

99
import { withAuthenticationContext, AuthenticationContextProps } from './authentication/AuthenticationContext';
10-
import {PasswordValidator} from './components';
10+
import { PasswordValidator } from './components';
1111
import { PROJECT_NAME, SIGN_IN_ENDPOINT } from './api';
12+
import { FormattedMessage, injectIntl, WrappedComponentProps } from 'react-intl';
1213

1314
const styles = (theme: Theme) => createStyles({
1415
signInPage: {
@@ -39,7 +40,7 @@ const styles = (theme: Theme) => createStyles({
3940
}
4041
});
4142

42-
type SignInProps = WithSnackbarProps & WithStyles<typeof styles> & AuthenticationContextProps;
43+
type SignInProps = WrappedComponentProps & WithSnackbarProps & WithStyles<typeof styles> & AuthenticationContextProps;
4344

4445
interface SignInState {
4546
username: string,
@@ -68,7 +69,7 @@ class SignIn extends Component<SignInProps, SignInState> {
6869

6970
onSubmit = () => {
7071
const { username, password } = this.state;
71-
const { authenticationContext } = this.props;
72+
const { authenticationContext, intl } = this.props;
7273
this.setState({ processing: true });
7374
fetch(SIGN_IN_ENDPOINT, {
7475
method: 'POST',
@@ -81,9 +82,14 @@ class SignIn extends Component<SignInProps, SignInState> {
8182
if (response.status === 200) {
8283
return response.json();
8384
} else if (response.status === 401) {
84-
throw Error("Invalid credentials.");
85+
throw Error(intl.formatMessage(
86+
{ id: 'signIn.invalidCredentials', defaultMessage: 'Invalid credentials' })
87+
);
8588
} else {
86-
throw Error("Invalid status code: " + response.status);
89+
throw Error(intl.formatMessage(
90+
{ id: 'signIn.invalidStatusCode', defaultMessage: 'Invalid status code: {code}' },
91+
{ code: response.status })
92+
);
8793
}
8894
}).then(json => {
8995
authenticationContext.signIn(json.access_token);
@@ -98,7 +104,7 @@ class SignIn extends Component<SignInProps, SignInState> {
98104

99105
render() {
100106
const { username, password, processing } = this.state;
101-
const { classes } = this.props;
107+
const { classes, intl } = this.props;
102108
return (
103109
<div className={classes.signInPage}>
104110
<Paper className={classes.signInPanel}>
@@ -107,9 +113,9 @@ class SignIn extends Component<SignInProps, SignInState> {
107113
<TextValidator
108114
disabled={processing}
109115
validators={['required']}
110-
errorMessages={['Username is required']}
116+
errorMessages={[intl.formatMessage({ id: 'signIn.usernameRequired', defaultMessage: 'Username is required' })]}
111117
name="username"
112-
label="Username"
118+
label={[intl.formatMessage({ id: 'signIn.username', defaultMessage: 'Username' })]}
113119
fullWidth
114120
variant="outlined"
115121
value={username}
@@ -123,9 +129,9 @@ class SignIn extends Component<SignInProps, SignInState> {
123129
<PasswordValidator
124130
disabled={processing}
125131
validators={['required']}
126-
errorMessages={['Password is required']}
132+
errorMessages={[intl.formatMessage({ id: 'signIn.passwordRequired', defaultMessage: 'Password is required' })]}
127133
name="password"
128-
label="Password"
134+
label={[intl.formatMessage({ id: 'signIn.password', defaultMessage: 'Password' })]}
129135
fullWidth
130136
variant="outlined"
131137
value={password}
@@ -134,7 +140,7 @@ class SignIn extends Component<SignInProps, SignInState> {
134140
/>
135141
<Fab variant="extended" color="primary" className={classes.button} type="submit" disabled={processing}>
136142
<ForwardIcon className={classes.extendedIcon} />
137-
Sign In
143+
<FormattedMessage id="signIn.signIn" defaultMessage="Sign In" />
138144
</Fab>
139145
</ValidatorForm>
140146
</Paper>
@@ -144,4 +150,4 @@ class SignIn extends Component<SignInProps, SignInState> {
144150

145151
}
146152

147-
export default withAuthenticationContext(withSnackbar(withStyles(styles)(SignIn)));
153+
export default injectIntl(withAuthenticationContext(withSnackbar(withStyles(styles)(SignIn))));

interface/src/i18n/I18n.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { Component } from 'react';
2+
import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl';
3+
import { messages } from './messages';
4+
5+
const defaultLocale = "es";
6+
7+
const cache = createIntlCache()
8+
9+
export const intl = createIntl({
10+
locale: defaultLocale,
11+
defaultLocale: defaultLocale,
12+
messages: messages[defaultLocale]
13+
}, cache)
14+
15+
interface LanguageWrapperState {
16+
locale: string;
17+
};
18+
19+
class I18n extends Component<{}, LanguageWrapperState> {
20+
21+
state: LanguageWrapperState = { locale: defaultLocale };
22+
23+
// load locale from local storage here
24+
componentDidMount = () => {
25+
26+
}
27+
28+
selectLanguage = (locale: string) => {
29+
intl.locale = locale;
30+
intl.messages = messages[locale];
31+
this.setState({ locale })
32+
}
33+
34+
render() {
35+
const { locale } = this.state;
36+
return (
37+
<RawIntlProvider key={locale} value={intl}>
38+
{this.props.children}
39+
</RawIntlProvider>
40+
);
41+
}
42+
43+
}
44+
45+
export default I18n;

interface/src/i18n/messages.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { merge } from 'lodash';
2+
import { signInMessages } from '../messages';
3+
import { projectMessages } from '../project/messages';
4+
5+
export const messages: Record<string, Record<string, string>> = merge(
6+
signInMessages,
7+
projectMessages
8+
);

interface/src/messages.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const signInMessages: Record<string, Record<string, string>> = {
2+
es: {
3+
"signIn.invalidCredentials": "Credenciales no válidas",
4+
"signIn.invalidStatusCode": "Codigo invalido: {code}",
5+
"signIn.password": "Contraseña",
6+
"signIn.passwordRequired": "Se requiere contraseña",
7+
"signIn.signIn": "Registrarse",
8+
"signIn.username": "Nombre de usuario",
9+
"signIn.usernameRequired": "Se requiere nombre de usuario"
10+
}
11+
};

interface/src/project/DemoProject.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,37 @@ import DemoInformation from './DemoInformation';
1111
import LightStateRestController from './LightStateRestController';
1212
import LightStateWebSocketController from './LightStateWebSocketController';
1313
import LightMqttSettingsController from './LightMqttSettingsController';
14+
import { WrappedComponentProps, injectIntl } from 'react-intl';
1415

15-
class DemoProject extends Component<RouteComponentProps> {
16+
type DemoProjectProps = RouteComponentProps & WrappedComponentProps;
17+
18+
class DemoProject extends Component<DemoProjectProps> {
1619

1720
handleTabChange = (event: React.ChangeEvent<{}>, path: string) => {
1821
this.props.history.push(path);
1922
};
2023

2124
render() {
25+
const { intl } = this.props;
2226
return (
2327
<MenuAppBar sectionTitle="Demo Project">
2428
<Tabs value={this.props.match.url} onChange={this.handleTabChange} variant="fullWidth">
25-
<Tab value={`/${PROJECT_PATH}/demo/information`} label="Information" />
26-
<Tab value={`/${PROJECT_PATH}/demo/rest`} label="REST Controller" />
27-
<Tab value={`/${PROJECT_PATH}/demo/socket`} label="WebSocket Controller" />
28-
<Tab value={`/${PROJECT_PATH}/demo/mqtt`} label="MQTT Controller" />
29+
<Tab value={`/${PROJECT_PATH}/demo/information`} label={intl.formatMessage({
30+
id: 'project.information',
31+
defaultMessage: 'Information'
32+
})} />
33+
<Tab value={`/${PROJECT_PATH}/demo/rest`} label={intl.formatMessage({
34+
id: 'project.restController',
35+
defaultMessage: 'REST Controller'
36+
})} />
37+
<Tab value={`/${PROJECT_PATH}/demo/socket`} label={intl.formatMessage({
38+
id: 'project.webSocketController',
39+
defaultMessage: 'WebSocket Controller'
40+
})} />
41+
<Tab value={`/${PROJECT_PATH}/demo/mqtt`} label={intl.formatMessage({
42+
id: 'project.mqttController',
43+
defaultMessage: 'MQTT Controller'
44+
})} />
2945
</Tabs>
3046
<Switch>
3147
<AuthenticatedRoute exact path={`/${PROJECT_PATH}/demo/information`} component={DemoInformation} />
@@ -40,4 +56,4 @@ class DemoProject extends Component<RouteComponentProps> {
4056

4157
}
4258

43-
export default DemoProject;
59+
export default injectIntl(DemoProject);

0 commit comments

Comments
 (0)