Skip to content

Commit 90ea02f

Browse files
committed
add i18n uk and us locales, translation
1 parent 9ea0b76 commit 90ea02f

15 files changed

+315
-66
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@
2121
"@types/recompose": "^0.30.6",
2222
"d3": "^5.9.2",
2323
"d3-selection-multi": "^1.0.1",
24+
"i18next": "^15.1.3",
25+
"i18next-browser-languagedetector": "^3.0.1",
26+
"i18next-xhr-backend": "^2.0.1",
2427
"lodash": "^4.17.11",
2528
"ml-matrix": "^6.0.0",
2629
"ml-pca": "^3.0.0",
2730
"papaparse": "^5.0.0",
2831
"react": "^16.8.6",
2932
"react-dom": "^16.8.6",
33+
"react-i18next": "^10.11.0",
3034
"react-router-dom": "^5.0.0",
3135
"react-scripts-ts": "3.1.0",
3236
"recompose": "^0.30.0"

public/locales/en/translation.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"navigation": {
3+
"/": "Home",
4+
"/pca": "PCA",
5+
"/som": "SOM",
6+
"/docs": "Docs"
7+
},
8+
"pages": {
9+
"home": {
10+
"title": "What is reduction?",
11+
"description": "In statistics, machine learning, and information theory, dimensionality reduction or dimension reduction is the process of reducing the number of random variables under consideration by obtaining a set of principal variables. It can be divided into feature selection and feature extraction.",
12+
"pca": {
13+
"title": "Principal Component Analysis",
14+
"description": "A statistical procedure that uses an orthogonal transformation to convert a set of observations of possibly correlated variables (entities each of which takes on various numerical values) into a set of values of linearly uncorrelated variables called principal components.",
15+
"button": "Try it"
16+
},
17+
"som": {
18+
"title": "Self-Organizing Maps",
19+
"description": "A type of artificial neural network (ANN) that is trained using unsupervised learning to produce a low-dimensional (typically two-dimensional), discretized representation of the input space of the training samples, called a map, and is therefore a method to do dimensionality reduction.",
20+
"button": "Try it"
21+
}
22+
},
23+
"pca": {
24+
"title": "Principal Component Analysis",
25+
"description": "Process two-dimensional data arrays using principal component analysis.",
26+
"messages": {
27+
"uploaded": "The dataset is uploaded. Press on the submit button."
28+
}
29+
},
30+
"som": {
31+
"title": "Self-Organizing Maps",
32+
"description": "Process two-dimensional data arrays using self-organizing maps.",
33+
"messages": {
34+
"uploaded": "The dataset is uploaded. Press on the submit button."
35+
}
36+
}
37+
},
38+
"controls": {
39+
"upload": {
40+
"button": {
41+
"choose": "choose a file",
42+
"upload": "upload"
43+
}
44+
}
45+
},
46+
"changeLanguage": "Change language"
47+
}

public/locales/uk/translation.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"navigation": {
3+
"/": "Домашня",
4+
"/pca": "МГК",
5+
"/som": "СКК",
6+
"/docs": "Документація"
7+
},
8+
"pages": {
9+
"home": {
10+
"title": "Що таке редукція?",
11+
"description": "У статистиці, машинному навчанні та теорії інформації зниження розмірності є процесом скорочення кількості випадкових змінних шляхом отримання множини головних змінних. Цей процес можна поділити на обирання ознак та виділяння ознак.",
12+
"pca": {
13+
"title": "Метод головних компонент",
14+
"description": "Метод факторного аналізу в статистиці, який використовує ортогональне перетворення множини спостережень з можливо пов'язаними змінними (сутностями, кожна з яких набуває різних числових значень) у множину змінних без лінійної кореляції, які називаються головними компонентами.",
15+
"button": "Спробувати"
16+
},
17+
"som": {
18+
"title": "Самоорганізаційна Карта Кохонена",
19+
"description": "Нейронна мережа з некерованим навчанням, яка використовується для проектування багатовимірного простору в простір з нижчою розмірністю (найчастіше, двовимірний). Створює дискретне представлення вхідних просторів навчальних вибірок, які називаються картою (англ. map), і тому використання цього типу нейронної мережі є методом для зниження розмірності.",
20+
"button": "Спробувати"
21+
}
22+
},
23+
"pca": {
24+
"title": "Метод головних компонент",
25+
"description": "Обробка двомірних масивів даних з використанням методу головних компонент.",
26+
"messages": {
27+
"uploaded": "Набір даних завантажено. Натисніть кнопку надіслати."
28+
}
29+
},
30+
"som": {
31+
"title": "Самоорганізаційна Карта Кохонена",
32+
"description": "Обробка двомірних масивів даних з використанням самоорганізаційної карти Кохонена."
33+
}
34+
},
35+
"controls": {
36+
"upload": {
37+
"button": {
38+
"choose": "вибрати файл",
39+
"upload": "завантажити"
40+
}
41+
}
42+
},
43+
"changeLanguage": "Змінити мову"
44+
}

src/components/Header.tsx

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,26 @@ import Button from "@material-ui/core/Button";
33
import Divider from "@material-ui/core/Divider";
44
import Drawer from "@material-ui/core/Drawer";
55
import Grid from "@material-ui/core/Grid";
6+
import Typography from '@material-ui/core/Typography';
67
import Hidden from "@material-ui/core/Hidden";
78
import IconButton from "@material-ui/core/IconButton";
9+
import IconLanguage from '@material-ui/icons/Language';
810
import List from "@material-ui/core/List";
911
import ListItem from "@material-ui/core/ListItem";
1012
import ListItemIcon from "@material-ui/core/ListItemIcon";
1113
import ListItemText from "@material-ui/core/ListItemText";
12-
import { Theme } from "@material-ui/core/styles";
1314
import Toolbar from "@material-ui/core/Toolbar";
15+
import Tooltip from "@material-ui/core/Tooltip";
1416
import MenuIcon from "@material-ui/icons/Menu";
17+
import Popover from '@material-ui/core/Popover';
1518
import { makeStyles } from "@material-ui/styles";
1619
import React, { useState } from "react";
1720
import { Link } from "react-router-dom";
21+
import { useTranslation } from 'react-i18next';
1822
import { withRouter, RouteComponentProps } from "react-router-dom";
1923
import { IRoute } from "../router";
2024

21-
const useStyles = makeStyles(({ breakpoints }: Theme) => ({
25+
const useStyles = makeStyles({
2226
menuButton: {
2327
marginLeft: -12
2428
},
@@ -28,18 +32,36 @@ const useStyles = makeStyles(({ breakpoints }: Theme) => ({
2832
right: {
2933
marginLeft: "auto"
3034
}
31-
}));
35+
});
3236

3337
interface IHeaderProps extends RouteComponentProps {
3438
routes: IRoute[];
3539
}
3640

3741
export const HeaderBase: React.FC<IHeaderProps> = ({ routes, location }) => {
3842
const classes = useStyles();
43+
const { t, i18n } = useTranslation();
3944
const [isOpenDrawer, toggleDrawer] = useState<boolean>(false);
45+
const [anchorEl, setAnchorEl] = React.useState<Element | null>(null);
4046

41-
const links = routes.map(({ path, title }, i) => {
42-
if (path && title) {
47+
function handleClick(event: any) {
48+
setAnchorEl(event.currentTarget);
49+
}
50+
51+
function handleClose() {
52+
setAnchorEl(null);
53+
}
54+
55+
function handleChangeLanguage(lng: string) {
56+
i18n.changeLanguage(lng);
57+
handleClose();
58+
}
59+
60+
const open = Boolean(anchorEl);
61+
const id = open ? 'lng-popover' : undefined;
62+
63+
const links = routes.map(({ path }, i) => {
64+
if (path) {
4365
return (
4466
<Button
4567
key={i}
@@ -51,16 +73,16 @@ export const HeaderBase: React.FC<IHeaderProps> = ({ routes, location }) => {
5173
variant="text"
5274
color="inherit"
5375
>
54-
{title || ""}
76+
{t(`navigation.${path}`) || ""}
5577
</Button>
5678
);
5779
}
5880

5981
return null;
6082
});
6183

62-
const drawerLinks = routes.map(({ path, title, icon }, i) => {
63-
if (path && title && icon) {
84+
const drawerLinks = routes.map(({ path, icon }, i) => {
85+
if (path && icon) {
6486
const Icon = icon;
6587

6688
return (
@@ -76,7 +98,7 @@ export const HeaderBase: React.FC<IHeaderProps> = ({ routes, location }) => {
7698
<ListItemIcon>
7799
<Icon color={path === location.pathname ? "primary" : "inherit"} />
78100
</ListItemIcon>
79-
<ListItemText primary={title} />
101+
<ListItemText primary={t(`navigation.${path}`)} />
80102
</ListItem>
81103
);
82104
}
@@ -102,6 +124,38 @@ export const HeaderBase: React.FC<IHeaderProps> = ({ routes, location }) => {
102124
<Hidden smDown={true}>
103125
<div className={classes.right}>{links}</div>
104126
</Hidden>
127+
<Tooltip title={t('changeLanguage')}>
128+
<IconButton color="inherit" aria-describedby={id} onClick={handleClick}>
129+
<IconLanguage color="inherit" />
130+
</IconButton>
131+
</Tooltip>
132+
<Popover
133+
id={id}
134+
open={open}
135+
anchorEl={anchorEl}
136+
onClose={handleClose}
137+
anchorOrigin={{
138+
vertical: 'bottom',
139+
horizontal: 'center',
140+
}}
141+
transformOrigin={{
142+
vertical: 'top',
143+
horizontal: 'center',
144+
}}
145+
>
146+
<List component="nav" dense={true}>
147+
<ListItem button={true} onClick={() => handleChangeLanguage('en')}>
148+
<ListItemText disableTypography={true}>
149+
<Typography component="span"><small>us</small> English</Typography>
150+
</ListItemText>
151+
</ListItem>
152+
<ListItem button={true} onClick={() => handleChangeLanguage('uk')}>
153+
<ListItemText disableTypography={true}>
154+
<Typography component="span"><small>uk</small> Українська</Typography>
155+
</ListItemText>
156+
</ListItem>
157+
</List>
158+
</Popover>
105159
</Grid>
106160
</Toolbar>
107161
</AppBar>

src/components/controls/UploadControls.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import CircularProgress from "@material-ui/core/CircularProgress";
55
import Grid from "@material-ui/core/Grid";
66
import CloudUploadIcon from "@material-ui/icons/CloudUpload";
77
import { makeStyles } from "@material-ui/styles";
8+
import { useTranslation } from 'react-i18next';
9+
810
import React from "react";
911

1012
const useStyles = makeStyles((theme: Theme) => ({
@@ -48,6 +50,7 @@ export const UploadControls: React.FC<IUploadControlsProps> = ({
4850
}) => {
4951
const classes = useStyles();
5052
const fileInput: React.RefObject<HTMLInputElement> = React.createRef();
53+
const { t } = useTranslation();
5154

5255
function onChoose(event: React.MouseEvent<HTMLElement, MouseEvent>) {
5356
const input = fileInput.current;
@@ -114,7 +117,7 @@ export const UploadControls: React.FC<IUploadControlsProps> = ({
114117
disabled={uploading}
115118
fullWidth={true}
116119
>
117-
choose a file
120+
{t('controls.upload.button.choose')}
118121
</Button>
119122
</Grid>
120123
<Grid item={true} xs={6} sm={4} md={3} lg={2}>
@@ -126,7 +129,7 @@ export const UploadControls: React.FC<IUploadControlsProps> = ({
126129
onClick={onUpload}
127130
fullWidth={true}
128131
>
129-
Upload
132+
{t('controls.upload.button.upload')}
130133
<CloudUploadIcon className={classes.rightIcon} />
131134
</Button>
132135
{uploading && (

src/components/pca/CalculateControls.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Button from "@material-ui/core/Button";
22
import CircularProgress from "@material-ui/core/CircularProgress";
33
import Grid from "@material-ui/core/Grid";
44
import { Theme } from "@material-ui/core/styles";
5-
import Typography from "@material-ui/core/Typography";
65
import { makeStyles } from "@material-ui/styles";
76
import React from "react";
87
import {
@@ -61,9 +60,6 @@ export const CalculateControls: React.FC<ICalculateControlsProps> = ({
6160

6261
return (
6362
<div className={classes.root}>
64-
<Typography variant="body1" paragraph={true}>
65-
The dataset is processed. Press on the calculate button
66-
</Typography>
6763
<Grid container={true} alignItems="center" spacing={2}>
6864
<Grid item={true} xs={6} sm={4} md={3} lg={2}>
6965
<div className={classes.buttonWrapper}>

src/i18n.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import i18n from 'i18next';
2+
import Backend from 'i18next-xhr-backend';
3+
import LanguageDetector from 'i18next-browser-languagedetector';
4+
import { initReactI18next } from 'react-i18next';
5+
6+
i18n
7+
// load translation using xhr -> see /public/locales
8+
// learn more: https://github.com/i18next/i18next-xhr-backend
9+
.use(Backend)
10+
// detect user language
11+
// learn more: https://github.com/i18next/i18next-browser-languageDetector
12+
.use(LanguageDetector)
13+
// pass the i18n instance to react-i18next.
14+
.use(initReactI18next)
15+
// init i18next
16+
// for all options read: https://www.i18next.com/overview/configuration-options
17+
.init({
18+
fallbackLng: 'en',
19+
debug: true,
20+
interpolation: {
21+
escapeValue: false, // not needed for react as it escapes by default
22+
},
23+
});
24+
25+
export default i18n;

src/index.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { ThemeProvider } from "@material-ui/styles";
2-
import React from "react";
2+
import React, { Suspense } from "react";
33
import ReactDOM from "react-dom";
44
import { AppRouter } from "./router";
55
import { theme } from "./theme";
66

7+
// import i18n translations
8+
import './i18n';
9+
10+
// i18n translations might still be loaded by the xhr backend
11+
// use react's Suspense
712
ReactDOM.render(
8-
<ThemeProvider theme={theme}>
9-
<AppRouter />
10-
</ThemeProvider>,
13+
<Suspense fallback="loading">
14+
<ThemeProvider theme={theme}>
15+
<AppRouter />
16+
</ThemeProvider>
17+
</Suspense>,
1118
document.getElementById("root")
1219
);

0 commit comments

Comments
 (0)