Skip to content
This repository was archived by the owner on Jan 16, 2022. It is now read-only.

Commit 5f6dc6c

Browse files
feat(lng): added change language on the fly
1 parent e037799 commit 5f6dc6c

File tree

9 files changed

+183
-4
lines changed

9 files changed

+183
-4
lines changed

i18n/translations/en-US.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,13 @@
135135
"app-context-not-correct-used": "The app context was not correct used",
136136
"theme-context-not-correct-used": "The theme context was not correct used",
137137
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
138-
}
138+
},
139+
"lng": {
140+
"english": "English",
141+
"portuguese": "Portuguese",
142+
"spanish": "Spanish",
143+
"german": "German",
144+
"chinese": "Chinese"
145+
},
146+
"help-to-translate": "Help to translate"
139147
}

src/App/load-dayjs-locale.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,13 @@ function loadDayJSLocale() {
2525
}
2626

2727
switch (locale.toLowerCase()) {
28-
// At the moment we only support pt-BR, please see: i18n/translations/*
2928
case 'pt-br':
3029
{
3130
require('dayjs/locale/pt-br');
3231
dayjs.locale('pt-br');
3332
}
3433
break;
35-
case 'de':
34+
case 'de-de':
3635
{
3736
require('dayjs/locale/de');
3837
dayjs.locale('de');

src/components/Header/HeaderRight.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
33

44
import Button from '../../muiComponents/Button';
55
import ThemeContext from '../../design-tokens/ThemeContext';
6+
import LanguageSwitch from '../LanguageSwitch';
67

78
import { RightSide } from './styles';
89
import HeaderToolTip from './HeaderToolTip';
@@ -72,6 +73,7 @@ const HeaderRight: React.FC<Props> = ({
7273
{!withoutSearch && (
7374
<HeaderToolTip onClick={onToggleMobileNav} title={t('search.packages')} tooltipIconType={'search'} />
7475
)}
76+
<LanguageSwitch />
7577
<HeaderToolTip title={t('header.documentation')} tooltipIconType={'help'} />
7678
<HeaderToolTip onClick={onOpenRegistryInfoDialog} title={t('header.registry-info')} tooltipIconType={'info'} />
7779
<HeaderToolTip

src/components/Icon/Icon.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import nicaragua from './img/nicaragua.svg';
1111
import pakistan from './img/pakistan.svg';
1212
import austria from './img/austria.svg';
1313
import spain from './img/spain.svg';
14+
import usa from './img/usa.svg';
1415
import earth from './img/earth.svg';
1516
import verdaccio from './img/verdaccio.svg';
1617
import filebinary from './img/filebinary.svg';
@@ -23,6 +24,7 @@ export interface IconsMap {
2324
brazil: string;
2425
spain: string;
2526
china: string;
27+
usa: string;
2628
nicaragua: string;
2729
pakistan: string;
2830
austria: string;
@@ -54,6 +56,7 @@ export const Icons: IconsMap = {
5456
time,
5557
version,
5658
germany,
59+
usa,
5760
};
5861

5962
export interface Props {

src/components/Icon/img/usa.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { MouseEvent } from 'react';
2+
import LanguageIcon from '@material-ui/icons/Language';
3+
import { useTranslation } from 'react-i18next';
4+
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
5+
import i18n from 'i18next';
6+
import { withStyles } from '@material-ui/core/styles';
7+
import styled from '@emotion/styled';
8+
import Menu from '@material-ui/core/Menu';
9+
import MenuItem from '@material-ui/core/MenuItem';
10+
11+
import Button from '../../muiComponents/Button';
12+
import Tooltip from '../../muiComponents/Tooltip';
13+
import Divider from '../../muiComponents/Divider';
14+
import Box from '../../muiComponents/Box';
15+
import Link from '../Link';
16+
import { Theme } from '../../design-tokens/theme';
17+
18+
import LanguageSwitchMenuItemContent from './LanguageSwitchMenuItemContent';
19+
20+
const LanguageSwitch = () => {
21+
const { t } = useTranslation();
22+
const [open, setOpen] = React.useState(false);
23+
const anchorRef = React.useRef<HTMLButtonElement>(null);
24+
25+
const languages = i18n.options.resources ? Object.keys(i18n.options.resources) : [];
26+
const userLanguage = i18n.language || i18n.options?.fallbackLng?.[0];
27+
28+
const handleToggle = () => {
29+
setOpen(prevOpen => !prevOpen);
30+
};
31+
32+
const handleClose = (event: MouseEvent<HTMLLIElement | HTMLUListElement>) => {
33+
if (anchorRef.current) {
34+
if (anchorRef.current.contains(event.currentTarget)) {
35+
return;
36+
}
37+
}
38+
39+
setOpen(false);
40+
};
41+
42+
// return focus to the button when we transitioned from !open -> open
43+
const prevOpen = React.useRef(open);
44+
React.useEffect(() => {
45+
if (prevOpen.current === true && open === false) {
46+
if (anchorRef.current) {
47+
anchorRef.current.focus();
48+
}
49+
}
50+
51+
prevOpen.current = open;
52+
}, [open]);
53+
54+
const handleSwitchLanguage = (language: string) => (event: MouseEvent<HTMLLIElement>) => {
55+
i18n.changeLanguage(language);
56+
handleClose(event);
57+
};
58+
59+
return (
60+
<>
61+
<Tooltip enterDelay={300} title={t('changeLanguage')}>
62+
<SwitchButton color="inherit" onClick={handleToggle} ref={anchorRef}>
63+
<LanguageIcon />
64+
<span>{userLanguage}</span>
65+
<ExpandMoreIcon fontSize="small" />
66+
</SwitchButton>
67+
</Tooltip>
68+
<Menu anchorEl={anchorRef.current} onClose={handleClose} open={open}>
69+
{languages.map(language => (
70+
<MenuItem key={language} onClick={handleSwitchLanguage(language)} selected={userLanguage === language}>
71+
<LanguageSwitchMenuItemContent language={language} />
72+
</MenuItem>
73+
))}
74+
<Box my={1}>
75+
<Divider />
76+
</Box>
77+
<MenuItem
78+
component="a"
79+
href="https://github.com/verdaccio/ui#translations"
80+
onClick={handleClose}
81+
target="_blank">
82+
{`${t('help-to-translate')}`}
83+
</MenuItem>
84+
</Menu>
85+
</>
86+
);
87+
};
88+
89+
export default LanguageSwitch;
90+
91+
const SwitchButton = withStyles((theme: Theme) => ({
92+
label: {
93+
display: 'grid',
94+
gridGap: theme?.spacing(1),
95+
gridTemplateColumns: '24px 1fr 20px',
96+
},
97+
}))(Button);
98+
99+
const StyledLink = styled(Link)<{ theme?: Theme }>(({ theme }) => ({
100+
color: theme?.palette.white,
101+
textDecoration: 'none',
102+
}));
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import styled from '@emotion/styled';
4+
5+
import Icon from '../Icon';
6+
import Box from '../../muiComponents/Box';
7+
import { Theme } from '../../design-tokens/theme';
8+
9+
interface Props {
10+
language: string;
11+
}
12+
13+
const LanguageSwitchMenuItemContent = ({ language }: Props) => {
14+
const { t } = useTranslation();
15+
16+
switch (language.toLowerCase()) {
17+
case 'en-us':
18+
return (
19+
<StyledBox display="grid">
20+
<Icon name="usa" size="md" />
21+
{t('lng.english')}
22+
</StyledBox>
23+
);
24+
case 'pt-br':
25+
return (
26+
<StyledBox display="grid">
27+
<Icon name="brazil" size="md" />
28+
{t('lng.portuguese')}
29+
</StyledBox>
30+
);
31+
case 'de-de':
32+
return (
33+
<StyledBox display="grid">
34+
<Icon name="germany" size="md" />
35+
{t('lng.german')}
36+
</StyledBox>
37+
);
38+
case 'es-es':
39+
return (
40+
<StyledBox display="grid">
41+
<Icon name="spain" size="md" />
42+
{t('lng.spanish')}
43+
</StyledBox>
44+
);
45+
case 'zh-cn':
46+
return (
47+
<StyledBox display="grid">
48+
<Icon name="china" size="md" />
49+
{t('lng.chinese')}
50+
</StyledBox>
51+
);
52+
default:
53+
return null;
54+
}
55+
};
56+
57+
export default LanguageSwitchMenuItemContent;
58+
59+
const StyledBox = styled(Box)(({ theme }: Theme) => ({
60+
gridGap: theme?.spacing(0.5),
61+
gridTemplateColumns: '20px 1fr',
62+
alignItems: 'center',
63+
}));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './LanguageSwitch';

src/muiComponents/MenuItem/MenuItem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface Props extends Omit<MenuItemProps, 'component'> {
1212
const MenuItem = forwardRef<MenuItemRef, Props>(function MenuItem({ button, ...props }, ref) {
1313
// it seems typescript has some discrimination type limitions. Please see: https://github.com/mui-org/material-ui/issues/14971
1414
// eslint-disable-next-line @typescript-eslint/no-explicit-any
15-
return <StyledMaterialUIMenuItem {...props} button={button as any} innerRef={ref} />;
15+
return <StyledMaterialUIMenuItem {...props} button={button as any} ref={ref as any} />;
1616
});
1717

1818
MenuItem.defaultProps = {

0 commit comments

Comments
 (0)