Skip to content

Commit fa477f7

Browse files
add i18n feature
1 parent dd270ea commit fa477f7

File tree

16 files changed

+403
-44
lines changed

16 files changed

+403
-44
lines changed

config/@types/index.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ declare module '*.png' {
1515
}
1616

1717
declare module '*.json' {
18-
const content: string;
18+
const content: any;
1919
export default content;
2020
}
2121

+52
Loading

public/static/images/icon/en.svg

+62
Loading

public/static/images/icon/vi.svg

+45
Loading

src/components/select/index.tsx

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import useOutsideClick from '@/hooks/useOutsideClick';
2+
import React, { useRef, useState } from 'react';
3+
import ArrowDown from '@/static/images/icon/arrow-down.svg';
4+
5+
interface IOption {
6+
value: any;
7+
label: any;
8+
}
9+
10+
interface IProps {
11+
options: Array<IOption>;
12+
width?: number;
13+
onChange?: (option: IOption) => void;
14+
className?: string;
15+
}
16+
17+
const Select: React.FC<IProps> = ({
18+
options,
19+
width = 240,
20+
onChange,
21+
className,
22+
}) => {
23+
const [show, setShow] = useState<boolean>(false);
24+
const [option, setOption] = useState<IOption>(options[0]);
25+
const selectRef = useRef(null);
26+
useOutsideClick(selectRef, () => {
27+
show === true && setShow(false);
28+
});
29+
30+
function handleSelectDropdown(option: IOption) {
31+
const opt = options.find(opt => opt.value === option.value);
32+
33+
typeof onChange === 'function' && onChange(opt);
34+
setOption(opt);
35+
setShow(false);
36+
}
37+
return (
38+
<div
39+
ref={selectRef}
40+
style={{ width }}
41+
className={`dropdown ${className || ''}`}
42+
>
43+
<div onClick={() => setShow(true)} className="dropdown-select">
44+
<span className="dropdown-selected">{option.label}</span>
45+
<ArrowDown className={`dropdown-caret ${show ? 'up' : 'down'}`} />
46+
</div>
47+
<ul className={`dropdown-list ${show ? 'show' : ''}`}>
48+
{options.map(opt => (
49+
<li
50+
key={opt.value}
51+
onClick={() => handleSelectDropdown(opt)}
52+
className="dropdown-item"
53+
>
54+
{opt.label}
55+
</li>
56+
))}
57+
</ul>
58+
</div>
59+
);
60+
};
61+
62+
export default Select;

src/components/select/style.scss

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
.dropdown {
2+
color: $primary-color;
3+
width: 100%;
4+
position: relative;
5+
border-radius: 8px;
6+
.dropdown-caret {
7+
width: 12px;
8+
fill: $primary-color;
9+
10+
&.up {
11+
transform: rotate(180deg);
12+
}
13+
}
14+
.dropdown-select {
15+
background-color: white;
16+
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.1);
17+
padding: 1rem;
18+
border-radius: inherit;
19+
display: flex;
20+
align-items: center;
21+
justify-content: space-between;
22+
cursor: pointer;
23+
}
24+
.dropdown-select * {
25+
pointer-events: none;
26+
}
27+
.dropdown-list {
28+
position: absolute;
29+
top: 100%;
30+
left: 0;
31+
right: 0;
32+
margin-top: 0.4rem;
33+
background-color: white;
34+
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.1);
35+
padding: 1rem;
36+
border-radius: 8px;
37+
display: none;
38+
&::before {
39+
content: '';
40+
height: 1rem;
41+
position: absolute;
42+
top: 0;
43+
left: 0;
44+
right: 0;
45+
background-color: transparent;
46+
transform: translateY(-100%);
47+
}
48+
&.show {
49+
display: block;
50+
}
51+
52+
.dropdown-item {
53+
padding: 1rem;
54+
color: #47536b;
55+
transition: all 0.25s ease;
56+
border-radius: 8px;
57+
cursor: pointer;
58+
&:hover {
59+
color: $primary-color;
60+
background-color: #f1fbff;
61+
}
62+
}
63+
}
64+
}

src/features/home/index.tsx

+38-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
23
import { ITag } from './types';
34
import HomeTag from './components/tag';
5+
import Select from '@/components/select';
6+
import i18n from '@/locales/i18n';
7+
48
import Banner from '@/static/images/banner.png';
9+
import ViIcon from '@/static/images/icon/vi.svg';
10+
import EnIcon from '@/static/images/icon/en.svg';
511

612
const keywords: Array<ITag> = [
713
{ label: 'React.js' },
@@ -12,31 +18,56 @@ const keywords: Array<ITag> = [
1218
{ color: '#e94949', label: 'react-router' },
1319
{ color: '#bf4080', label: 'sass' },
1420
{ color: '#764abc', label: 'redux-thunk' },
15-
1621
{ color: '#2b037a', label: 'pm2' },
1722
];
1823

24+
const languageOptions = [
25+
{
26+
label: (
27+
<div className="lang-item">
28+
<ViIcon width={14} /> <span>&nbsp;&nbsp;</span> Vietnamese
29+
</div>
30+
),
31+
value: 'vi',
32+
},
33+
{
34+
label: (
35+
<div className="lang-item">
36+
<EnIcon width={14} />
37+
<span>&nbsp;&nbsp;</span> English
38+
</div>
39+
),
40+
value: 'en',
41+
},
42+
];
43+
1944
const Home: React.FC = () => {
45+
const { t } = useTranslation();
2046
return (
2147
<div className="row home">
2248
<div className="container">
2349
<img className="banner" src={Banner} />
24-
<div className="title">
25-
React-Typescript-Webpack was config with React, Typescript and Webpack
26-
without CRA. Faster to start your next react project.
27-
</div>
50+
<Select
51+
className="select-language"
52+
onChange={option => {
53+
i18n.changeLanguage(option.value);
54+
}}
55+
options={languageOptions}
56+
/>
57+
<div className="title">{t('home.title')}</div>
2858
<div>
2959
<div className="keywords">
3060
<i>
31-
Keywords:
61+
{t('home.keywords')}:
3262
{keywords.map(key => (
3363
<HomeTag color={key.color} label={key.label} key={key.label} />
3464
))}
3565
</i>
3666
</div>
3767
</div>
3868
<div className="aldenn">
39-
Created with by 👻 <a href="http://aldenn.vercel.app/">Aldenn</a>
69+
{t('home.created_by')} 👻{' '}
70+
<a href="http://aldenn.vercel.app/">Aldenn</a>
4071
</div>
4172
</div>
4273
</div>

src/features/home/style.scss

+10
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,19 @@
44
align-items: center;
55
justify-content: center;
66
text-align: center;
7+
.select-language {
8+
margin: 12px auto;
9+
.lang-item {
10+
display: flex;
11+
align-items: center;
12+
}
13+
}
714

815
.banner {
916
margin-bottom: 20px;
17+
@include s-mobile {
18+
width: 100%;
19+
}
1020
}
1121

1222
.title {

0 commit comments

Comments
 (0)