diff --git a/README.md b/README.md index 5bba52d..2556166 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ For Production: - [ ] Redux-saga - [x] Jest - [x] Axios -- [x] I18n [Not completed] +- [x] I18n - [x] React-router - [x] Alias - [x] Hot reload @@ -139,6 +139,43 @@ For Production: _- You should use [BEM](http://getbem.com/) to write css without conflict_ +- Using i18n + + - Create new json file at `src/locales/resources/.json` + - Add content follow this format into json file + ```javascript + { + "en": { + "name": "Name" + }, + "vi": { + "name": "Tên" + } + } + ``` + - update `src/locales/resources/index.ts` like this: + + ```javascript + /* + * you can use other name instead `user` + * this name will be used as path to key + */ + import user from './.json + + const mergeResource: IResource = { + ..., // others json + user + }; + ``` + + - Now inside any where, you can access to key like this: + + ```javascript + const { t } = useTranslation() + + t('user.name') will be render "Name" for `en` and "Tên" for `vi` + ``` + --- ## Tips diff --git a/config/@types/index.d.ts b/config/@types/index.d.ts index 3eb7fcc..38c8e75 100644 --- a/config/@types/index.d.ts +++ b/config/@types/index.d.ts @@ -15,7 +15,7 @@ declare module '*.png' { } declare module '*.json' { - const content: string; + const content: any; export default content; } diff --git a/public/static/images/icon/arrow-down.svg b/public/static/images/icon/arrow-down.svg new file mode 100644 index 0000000..7d54372 --- /dev/null +++ b/public/static/images/icon/arrow-down.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/images/icon/en.svg b/public/static/images/icon/en.svg new file mode 100644 index 0000000..cf79b12 --- /dev/null +++ b/public/static/images/icon/en.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/images/icon/vi.svg b/public/static/images/icon/vi.svg new file mode 100644 index 0000000..ee0866c --- /dev/null +++ b/public/static/images/icon/vi.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/select/index.tsx b/src/components/select/index.tsx new file mode 100644 index 0000000..44dac45 --- /dev/null +++ b/src/components/select/index.tsx @@ -0,0 +1,62 @@ +import useOutsideClick from '@/hooks/useOutsideClick'; +import React, { useRef, useState } from 'react'; +import ArrowDown from '@/static/images/icon/arrow-down.svg'; + +interface IOption { + value: any; + label: any; +} + +interface IProps { + options: Array; + width?: number; + onChange?: (option: IOption) => void; + className?: string; +} + +const Select: React.FC = ({ + options, + width = 240, + onChange, + className, +}) => { + const [show, setShow] = useState(false); + const [option, setOption] = useState(options[0]); + const selectRef = useRef(null); + useOutsideClick(selectRef, () => { + show === true && setShow(false); + }); + + function handleSelectDropdown(option: IOption) { + const opt = options.find(opt => opt.value === option.value); + + typeof onChange === 'function' && onChange(opt); + setOption(opt); + setShow(false); + } + return ( +
+
setShow(true)} className="dropdown-select"> + {option.label} + +
+
    + {options.map(opt => ( +
  • handleSelectDropdown(opt)} + className="dropdown-item" + > + {opt.label} +
  • + ))} +
+
+ ); +}; + +export default Select; diff --git a/src/components/select/style.scss b/src/components/select/style.scss new file mode 100644 index 0000000..3cdc8d9 --- /dev/null +++ b/src/components/select/style.scss @@ -0,0 +1,64 @@ +.dropdown { + color: $primary-color; + width: 100%; + position: relative; + border-radius: 8px; + .dropdown-caret { + width: 12px; + fill: $primary-color; + + &.up { + transform: rotate(180deg); + } + } + .dropdown-select { + background-color: white; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.1); + padding: 1rem; + border-radius: inherit; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + } + .dropdown-select * { + pointer-events: none; + } + .dropdown-list { + position: absolute; + top: 100%; + left: 0; + right: 0; + margin-top: 0.4rem; + background-color: white; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.1); + padding: 1rem; + border-radius: 8px; + display: none; + &::before { + content: ''; + height: 1rem; + position: absolute; + top: 0; + left: 0; + right: 0; + background-color: transparent; + transform: translateY(-100%); + } + &.show { + display: block; + } + + .dropdown-item { + padding: 1rem; + color: #47536b; + transition: all 0.25s ease; + border-radius: 8px; + cursor: pointer; + &:hover { + color: $primary-color; + background-color: #f1fbff; + } + } + } +} diff --git a/src/features/home/index.tsx b/src/features/home/index.tsx index c51f403..c06f4b5 100644 --- a/src/features/home/index.tsx +++ b/src/features/home/index.tsx @@ -1,7 +1,13 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { ITag } from './types'; import HomeTag from './components/tag'; +import Select from '@/components/select'; +import i18n from '@/locales/i18n'; + import Banner from '@/static/images/banner.png'; +import ViIcon from '@/static/images/icon/vi.svg'; +import EnIcon from '@/static/images/icon/en.svg'; const keywords: Array = [ { label: 'React.js' }, @@ -12,23 +18,47 @@ const keywords: Array = [ { color: '#e94949', label: 'react-router' }, { color: '#bf4080', label: 'sass' }, { color: '#764abc', label: 'redux-thunk' }, - { color: '#2b037a', label: 'pm2' }, ]; +const languageOptions = [ + { + label: ( +
+    Vietnamese +
+ ), + value: 'vi', + }, + { + label: ( +
+ +    English +
+ ), + value: 'en', + }, +]; + const Home: React.FC = () => { + const { t } = useTranslation(); return (
-
- React-Typescript-Webpack was config with React, Typescript and Webpack - without CRA. Faster to start your next react project. -
+