├── .browserslistrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── README.md ├── babel.config.js ├── commitlint.config.js ├── config ├── adminMenuData.js └── dataMap.js ├── declares.d.ts ├── jest.config.js ├── jsconfig.json ├── package.json ├── public └── favicon.ico ├── src ├── App.less ├── App.tsx ├── api │ └── demo.ts ├── assets │ ├── Demo.png │ ├── fonts │ │ └── roboto │ │ │ ├── fonts.less │ │ │ ├── roboto-v20-latin-100.eot │ │ │ ├── roboto-v20-latin-100.svg │ │ │ ├── roboto-v20-latin-100.ttf │ │ │ ├── roboto-v20-latin-100.woff │ │ │ ├── roboto-v20-latin-100.woff2 │ │ │ ├── roboto-v20-latin-100italic.eot │ │ │ ├── roboto-v20-latin-100italic.svg │ │ │ ├── roboto-v20-latin-100italic.ttf │ │ │ ├── roboto-v20-latin-100italic.woff │ │ │ ├── roboto-v20-latin-100italic.woff2 │ │ │ ├── roboto-v20-latin-300.eot │ │ │ ├── roboto-v20-latin-300.svg │ │ │ ├── roboto-v20-latin-300.ttf │ │ │ ├── roboto-v20-latin-300.woff │ │ │ ├── roboto-v20-latin-300.woff2 │ │ │ ├── roboto-v20-latin-300italic.eot │ │ │ ├── roboto-v20-latin-300italic.svg │ │ │ ├── roboto-v20-latin-300italic.ttf │ │ │ ├── roboto-v20-latin-300italic.woff │ │ │ ├── roboto-v20-latin-300italic.woff2 │ │ │ ├── roboto-v20-latin-500.eot │ │ │ ├── roboto-v20-latin-500.svg │ │ │ ├── roboto-v20-latin-500.ttf │ │ │ ├── roboto-v20-latin-500.woff │ │ │ ├── roboto-v20-latin-500.woff2 │ │ │ ├── roboto-v20-latin-500italic.eot │ │ │ ├── roboto-v20-latin-500italic.svg │ │ │ ├── roboto-v20-latin-500italic.ttf │ │ │ ├── roboto-v20-latin-500italic.woff │ │ │ ├── roboto-v20-latin-500italic.woff2 │ │ │ ├── roboto-v20-latin-700.eot │ │ │ ├── roboto-v20-latin-700.svg │ │ │ ├── roboto-v20-latin-700.ttf │ │ │ ├── roboto-v20-latin-700.woff │ │ │ ├── roboto-v20-latin-700.woff2 │ │ │ ├── roboto-v20-latin-700italic.eot │ │ │ ├── roboto-v20-latin-700italic.svg │ │ │ ├── roboto-v20-latin-700italic.ttf │ │ │ ├── roboto-v20-latin-700italic.woff │ │ │ ├── roboto-v20-latin-700italic.woff2 │ │ │ ├── roboto-v20-latin-900.eot │ │ │ ├── roboto-v20-latin-900.svg │ │ │ ├── roboto-v20-latin-900.ttf │ │ │ ├── roboto-v20-latin-900.woff │ │ │ ├── roboto-v20-latin-900.woff2 │ │ │ ├── roboto-v20-latin-900italic.eot │ │ │ ├── roboto-v20-latin-900italic.svg │ │ │ ├── roboto-v20-latin-900italic.ttf │ │ │ ├── roboto-v20-latin-900italic.woff │ │ │ ├── roboto-v20-latin-900italic.woff2 │ │ │ ├── roboto-v20-latin-italic.eot │ │ │ ├── roboto-v20-latin-italic.svg │ │ │ ├── roboto-v20-latin-italic.ttf │ │ │ ├── roboto-v20-latin-italic.woff │ │ │ ├── roboto-v20-latin-italic.woff2 │ │ │ ├── roboto-v20-latin-regular.eot │ │ │ ├── roboto-v20-latin-regular.svg │ │ │ ├── roboto-v20-latin-regular.ttf │ │ │ ├── roboto-v20-latin-regular.woff │ │ │ └── roboto-v20-latin-regular.woff2 │ ├── i18n.gif │ ├── logo192.png │ └── logo512.png ├── components │ └── Locale.tsx ├── config │ └── lang.ts ├── hooks │ ├── useDebounce.ts │ ├── useEnums.tsx │ └── useTranslate.ts ├── i18n │ ├── docs.md │ ├── index.js │ ├── locales │ │ ├── en_US.json │ │ └── id_ID.json │ └── localesHash.js ├── index.tsx ├── pages │ └── HelloWord │ │ ├── index.less │ │ ├── index.tsx │ │ └── logo192.png ├── redux │ ├── actions │ │ ├── global.ts │ │ └── index.ts │ ├── reducers │ │ ├── global.ts │ │ └── index.ts │ └── store.ts ├── types │ └── SelectOptions.d.ts ├── utils │ ├── action.ts │ ├── index.tsx │ ├── request.ts │ ├── thousandth.test.ts │ ├── thousandth.ts │ └── time.ts └── vars.less ├── template.html ├── test ├── __mocks__ │ ├── fileMock.js │ └── styleMock.js └── setup.js ├── tsconfig.json ├── tsconfig.paths.json ├── webpack.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | last 3 version 4 | > 1% 5 | maintained node versions 6 | not dead 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.next 3 | /out 4 | /dist 5 | /adminDist 6 | /.cache -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@typescript-eslint', 4 | 'plugin:@typescript-eslint/eslint-recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | ], 7 | env: { 8 | browser: true, 9 | es6: true, 10 | node: true, 11 | }, 12 | globals: { 13 | Atomics: 'readonly', 14 | SharedArrayBuffer: 'readonly', 15 | JSX: 'readonly', 16 | }, 17 | parserOptions: { 18 | ecmaFeatures: { 19 | jsx: true, 20 | }, 21 | ecmaVersion: 2018, 22 | sourceType: 'module', 23 | }, 24 | plugins: [ 25 | 'react', 26 | 'react-hooks', 27 | 'typescript' 28 | ], 29 | parser: '@typescript-eslint/parser', 30 | settings: { 31 | 'import/resolver': { 32 | webpack:{ 33 | config: 'webpack.config.js' 34 | } 35 | } 36 | }, 37 | rules: { 38 | '@typescript-eslint/no-unused-vars': [2, { args: 'none' }], 39 | 'import/prefer-default-export': 'off', 40 | 'import/no-extraneous-dependencies': 'off', 41 | 'max-len': [2, 130, 2], 42 | 'react-hooks/rules-of-hooks': 'error', 43 | 'react/jsx-filename-extension': 'off', 44 | 'react/jsx-props-no-spreading':'off', 45 | 'semi': ['error', 'always'], 46 | 'react/prop-types': 'off', 47 | 'react/destructuring-assignment': 'off', 48 | 'import/no-extraneous-dependencies': 'off', 49 | 'react/button-has-type': 'off', 50 | 'no-console': 'off', 51 | 'global-require': 'off', 52 | 'no-debugger': 'warn', 53 | 'camelcase': 'off', 54 | 'no-restricted-syntax': 'off', 55 | 'react/state-in-constructor': 'off', 56 | 'import/no-unresolved':'off', 57 | '@typescript-eslint/no-empty-function':'off', 58 | 'react/jsx-curly-newline': 'off', 59 | 'react/jsx-key': ['error', { 'checkFragmentShorthand': true }], 60 | 'import/extensions': [ 61 | 'error', 62 | 'always', 63 | { 64 | 'js': 'never', 65 | 'jsx': 'never', 66 | 'ts': 'never', 67 | 'tsx': 'never', 68 | '.d.ts': 'never', 69 | }, 70 | ], 71 | 'import/order': ['error', { 72 | 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 73 | 'pathGroups': [ 74 | { 75 | 'pattern': '@/**', 76 | 'group': 'internal' 77 | } 78 | ] 79 | }], 80 | 'object-curly-newline': 0, 81 | 'no-underscore-dangle': 0, 82 | 'implicit-arrow-linebreak': 0, 83 | '@typescript-eslint/array-type': [2, { default: 'array-simple' }], 84 | '@typescript-eslint/camelcase': 0, 85 | '@typescript-eslint/explicit-function-return-type': 0, 86 | '@typescript-eslint/indent': [2, 2], 87 | '@typescript-eslint/no-explicit-any': 0, 88 | '@typescript-eslint/explicit-member-accessibility': [2, { accessibility: 'off' }], 89 | '@typescript-eslint/no-object-literal-type-assertion': 0, 90 | '@typescript-eslint/no-unused-vars': [2, { argsIgnorePattern: '^_' }], 91 | '@typescript-eslint/no-unused-vars': ['error', { 92 | 'vars': 'all', 93 | 'args': 'none', 94 | 'ignoreRestSiblings': true, 95 | }], 96 | '@typescript-eslint/no-var-requires': 0, 97 | '@typescript-eslint/restrict-plus-operands': 0, 98 | '@typescript-eslint/explicit-function-return-type': 'off', 99 | '@typescript-eslint/indent': ['error', 2], 100 | '@typescript-eslint/explicit-member-accessibility': 'off', 101 | '@typescript-eslint/no-explicit-any': 'off', 102 | 'arrow-parens': 0, 103 | 'arrow-spacing': [2, { before: true, after: true }], 104 | 'comma-dangle': 'off', 105 | 'comma-spacing': [2, { before: false, after: true }], 106 | 'func-call-spacing': [2, 'never'], 107 | 'jsx-quotes': [2, 'prefer-double'], 108 | 'key-spacing': [2, { beforeColon: false, afterColon: true }], 109 | 'keyword-spacing': [2, { before: true, after: true }], 110 | 'no-case-declarations': 0, 111 | 'no-mixed-operators': 0, 112 | 'no-multi-spaces': 2, 113 | 'no-prototype-builtins': 0, 114 | 'no-trailing-spaces': 2, 115 | 'no-useless-concat': 2, 116 | 'prefer-template': 2, 117 | 'quotes': [2, 'single'], 118 | 'react-hooks/exhaustive-deps': 2, 119 | 'react-hooks/rules-of-hooks': 2, 120 | 'react/display-name': 0, 121 | 'react/jsx-closing-tag-location': 2, 122 | 'react/jsx-curly-spacing': [2, { when: 'never' }], 123 | 'react/jsx-first-prop-new-line': [2, 'multiline'], 124 | 'react/jsx-fragments': [2, 'syntax'], 125 | 'react/jsx-indent-props': [2, 2], 126 | 'react/jsx-tag-spacing': [2, { 127 | beforeClosing: 'never', 128 | beforeSelfClosing: 'always', 129 | closingSlash: 'never', 130 | afterOpening: 'never', 131 | }], 132 | 'react/jsx-wrap-multilines': [2, { 133 | declaration: 'parens', 134 | assignment: 'parens', 135 | return: 'parens', 136 | arrow: 'parens', 137 | condition: 'parens', 138 | logical: 'parens', 139 | prop: 'parens', 140 | }], 141 | 'react/prop-types': 0, 142 | 'no-nested-ternary': 'off', 143 | 'react/self-closing-comp': [2, { component: true, html: true }], 144 | 'rest-spread-spacing': [2, 'never'], 145 | 'space-before-blocks': [2, 'always'], 146 | 'space-before-function-paren': [2, { 147 | anonymous: 'always', 148 | named: 'never', 149 | asyncArrow: 'always', 150 | }], 151 | 'space-in-parens': [2, 'never'], 152 | 'space-infix-ops': 2, 153 | 'space-unary-ops': [2, { words: true, nonwords: false }], 154 | }, 155 | }; 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /dist 12 | /adminDist 13 | /.next 14 | /out 15 | 16 | # misc 17 | .DS_Store 18 | .env 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | package-lock.json 23 | .cache 24 | 25 | # IDE 26 | /.idea 27 | /.vscode 28 | 29 | # i18n locales 30 | # /src/i18n/locales 31 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "singleQuote": true, 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Hooks-i18n-template 2 | 3 | ![image](https://github.com/beMySun/react-hooks-i18n-template/blob/master/src/assets/i18n.gif) 4 | 5 | - React Hooks 6 | - TypeScript 7 | - i18n Support 8 | - Code Splitting 9 | - Bundle Analyzer 10 | - Autoprefixer 11 | - Useful Custom Hooks 12 | - etc... 13 | 14 | # Download 15 | 16 | ```bash 17 | git@github.com:beMySun/react-hooks-i18n-template.git 18 | ``` 19 | 20 | # Run 21 | 22 | ```bash 23 | yarn && yarn start 24 | ``` 25 | 26 | ** Have a Try and Enjoy~ ** 27 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const plugins = [ 2 | [ 3 | 'import', 4 | { 5 | libraryName: 'antd', 6 | libraryDirectory: 'es', 7 | style: true, 8 | }, 9 | ], 10 | 'lodash', 11 | '@babel/plugin-syntax-dynamic-import', 12 | [ 13 | '@babel/plugin-proposal-decorators', 14 | { 15 | legacy: true, 16 | }, 17 | ], 18 | ['@babel/plugin-proposal-class-properties', { loose: true }], 19 | ]; 20 | 21 | module.exports = (api) => { 22 | api.cache(true); 23 | return { 24 | presets: ['@babel/preset-env', '@babel/preset-react'], 25 | env: { 26 | development: { 27 | plugins: [...plugins, '@babel/plugin-transform-react-jsx-source'], 28 | }, 29 | production: { 30 | plugins, 31 | }, 32 | }, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'scope-empty': [0], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /config/adminMenuData.js: -------------------------------------------------------------------------------- 1 | const menuData = [ 2 | { 3 | title: 'Merchant Management', 4 | path: '/admin/merchantManagement', 5 | icon: 'shop', 6 | children: [ 7 | { 8 | title: 'Merchant List', 9 | path: '/admin/merchantManagement/merchantList', 10 | }, 11 | ], 12 | }, { 13 | title: 'User Management', 14 | path: '/admin/userManagement', 15 | icon: 'team', 16 | children: [ 17 | { 18 | title: 'User List', 19 | path: '/admin/userManagement/userList', 20 | }, 21 | ], 22 | }, 23 | ]; 24 | export default menuData; 25 | -------------------------------------------------------------------------------- /config/dataMap.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export const role = { 4 | 1: 'Admin', 5 | 2: 'Employee', 6 | }; 7 | export const status = { 8 | 1: 'Active', 9 | 2: 'Inactive', 10 | }; 11 | export const oppositeStatus = (type, val) => { 12 | switch (type) { 13 | case 'role': return val === 1 ? 2 : 1; 14 | case 'status': return val === 1 ? 2 : 1; 15 | default: return 1; 16 | } 17 | }; 18 | export const emailRex = /^([A-Za-z0-9_\-.])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,4})$/; 19 | export const phoneRex = /\d{5,20}/; 20 | export const nameRex = /^[\S][\s\S]{4,29}$/; 21 | export const passwordRex = /(^[0-9A-Za-z+=_]{5,15}$)/; 22 | export const timeRender = time => moment(time * 1000).format('HH:mm DD/MM/YYYY'); 23 | export const StockType = { 24 | StockAddType: [1, 2], 25 | StockDecreaseType: [11, 12, 13, 14, 15, 16], 26 | }; 27 | export const pageSizeOptions = ['10', '20', '50', '100']; 28 | -------------------------------------------------------------------------------- /declares.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png' 2 | declare module '*.jpg' 3 | declare module '*.jpeg' 4 | declare module '*.svg' 5 | 6 | declare module 'react-hooks-lib' 7 | declare module 'memoize' 8 | declare module 'react-custom-scrollbars' 9 | declare module 'lrz' 10 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFiles: [ 3 | './test/setup.js', 4 | ], 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'jsx', 8 | 'ts', 9 | 'tsx', 10 | ], 11 | testPathIgnorePatterns: [ 12 | '/node_modules/', 13 | ], 14 | testRegex: '\\.test\\.[jt]sx?$', 15 | moduleNameMapper: { 16 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 17 | '/test/__mocks__/fileMock.js', 18 | '\\.(css|less|scss)$': '/test/__mocks__/styleMock.js', 19 | }, 20 | transform: { 21 | '^.+\\.jsx?$': 'babel-jest', 22 | '^.+\\.tsx?$': 'ts-jest', 23 | }, 24 | globals: { 25 | 'ts-jest': { 26 | babelConfig: { 27 | comments: false, 28 | plugins: [ 29 | // using "lib" not "es" here to avoid antd files transformation 30 | // which saves lots of time and does import as needed 31 | ['import', { libraryName: 'antd', libraryDirectory: 'lib', style: true }], 32 | ['transform-define', { 'window.__CID__': 'sg' }], 33 | ], 34 | }, 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "allowSyntheticDefaultImports": false, 5 | "baseUrl": "../", 6 | "paths": { 7 | "root/*": ["react-hooks-i18n-template-fe/*"], 8 | } 9 | }, 10 | "exclude": ["node_modules", "dist"] 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hooks-i18n-template", 3 | "licencs": "MIT", 4 | "author": "皮小蛋", 5 | "scripts": { 6 | "start": "cross-env NODE_ENV=development APP_TYPE=main webpack-dev-server", 7 | "start:analyse": "cross-env NODE_ENV=development ANALYSE=1 APP_TYPE=main webpack-dev-server", 8 | "build": "cross-env NODE_ENV=production APP_TYPE=main webpack", 9 | "build:analyse": "cross-env NODE_ENV=production ANALYSE=1 APP_TYPE=main webpack", 10 | "lint": "cross-env eslint ./ --ext .js,.tsx,.ts", 11 | "lint:fix": "yarn lint --fix", 12 | "i18n": "cross-env node src/i18n/index.js", 13 | "test": "cross-env NODE_ENV=jest jest --verbose --coverage", 14 | "prettier": "cross-env prettier --write '**/*.less'" 15 | }, 16 | "dependencies": { 17 | "@loadable/component": "^5.12.0", 18 | "@types/qs": "^6.9.1", 19 | "antd": "^4.1.2", 20 | "autoprefixer": "^9.7.6", 21 | "axios": "^0.19.2", 22 | "dayjs": "^1.8.24", 23 | "es6-promise": "^4.2.8", 24 | "fs": "^0.0.2", 25 | "history": "^4.10.1", 26 | "intersection-observer": "^0.7.0", 27 | "js-cookie": "^2.2.1", 28 | "lodash": "^4.17.19", 29 | "lrz": "^4.9.40", 30 | "md5": "^2.2.1", 31 | "memoize": "^0.1.1", 32 | "moment": "^2.24.0", 33 | "number-precision": "^1.3.2", 34 | "optimize-css-assets-webpack-plugin": "^5.0.3", 35 | "qs": "^6.9.3", 36 | "query-string": "^6.11.1", 37 | "rc-value": "^0.0.3", 38 | "react": "^16.13.0", 39 | "react-cropper": "^1.3.0", 40 | "react-custom-scrollbars": "^4.2.1", 41 | "react-dom": "^16.13.0", 42 | "react-hooks-lib": "^0.1.5", 43 | "react-intl": "4.1.1", 44 | "react-redux": "^7.2.0", 45 | "react-router-dom": "^5.1.2", 46 | "redux": "^4.0.5", 47 | "redux-actions": "^2.6.5", 48 | "redux-thunk": "^2.3.0", 49 | "styled-components": "^5.0.1", 50 | "typescript": "^3.8.3", 51 | "uatu-lib": "^0.6.0", 52 | "uuid": "^7.0.2" 53 | }, 54 | "devDependencies": { 55 | "@babel/cli": "^7.8.4", 56 | "@babel/core": "^7.8.7", 57 | "@babel/plugin-proposal-class-properties": "^7.8.3", 58 | "@babel/plugin-proposal-decorators": "^7.8.3", 59 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 60 | "@babel/plugin-transform-react-jsx-source": "^7.9.0", 61 | "@babel/polyfill": "^7.8.7", 62 | "@babel/preset-env": "^7.8.7", 63 | "@babel/preset-react": "^7.8.3", 64 | "@commitlint/cli": "^8.3.5", 65 | "@commitlint/config-conventional": "^8.3.4", 66 | "@hot-loader/react-dom": "^16.12.0", 67 | "@testing-library/react": "^10.0.1", 68 | "@types/classnames": "^2.2.10", 69 | "@types/jest": "^25.1.4", 70 | "@types/js-cookie": "^2.2.5", 71 | "@types/loadable__component": "^5.10.0", 72 | "@types/lodash": "^4.14.149", 73 | "@types/react": "^16.9.23", 74 | "@types/react-cropper": "^1.3.0", 75 | "@types/react-dom": "^16.9.5", 76 | "@types/react-redux": "^7.1.7", 77 | "@types/react-router": "^5.1.4", 78 | "@types/react-router-dom": "^5.1.3", 79 | "@types/react-test-renderer": "^16.9.2", 80 | "@types/redux-actions": "^2.6.1", 81 | "@types/uuid": "^7.0.2", 82 | "@typescript-eslint/eslint-plugin": "^2.23.0", 83 | "@typescript-eslint/parser": "^2.23.0", 84 | "antd-dayjs-webpack-plugin": "^1.0.0", 85 | "babel-eslint": "^10.1.0", 86 | "babel-jest": "^25.1.0", 87 | "babel-loader": "^8.0.6", 88 | "babel-plugin-import": "^1.13.0", 89 | "babel-plugin-lodash": "^3.3.4", 90 | "babel-plugin-module-resolver": "^4.0.0", 91 | "babel-plugin-transform-define": "^2.0.0", 92 | "babel-polyfill": "^6.26.0", 93 | "babel-register": "^6.26.0", 94 | "cache-loader": "^4.1.0", 95 | "clean-webpack-plugin": "^3.0.0", 96 | "commitlint": "^8.3.5", 97 | "copy-webpack-plugin": "^5.1.1", 98 | "cross-env": "^7.0.2", 99 | "css-loader": "^3.4.2", 100 | "eslint": "^6.8.0", 101 | "eslint-config-airbnb": "^18.0.1", 102 | "eslint-config-airbnb-typescript": "^7.0.0", 103 | "eslint-import-resolver-webpack": "^0.12.1", 104 | "eslint-plugin-import": "^2.20.1", 105 | "eslint-plugin-jest": "^23.8.2", 106 | "eslint-plugin-jsx-a11y": "^6.2.3", 107 | "eslint-plugin-react": "^7.19.0", 108 | "eslint-plugin-react-hooks": "^2.5.0", 109 | "eslint-plugin-typescript": "^0.14.0", 110 | "file-loader": "^5.1.0", 111 | "happypack": "^5.0.1", 112 | "html-webpack-plugin": "^3.2.0", 113 | "husky": "^4.2.3", 114 | "img-loader": "^3.0.1", 115 | "isomorphic-fetch": "^2.2.1", 116 | "jest": "^25.1.0", 117 | "less": "^3.11.1", 118 | "less-loader": "^5.0.0", 119 | "less-vars-to-js": "^1.3.0", 120 | "mini-css-extract-plugin": "0.9.0", 121 | "optimize-css-assets-webpack-plugin": "^5.0.3", 122 | "os": "^0.1.1", 123 | "postcss-easy-import": "^3.0.0", 124 | "postcss-loader": "^3.0.0", 125 | "pre-commit": "^1.2.2", 126 | "prettier": "2.0.2", 127 | "raw-loader": "^4.0.0", 128 | "react-hot-loader": "^4.12.19", 129 | "react-scripts": "^3.4.0", 130 | "react-test-renderer": "^16.13.1", 131 | "redux-devtools-extension": "^2.13.8", 132 | "snazzy": "^8.0.0", 133 | "speed-measure-webpack-plugin": "^1.3.1", 134 | "standard": "^14.3.1", 135 | "style-loader": "^1.1.3", 136 | "transform": "^1.1.2", 137 | "ts-import-plugin": "^1.6.5", 138 | "ts-jest": "^25.2.1", 139 | "ts-loader": "^6.2.1", 140 | "url-loader": "^3.0.0", 141 | "webpack": "^4.42.0", 142 | "webpack-bundle-analyzer": "^3.6.1", 143 | "webpack-cli": "^3.3.11", 144 | "webpack-common-shake": "^2.1.0", 145 | "webpack-dev-server": "^3.10.3" 146 | }, 147 | "husky": { 148 | "hooks": { 149 | "commit-msg": "commitlint -e .git/COMMIT_EDITMSG", 150 | "pre-commit": "yarn lint:fix && yarn prettier && git add -A" 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/public/favicon.ico -------------------------------------------------------------------------------- /src/App.less: -------------------------------------------------------------------------------- 1 | html body { 2 | font-family: Roboto; 3 | font-size: 14px; 4 | color: @color-text-main; 5 | letter-spacing: 1px; 6 | } 7 | 8 | #app { 9 | background: #f0f2f5; 10 | } 11 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { Router } from 'react-router-dom'; 4 | import { createBrowserHistory } from 'history'; 5 | import Locale from './components/Locale'; 6 | import { store } from './redux/store'; 7 | import HelloWorld from './pages/HelloWord'; 8 | import './App.less'; 9 | 10 | const history = createBrowserHistory(); 11 | 12 | const AppContainer = () => ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default AppContainer; 23 | -------------------------------------------------------------------------------- /src/api/demo.ts: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request'; 2 | 3 | export const getToken = () => request.get('/api/csrftoken'); 4 | -------------------------------------------------------------------------------- /src/assets/Demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/Demo.png -------------------------------------------------------------------------------- /src/assets/fonts/roboto/fonts.less: -------------------------------------------------------------------------------- 1 | /* roboto-100 - latin */ 2 | @font-face { 3 | font-family: 'Roboto'; 4 | font-style: normal; 5 | font-weight: 100; 6 | src: url('./roboto-v20-latin-100.eot'); /* IE9 Compat Modes */ 7 | src: local('Roboto Thin'), local('Roboto-Thin'), 8 | url('./roboto-v20-latin-100.eot?#iefix') format('embedded-opentype'), 9 | /* IE6-IE8 */ url('./roboto-v20-latin-100.woff2') format('woff2'), 10 | /* Super Modern Browsers */ url('./roboto-v20-latin-100.woff') 11 | format('woff'), 12 | /* Modern Browsers */ url('./roboto-v20-latin-100.ttf') format('truetype'), 13 | /* Safari, Android, iOS */ url('./roboto-v20-latin-100.svg#Roboto') 14 | format('svg'); /* Legacy iOS */ 15 | } 16 | /* roboto-100italic - latin */ 17 | @font-face { 18 | font-family: 'Roboto'; 19 | font-style: italic; 20 | font-weight: 100; 21 | src: url('./roboto-v20-latin-100italic.eot'); /* IE9 Compat Modes */ 22 | src: local('Roboto Thin Italic'), local('Roboto-ThinItalic'), 23 | url('./roboto-v20-latin-100italic.eot?#iefix') format('embedded-opentype'), 24 | /* IE6-IE8 */ url('./roboto-v20-latin-100italic.woff2') format('woff2'), 25 | /* Super Modern Browsers */ url('./roboto-v20-latin-100italic.woff') 26 | format('woff'), 27 | /* Modern Browsers */ url('./roboto-v20-latin-100italic.ttf') 28 | format('truetype'), 29 | /* Safari, Android, iOS */ url('./roboto-v20-latin-100italic.svg#Roboto') 30 | format('svg'); /* Legacy iOS */ 31 | } 32 | /* roboto-300 - latin */ 33 | @font-face { 34 | font-family: 'Roboto'; 35 | font-style: normal; 36 | font-weight: 300; 37 | src: url('./roboto-v20-latin-300.eot'); /* IE9 Compat Modes */ 38 | src: local('Roboto Light'), local('Roboto-Light'), 39 | url('./roboto-v20-latin-300.eot?#iefix') format('embedded-opentype'), 40 | /* IE6-IE8 */ url('./roboto-v20-latin-300.woff2') format('woff2'), 41 | /* Super Modern Browsers */ url('./roboto-v20-latin-300.woff') 42 | format('woff'), 43 | /* Modern Browsers */ url('./roboto-v20-latin-300.ttf') format('truetype'), 44 | /* Safari, Android, iOS */ url('./roboto-v20-latin-300.svg#Roboto') 45 | format('svg'); /* Legacy iOS */ 46 | } 47 | /* roboto-300italic - latin */ 48 | @font-face { 49 | font-family: 'Roboto'; 50 | font-style: italic; 51 | font-weight: 300; 52 | src: url('./roboto-v20-latin-300italic.eot'); /* IE9 Compat Modes */ 53 | src: local('Roboto Light Italic'), local('Roboto-LightItalic'), 54 | url('./roboto-v20-latin-300italic.eot?#iefix') format('embedded-opentype'), 55 | /* IE6-IE8 */ url('./roboto-v20-latin-300italic.woff2') format('woff2'), 56 | /* Super Modern Browsers */ url('./roboto-v20-latin-300italic.woff') 57 | format('woff'), 58 | /* Modern Browsers */ url('./roboto-v20-latin-300italic.ttf') 59 | format('truetype'), 60 | /* Safari, Android, iOS */ url('./roboto-v20-latin-300italic.svg#Roboto') 61 | format('svg'); /* Legacy iOS */ 62 | } 63 | /* roboto-regular - latin */ 64 | @font-face { 65 | font-family: 'Roboto'; 66 | font-style: normal; 67 | font-weight: 400; 68 | src: url('./roboto-v20-latin-regular.eot'); /* IE9 Compat Modes */ 69 | src: local('Roboto'), local('Roboto-Regular'), 70 | url('./roboto-v20-latin-regular.eot?#iefix') format('embedded-opentype'), 71 | /* IE6-IE8 */ url('./roboto-v20-latin-regular.woff2') format('woff2'), 72 | /* Super Modern Browsers */ url('./roboto-v20-latin-regular.woff') 73 | format('woff'), 74 | /* Modern Browsers */ url('./roboto-v20-latin-regular.ttf') 75 | format('truetype'), 76 | /* Safari, Android, iOS */ url('./roboto-v20-latin-regular.svg#Roboto') 77 | format('svg'); /* Legacy iOS */ 78 | } 79 | /* roboto-italic - latin */ 80 | @font-face { 81 | font-family: 'Roboto'; 82 | font-style: italic; 83 | font-weight: 400; 84 | src: url('./roboto-v20-latin-italic.eot'); /* IE9 Compat Modes */ 85 | src: local('Roboto Italic'), local('Roboto-Italic'), 86 | url('./roboto-v20-latin-italic.eot?#iefix') format('embedded-opentype'), 87 | /* IE6-IE8 */ url('./roboto-v20-latin-italic.woff2') format('woff2'), 88 | /* Super Modern Browsers */ url('./roboto-v20-latin-italic.woff') 89 | format('woff'), 90 | /* Modern Browsers */ url('./roboto-v20-latin-italic.ttf') 91 | format('truetype'), 92 | /* Safari, Android, iOS */ url('./roboto-v20-latin-italic.svg#Roboto') 93 | format('svg'); /* Legacy iOS */ 94 | } 95 | /* roboto-500 - latin */ 96 | @font-face { 97 | font-family: 'Roboto'; 98 | font-style: normal; 99 | font-weight: 500; 100 | src: url('./roboto-v20-latin-500.eot'); /* IE9 Compat Modes */ 101 | src: local('Roboto Medium'), local('Roboto-Medium'), 102 | url('./roboto-v20-latin-500.eot?#iefix') format('embedded-opentype'), 103 | /* IE6-IE8 */ url('./roboto-v20-latin-500.woff2') format('woff2'), 104 | /* Super Modern Browsers */ url('./roboto-v20-latin-500.woff') 105 | format('woff'), 106 | /* Modern Browsers */ url('./roboto-v20-latin-500.ttf') format('truetype'), 107 | /* Safari, Android, iOS */ url('./roboto-v20-latin-500.svg#Roboto') 108 | format('svg'); /* Legacy iOS */ 109 | } 110 | /* roboto-500italic - latin */ 111 | @font-face { 112 | font-family: 'Roboto'; 113 | font-style: italic; 114 | font-weight: 500; 115 | src: url('./roboto-v20-latin-500italic.eot'); /* IE9 Compat Modes */ 116 | src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), 117 | url('./roboto-v20-latin-500italic.eot?#iefix') format('embedded-opentype'), 118 | /* IE6-IE8 */ url('./roboto-v20-latin-500italic.woff2') format('woff2'), 119 | /* Super Modern Browsers */ url('./roboto-v20-latin-500italic.woff') 120 | format('woff'), 121 | /* Modern Browsers */ url('./roboto-v20-latin-500italic.ttf') 122 | format('truetype'), 123 | /* Safari, Android, iOS */ url('./roboto-v20-latin-500italic.svg#Roboto') 124 | format('svg'); /* Legacy iOS */ 125 | } 126 | /* roboto-700 - latin */ 127 | @font-face { 128 | font-family: 'Roboto'; 129 | font-style: normal; 130 | font-weight: 700; 131 | src: url('./roboto-v20-latin-700.eot'); /* IE9 Compat Modes */ 132 | src: local('Roboto Bold'), local('Roboto-Bold'), 133 | url('./roboto-v20-latin-700.eot?#iefix') format('embedded-opentype'), 134 | /* IE6-IE8 */ url('./roboto-v20-latin-700.woff2') format('woff2'), 135 | /* Super Modern Browsers */ url('./roboto-v20-latin-700.woff') 136 | format('woff'), 137 | /* Modern Browsers */ url('./roboto-v20-latin-700.ttf') format('truetype'), 138 | /* Safari, Android, iOS */ url('./roboto-v20-latin-700.svg#Roboto') 139 | format('svg'); /* Legacy iOS */ 140 | } 141 | /* roboto-700italic - latin */ 142 | @font-face { 143 | font-family: 'Roboto'; 144 | font-style: italic; 145 | font-weight: 700; 146 | src: url('./roboto-v20-latin-700italic.eot'); /* IE9 Compat Modes */ 147 | src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), 148 | url('./roboto-v20-latin-700italic.eot?#iefix') format('embedded-opentype'), 149 | /* IE6-IE8 */ url('./roboto-v20-latin-700italic.woff2') format('woff2'), 150 | /* Super Modern Browsers */ url('./roboto-v20-latin-700italic.woff') 151 | format('woff'), 152 | /* Modern Browsers */ url('./roboto-v20-latin-700italic.ttf') 153 | format('truetype'), 154 | /* Safari, Android, iOS */ url('./roboto-v20-latin-700italic.svg#Roboto') 155 | format('svg'); /* Legacy iOS */ 156 | } 157 | /* roboto-900 - latin */ 158 | @font-face { 159 | font-family: 'Roboto'; 160 | font-style: normal; 161 | font-weight: 900; 162 | src: url('./roboto-v20-latin-900.eot'); /* IE9 Compat Modes */ 163 | src: local('Roboto Black'), local('Roboto-Black'), 164 | url('./roboto-v20-latin-900.eot?#iefix') format('embedded-opentype'), 165 | /* IE6-IE8 */ url('./roboto-v20-latin-900.woff2') format('woff2'), 166 | /* Super Modern Browsers */ url('./roboto-v20-latin-900.woff') 167 | format('woff'), 168 | /* Modern Browsers */ url('./roboto-v20-latin-900.ttf') format('truetype'), 169 | /* Safari, Android, iOS */ url('./roboto-v20-latin-900.svg#Roboto') 170 | format('svg'); /* Legacy iOS */ 171 | } 172 | /* roboto-900italic - latin */ 173 | @font-face { 174 | font-family: 'Roboto'; 175 | font-style: italic; 176 | font-weight: 900; 177 | src: url('./roboto-v20-latin-900italic.eot'); /* IE9 Compat Modes */ 178 | src: local('Roboto Black Italic'), local('Roboto-BlackItalic'), 179 | url('./roboto-v20-latin-900italic.eot?#iefix') format('embedded-opentype'), 180 | /* IE6-IE8 */ url('./roboto-v20-latin-900italic.woff2') format('woff2'), 181 | /* Super Modern Browsers */ url('./roboto-v20-latin-900italic.woff') 182 | format('woff'), 183 | /* Modern Browsers */ url('./roboto-v20-latin-900italic.ttf') 184 | format('truetype'), 185 | /* Safari, Android, iOS */ url('./roboto-v20-latin-900italic.svg#Roboto') 186 | format('svg'); /* Legacy iOS */ 187 | } 188 | -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100italic.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-100italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-100italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300italic.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-300italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 38 | 39 | 41 | 43 | 44 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 58 | 59 | 61 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 77 | 78 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 98 | 100 | 102 | 103 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 118 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 139 | 141 | 143 | 144 | 145 | 148 | 149 | 152 | 154 | 155 | 156 | 157 | 160 | 161 | 162 | 163 | 164 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 178 | 181 | 183 | 184 | 185 | 186 | 188 | 190 | 192 | 193 | 195 | 196 | 197 | 198 | 200 | 201 | 202 | 203 | 204 | 205 | 207 | 209 | 211 | 213 | 216 | 219 | 220 | 222 | 223 | 224 | 225 | 227 | 228 | 229 | 231 | 233 | 235 | 237 | 240 | 243 | 246 | 249 | 251 | 253 | 255 | 257 | 259 | 260 | 261 | 262 | 263 | 265 | 267 | 269 | 271 | 273 | 276 | 278 | 280 | 282 | 283 | 284 | 285 | 287 | 288 | 290 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500italic.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-500italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-500italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700italic.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-700italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 39 | 40 | 42 | 44 | 45 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 59 | 60 | 62 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 101 | 103 | 104 | 106 | 107 | 109 | 110 | 111 | 112 | 113 | 114 | 116 | 117 | 119 | 120 | 121 | 122 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 139 | 141 | 143 | 144 | 145 | 148 | 149 | 152 | 154 | 155 | 156 | 157 | 160 | 161 | 162 | 163 | 164 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 178 | 181 | 183 | 184 | 185 | 186 | 188 | 190 | 192 | 193 | 195 | 196 | 197 | 198 | 200 | 201 | 202 | 203 | 204 | 205 | 207 | 209 | 211 | 213 | 216 | 219 | 220 | 222 | 223 | 224 | 225 | 227 | 228 | 229 | 231 | 233 | 235 | 237 | 240 | 243 | 246 | 249 | 251 | 253 | 255 | 257 | 259 | 260 | 261 | 262 | 263 | 265 | 267 | 269 | 271 | 273 | 275 | 277 | 278 | 280 | 281 | 282 | 283 | 285 | 286 | 287 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900italic.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-900italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-900italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-italic.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-italic.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-italic.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-regular.eot -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/roboto/roboto-v20-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/fonts/roboto/roboto-v20-latin-regular.woff2 -------------------------------------------------------------------------------- /src/assets/i18n.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/i18n.gif -------------------------------------------------------------------------------- /src/assets/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/logo192.png -------------------------------------------------------------------------------- /src/assets/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/assets/logo512.png -------------------------------------------------------------------------------- /src/components/Locale.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import React from 'react'; 3 | import { ConfigProvider } from 'antd'; 4 | import { connect } from 'react-redux'; 5 | import id_ID from 'antd/lib/locale-provider/id_ID'; 6 | import en_US from 'antd/lib/locale-provider/en_US'; 7 | import dayjs from 'dayjs'; 8 | import 'dayjs/locale/id'; 9 | import cookies from 'js-cookie'; 10 | import { IntlProvider } from 'react-intl'; 11 | 12 | import enMessages from '@/i18n/locales/en_US.json'; 13 | import idMessages from '@/i18n/locales/id_ID.json'; 14 | 15 | const localeMap = { 16 | id_ID, 17 | en_US, 18 | }; 19 | 20 | const dayjsLocaleMap = { 21 | id_ID: 'id', 22 | en_US: 'en', 23 | }; 24 | 25 | const intlMap = { 26 | en_US: 'en', 27 | id_ID: 'id', 28 | }; 29 | 30 | const messagesMap = { 31 | en_US: enMessages, 32 | id_ID: idMessages, 33 | }; 34 | 35 | export const countryCodeMap = { 36 | en_US: 1, 37 | id_ID: 6, 38 | }; 39 | 40 | const rootStore = ({ global: { lang } }: any) => ({ lang }); 41 | 42 | interface PropTypes { 43 | lang: any; 44 | } 45 | 46 | const _Intl: React.FC = ({ children, lang }) => ( 47 | 48 | {children} 49 | 50 | ); 51 | 52 | export const Intl = connect(rootStore)(_Intl); 53 | 54 | const Locale: React.FC = ({ children, lang }) => { 55 | React.useEffect(() => { 56 | dayjs.locale(dayjsLocaleMap[lang]); 57 | cookies.set('language_id', countryCodeMap[lang]); 58 | const root = document.querySelector('html'); 59 | if (root) { 60 | root.className = lang; 61 | } 62 | }, [lang]); 63 | return ( 64 | { 67 | if (node) { 68 | return node.parentNode; 69 | } 70 | return document.body; 71 | }} 72 | > 73 | {children} 74 | 75 | ); 76 | }; 77 | 78 | export default connect(rootStore)(Locale); 79 | -------------------------------------------------------------------------------- /src/config/lang.ts: -------------------------------------------------------------------------------- 1 | const langMap = { 2 | en_US: 'English', 3 | id_ID: 'Bahasa', 4 | // zh_CN: 'Chinese (Simplified)', 5 | }; 6 | 7 | export default langMap; 8 | -------------------------------------------------------------------------------- /src/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | import debounce from 'lodash/debounce'; 3 | 4 | const useDebounce = (initialFn: any, delay = 200) => { 5 | const fnRef = useRef(debounce(initialFn, delay, { leading: true })); 6 | useEffect(() => { 7 | fnRef.current = debounce(initialFn, delay, { leading: true }); 8 | return fnRef.current.cancel; 9 | }, [delay, initialFn]); 10 | return fnRef.current; 11 | }; 12 | 13 | export default useDebounce; 14 | -------------------------------------------------------------------------------- /src/hooks/useEnums.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { Select } from 'antd'; 4 | import { useTranslate } from './useTranslate'; 5 | 6 | export interface OptionType { 7 | label: string; 8 | value: any; 9 | } 10 | 11 | const useEnums = (entry: string) => { 12 | const enums = useSelector((state: any) => state.global.systemEnumMap); 13 | const translate = useTranslate(); 14 | if (!enums[entry]) { 15 | console.error(`entry ${entry} not exist!`); 16 | return { 17 | /* eslint-disable @typescript-eslint/no-unused-vars */ 18 | getText: (value: any) => undefined as any, 19 | getTranslateText: (value: any) => undefined as any, 20 | getValue: (text: string) => undefined as any, 21 | /* eslint-enable @typescript-eslint/no-unused-vars */ 22 | toOptions: () => [] as OptionType[], 23 | toTranslateOptions: () => [] as OptionType[], 24 | toSelectOptions: () => [] as JSX.Element[], 25 | toTranslateSelectOptions: () => [] as JSX.Element[], 26 | }; 27 | } 28 | 29 | const entries = Object.entries(enums[entry] as Record); 30 | 31 | const mapTextToValue = new Map(); 32 | const mapValueToText = new Map(); 33 | 34 | entries.forEach(([value, text]) => { 35 | mapTextToValue.set(text, +value); 36 | mapValueToText.set(+value, text); 37 | }); 38 | 39 | const options: OptionType[] = entries.map(([value, label]) => ({ 40 | value: +value, 41 | label, 42 | })); 43 | 44 | const translateOptions: OptionType[] = entries.map(([value, label]) => ({ 45 | value: +value, 46 | label: translate(label), 47 | })); 48 | 49 | return { 50 | getText: (value: any) => mapValueToText.get(value) || undefined, 51 | getTranslateText: (value: any) => { 52 | const resultValue = mapValueToText.get(value); 53 | if (!resultValue) { 54 | return undefined; 55 | } 56 | return translate(`${resultValue}`) || resultValue; 57 | }, 58 | getValue: (text: string) => mapTextToValue.get(text) || undefined, 59 | toOptions: () => options, 60 | toTranslateOptions: () => translateOptions, 61 | toSelectOptions: () => options.map(option => ( 62 | 66 | {option.label} 67 | 68 | )), 69 | toTranslateSelectOptions: () => options.map(option => ( 70 | 74 | {translate(option.label)} 75 | 76 | )) 77 | }; 78 | }; 79 | 80 | export default useEnums; 81 | -------------------------------------------------------------------------------- /src/hooks/useTranslate.ts: -------------------------------------------------------------------------------- 1 | import { useIntl, IntlShape } from 'react-intl'; 2 | import { PrimitiveType } from 'intl-messageformat/lib/intl-messageformat.d'; 3 | import { state } from '@/redux/store'; 4 | 5 | type FormatterType = >( 6 | id: T, 7 | values?: Record, 8 | ) => T 9 | 10 | let formatter: FormatterType | null = null; 11 | 12 | const { global: { translateMode } } = state; 13 | 14 | const getFormatter = (intl: IntlShape) => { 15 | const func = >( 16 | id: T, 17 | values: Record = {}, 18 | ): T => { 19 | if (translateMode === 'translate-key') { 20 | return id; 21 | } 22 | switch (typeof id) { 23 | case 'string': 24 | return intl.formatMessage({ id }, values) as T; 25 | case 'object': 26 | if (Array.isArray(id)) { 27 | return id.map(t => func(t, values)) as T; 28 | } 29 | const o = {}; 30 | Object.keys(id).forEach(key => { o[key] = func(id[key], values); }); 31 | return o as T; 32 | default: 33 | return id; 34 | } 35 | }; 36 | // don't need memoize because IntlProvider has its own private cache 37 | return func; 38 | }; 39 | 40 | export function useTranslate() { 41 | const intl = useIntl(); 42 | if (!formatter) { 43 | formatter = getFormatter(intl); 44 | } 45 | return formatter; 46 | } 47 | -------------------------------------------------------------------------------- /src/i18n/docs.md: -------------------------------------------------------------------------------- 1 | # 添加翻译 2 | 3 | -  在翻译平台: http://transify.xxx.com/resources/572 4 | 5 | 添加字段后, 需要执行: 6 | 7 | `yarn i18n` 下载到本地。 8 | 9 | # 新语言支持 10 | 11 | - 需要在 compoments/Local.js 中配置新的语言,注册等。 12 | 13 | # 翻译的用法: 14 | 15 | ```js 16 | import { useTranslate } from '@/hooks/useTranslate'; 17 | 18 | // inside your function compinents: 19 | const translate = useTranslate(); 20 | 21 | translate('transify_id'); 22 | 23 | or; 24 | 25 | translate('transify_id', { params }); 26 | ``` 27 | -------------------------------------------------------------------------------- /src/i18n/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const fetch = require('isomorphic-fetch'); 4 | const localesHash = require('./localesHash'); 5 | 6 | const argv = process.argv.slice(2); 7 | const country = (argv[0] || '').toUpperCase(); 8 | const deployMap = { 9 | en: 'en_US', 10 | id: 'id_ID', 11 | }; 12 | 13 | const i18nServerURI = locale => { 14 | const keywords = { 15 | en: 'en', 16 | id: 'id' 17 | }; 18 | const keyword = keywords[locale]; 19 | return keyword === 'en' 20 | ? 'https://transify.seagroup.com/resources/572/json/download' 21 | : `https://transify.seagroup.com/resources/572/${keyword}/json/download`; 22 | }; 23 | 24 | const fetchKeys = async locale => { 25 | const uri = i18nServerURI(locale); 26 | console.log(`🚚 🚚 🚚 Downloading ${locale} keys...\n${uri}`); 27 | const response = await fetch(uri); 28 | const keys = await response.json(); 29 | return keys; 30 | }; 31 | 32 | const access = async filePath => 33 | new Promise((resolve) => { 34 | fs.access(filePath, err => { 35 | if (err) { 36 | if (err.code === 'EXIST') { 37 | resolve(true); 38 | } 39 | resolve(false); 40 | } 41 | resolve(true); 42 | }); 43 | }); 44 | 45 | const run = async () => { 46 | const locales = localesHash[country] || Object.values(localesHash).reduce((previous, current) => previous.concat(current), []); 47 | if (locales === undefined) { 48 | console.error('This country is not in service.'); 49 | return; 50 | } 51 | for (const locale of locales) { 52 | const keys = await fetchKeys(locale); // eslint-disable-line 53 | const data = JSON.stringify(keys, null, 2); 54 | const directoryPath = path.resolve(__dirname, 'locales'); 55 | if (!fs.existsSync(directoryPath)) { 56 | fs.mkdirSync(directoryPath); 57 | } 58 | const filePath = path.resolve(__dirname, `locales/${deployMap[locale]}.json`); 59 | const isExist = await access(filePath); // eslint-disable-line 60 | const operation = isExist ? '🐈 Update' : '🐶 Create'; 61 | fs.writeFileSync(filePath, `${data}\n`); 62 | console.log(`${operation}\t${filePath}`); 63 | } 64 | }; 65 | 66 | run(); 67 | -------------------------------------------------------------------------------- /src/i18n/locales/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello React !": "Hello React !", 3 | "I love you xxx times": "I Love You {count} Times !" 4 | } 5 | -------------------------------------------------------------------------------- /src/i18n/locales/id_ID.json: -------------------------------------------------------------------------------- 1 | { 2 | "Hello React !": "Halo Bereaksi !", 3 | "I love you xxx times": "Aku Mencintaimu {count} Kali !" 4 | } 5 | -------------------------------------------------------------------------------- /src/i18n/localesHash.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | EN: ['en'], 3 | ID: ['id'], 4 | }; 5 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('app')); 6 | -------------------------------------------------------------------------------- /src/pages/HelloWord/index.less: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 20vmin; 7 | pointer-events: none; 8 | margin-bottom: 20px; 9 | } 10 | 11 | @media (prefers-reduced-motion: no-preference) { 12 | .App-logo { 13 | animation: App-logo-spin infinite 20s linear; 14 | } 15 | } 16 | 17 | .App-header { 18 | background-color: #282c34; 19 | min-height: 100vh; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | font-size: calc(10px + 2vmin); 25 | color: white; 26 | } 27 | 28 | .App-link { 29 | color: #61dafb; 30 | } 31 | 32 | @keyframes App-logo-spin { 33 | from { 34 | transform: rotate(0deg); 35 | } 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/pages/HelloWord/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import { Select } from 'antd'; 4 | import { useTranslate } from '@/hooks/useTranslate'; 5 | import { changeLang } from '@/redux/actions/global'; 6 | import langMap from '@/config/lang'; 7 | import logo from './logo192.png'; 8 | import './index.less'; 9 | 10 | const HelloWorld: React.FC = () => { 11 | const translate = useTranslate(); 12 | const dispatch = useDispatch(); 13 | const { lang } = useSelector((state: any) => state.global); 14 | 15 | const handleLangChange = (key: string) => { 16 | dispatch(changeLang(key)); 17 | window.location.reload(); 18 | }; 19 | 20 | const someInfo = { 21 | count: 1000, 22 | }; 23 | 24 | return ( 25 |
26 |
27 | logo 28 |

Hello React !

29 |

{translate('I love you xxx times', { count: someInfo?.count })}

30 | 43 |
44 |
45 | ); 46 | }; 47 | 48 | export default HelloWorld; 49 | -------------------------------------------------------------------------------- /src/pages/HelloWord/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beMySun/react-hooks-i18n-template/1387d07dd131a77a4548b3d3af243e201e6de061/src/pages/HelloWord/logo192.png -------------------------------------------------------------------------------- /src/redux/actions/global.ts: -------------------------------------------------------------------------------- 1 | export const CONSTANTS = { 2 | CHANGE_LANG: 'CHANGE_LANG', 3 | CHANGE_TRANSLATE_MODE: 'CHANGE_TRANSLATE_MODE', 4 | CHANGE_USER_INFO: 'CHANGE_USER_INFO', 5 | GET_SYSTEM_ENUMS: 'GET_SYSTEM_ENUMS', 6 | }; 7 | 8 | export const changeLang = (lang: string) => ({ 9 | type: CONSTANTS.CHANGE_LANG, 10 | lang, 11 | }); 12 | 13 | export const changeTranslateMode = (translateMode: string) => ({ 14 | type: CONSTANTS.CHANGE_TRANSLATE_MODE, 15 | translateMode, 16 | }); 17 | 18 | export const changeUserInfo = (userInfo = {}) => ({ 19 | type: CONSTANTS.CHANGE_USER_INFO, 20 | userInfo, 21 | }); 22 | -------------------------------------------------------------------------------- /src/redux/actions/index.ts: -------------------------------------------------------------------------------- 1 | import * as global from './global'; 2 | 3 | export default { 4 | ...global, 5 | }; 6 | -------------------------------------------------------------------------------- /src/redux/reducers/global.ts: -------------------------------------------------------------------------------- 1 | import { AnyAction } from 'redux'; 2 | import { CONSTANTS } from '../actions/global'; 3 | 4 | export default ( 5 | state = { 6 | lang: 'en_US', 7 | translateMode: 'translate-value', 8 | isLogin: false, 9 | userName: '', 10 | role: null, 11 | hasInitialized: false, 12 | }, 13 | action: AnyAction, 14 | ) => { 15 | switch (action.type) { 16 | case CONSTANTS.CHANGE_LANG: 17 | return { 18 | ...state, 19 | lang: action.lang, 20 | }; 21 | case CONSTANTS.CHANGE_TRANSLATE_MODE: 22 | return { 23 | ...state, 24 | translateMode: action.translateMode, 25 | }; 26 | case CONSTANTS.CHANGE_USER_INFO: 27 | return { 28 | ...state, 29 | ...action.userInfo, 30 | }; 31 | case CONSTANTS.GET_SYSTEM_ENUMS: 32 | return { 33 | ...state, 34 | systemEnumMap: action.payload, 35 | }; 36 | default: 37 | return state; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/redux/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import global from './global'; 3 | 4 | export default combineReducers({ 5 | global, 6 | }); 7 | -------------------------------------------------------------------------------- /src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import rootReducer from './reducers'; 4 | 5 | const middleware = [thunkMiddleware]; 6 | 7 | let composeEnhancers: (a: R) => R; 8 | 9 | if (process.env.NODE_ENV === 'production') { 10 | composeEnhancers = compose; 11 | } else { 12 | composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 13 | } 14 | 15 | export const configureStore = (rootReducers: any) => { 16 | const reduxState = localStorage.getItem('reduxState'); 17 | 18 | const global = JSON.parse(reduxState || '{}')?.global || {}; 19 | const persistedState = reduxState ? { global } : undefined; 20 | 21 | const store = createStore( 22 | rootReducers, 23 | persistedState, 24 | composeEnhancers(applyMiddleware(...middleware)), 25 | ); 26 | store.subscribe(() => { 27 | localStorage.setItem('reduxState', JSON.stringify(store.getState())); 28 | }); 29 | 30 | return store; 31 | }; 32 | 33 | export const store = configureStore(rootReducer); 34 | export const state = store.getState(); 35 | export default configureStore; 36 | -------------------------------------------------------------------------------- /src/types/SelectOptions.d.ts: -------------------------------------------------------------------------------- 1 | export interface SelectOption { 2 | id: string; 3 | value: string; 4 | name: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/action.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | import { Dispatch } from 'redux'; 3 | 4 | export default (id: string, request: (...args: any[]) => Promise) => 5 | (dispatch: Dispatch) => 6 | (...args: any[]) => 7 | request(...args).then((data: any) => { 8 | dispatch(createAction(id)(data)); 9 | return data; 10 | }); 11 | -------------------------------------------------------------------------------- /src/utils/index.tsx: -------------------------------------------------------------------------------- 1 | export const getBase64 = (file: any) => new Promise((resolve, reject) => { 2 | const reader = new FileReader(); 3 | reader.readAsDataURL(file); 4 | reader.onload = () => resolve(reader.result); 5 | reader.onerror = error => reject(error); 6 | }); 7 | 8 | export const downloadFileInFlow = (fileUrl: string) => { 9 | const a = document.createElement('a'); 10 | a.download = ''; 11 | a.href = fileUrl; 12 | a.click(); 13 | }; 14 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const baseURL = '/'; 4 | 5 | export const createRequest = ({ timeout = 10 * 1000 } = {}) => { 6 | const request = axios.create({ 7 | baseURL, 8 | withCredentials: true, 9 | xsrfCookieName: 'csrftoken', 10 | xsrfHeaderName: 'X-CSRF-TOKEN', 11 | timeout, 12 | }); 13 | 14 | request.interceptors.response.use( 15 | response => { 16 | const { data } = response; 17 | return data; 18 | }, 19 | error => Promise.reject(error) 20 | ); 21 | return request; 22 | }; 23 | 24 | export default createRequest(); 25 | -------------------------------------------------------------------------------- /src/utils/thousandth.test.ts: -------------------------------------------------------------------------------- 1 | import NP from 'number-precision'; 2 | import thousandth from './thousandth'; 3 | 4 | NP.enableBoundaryChecking(false); 5 | 6 | test('normal situations', () => { 7 | expect(thousandth(0)).toBe('0'); 8 | expect(thousandth(1)).toBe('1'); 9 | expect(thousandth(12)).toBe('12'); 10 | expect(thousandth(123)).toBe('123'); 11 | expect(thousandth(1234)).toBe('1,234'); 12 | expect(thousandth(12345)).toBe('12,345'); 13 | expect(thousandth(123456)).toBe('123,456'); 14 | expect(thousandth(1234567)).toBe('1,234,567'); 15 | expect(thousandth(12345678)).toBe('12,345,678'); 16 | expect(thousandth(123456789)).toBe('123,456,789'); 17 | }); 18 | 19 | test('negative numbers', () => { 20 | expect(thousandth(-0)).toBe('-0'); 21 | expect(thousandth(-1)).toBe('-1'); 22 | expect(thousandth(-12)).toBe('-12'); 23 | expect(thousandth(-123)).toBe('-123'); 24 | expect(thousandth(-1234)).toBe('-1,234'); 25 | expect(thousandth(-12345)).toBe('-12,345'); 26 | expect(thousandth(-123456)).toBe('-123,456'); 27 | expect(thousandth(-1234567)).toBe('-1,234,567'); 28 | expect(thousandth(-12345678)).toBe('-12,345,678'); 29 | expect(thousandth(-123456789)).toBe('-123,456,789'); 30 | }); 31 | 32 | test('floats, max 6 digits', () => { 33 | expect(thousandth(-1.1)).toBe('-1.1'); 34 | expect(thousandth(-12.12)).toBe('-12.12'); 35 | expect(thousandth(-123.123)).toBe('-123.123'); 36 | expect(thousandth(-1234.1234)).toBe('-1,234.1234'); 37 | expect(thousandth(-12345.12345)).toBe('-12,345.12345'); 38 | expect(thousandth(-123456.123456)).toBe('-123,456.123456'); 39 | expect(thousandth(-1234567.123456)).toBe('-1,234,567.123456'); 40 | expect(thousandth(-12345678.123456)).toBe('-12,345,678.123456'); 41 | expect(thousandth(-123456789.123456)).toBe('-123,456,789.123456'); 42 | }); 43 | 44 | test('normal situations for string input', () => { 45 | expect(thousandth('0')).toBe('0'); 46 | expect(thousandth('1')).toBe('1'); 47 | expect(thousandth('12')).toBe('12'); 48 | expect(thousandth('123')).toBe('123'); 49 | expect(thousandth('1234')).toBe('1,234'); 50 | expect(thousandth('12345')).toBe('12,345'); 51 | expect(thousandth('123456')).toBe('123,456'); 52 | expect(thousandth('1234567')).toBe('1,234,567'); 53 | expect(thousandth('12345678')).toBe('12,345,678'); 54 | expect(thousandth('123456789')).toBe('123,456,789'); 55 | }); 56 | 57 | test('negative numbers for string input', () => { 58 | expect(thousandth('-0')).toBe('-0'); 59 | expect(thousandth('-1')).toBe('-1'); 60 | expect(thousandth('-12')).toBe('-12'); 61 | expect(thousandth('-123')).toBe('-123'); 62 | expect(thousandth('-1234')).toBe('-1,234'); 63 | expect(thousandth('-12345')).toBe('-12,345'); 64 | expect(thousandth('-123456')).toBe('-123,456'); 65 | expect(thousandth('-1234567')).toBe('-1,234,567'); 66 | expect(thousandth('-12345678')).toBe('-12,345,678'); 67 | expect(thousandth('-123456789')).toBe('-123,456,789'); 68 | }); 69 | 70 | test('floats, max 6 digits for string input', () => { 71 | expect(thousandth('0.0')).toBe('0'); 72 | expect(thousandth('-0.0')).toBe('-0'); 73 | expect(thousandth('-1.10')).toBe('-1.1'); 74 | expect(thousandth('-12.120')).toBe('-12.12'); 75 | expect(thousandth('-123.1230')).toBe('-123.123'); 76 | expect(thousandth('-1234.12340')).toBe('-1,234.1234'); 77 | expect(thousandth('-12345.123450')).toBe('-12,345.12345'); 78 | expect(thousandth('-123456.1234560')).toBe('-123,456.123456'); 79 | expect(thousandth('-1234567.1234560')).toBe('-1,234,567.123456'); 80 | expect(thousandth('-12345678.1234560')).toBe('-12,345,678.123456'); 81 | expect(thousandth('-123456789.1234560')).toBe('-123,456,789.123456'); 82 | }); 83 | 84 | test('floats, with precision', () => { 85 | expect(thousandth(-1.1, 4)).toBe('-1.1000'); 86 | expect(thousandth(-12.12, 4)).toBe('-12.1200'); 87 | expect(thousandth(-123.123, 4)).toBe('-123.1230'); 88 | expect(thousandth(-1234.1234, 4)).toBe('-1,234.1234'); 89 | expect(thousandth(-12345.12345, 4)).toBe('-12,345.1235'); 90 | expect(thousandth(-123456.123456, 4)).toBe('-123,456.1235'); 91 | expect(thousandth(-1234567.123456, 4)).toBe('-1,234,567.1235'); 92 | expect(thousandth(-12345678.123456, 4)).toBe('-12,345,678.1235'); 93 | expect(thousandth(-123456789.123456, 4)).toBe('-123,456,789.1235'); 94 | }); 95 | 96 | test('floats, precision is large', () => { 97 | expect(thousandth(-1.1, 40)).toBe('-1.100000'); 98 | expect(thousandth(-12.12, 40)).toBe('-12.120000'); 99 | expect(thousandth(-123.123, 40)).toBe('-123.123000'); 100 | expect(thousandth(-1234.1234, 40)).toBe('-1,234.123400'); 101 | expect(thousandth(-12345.12345, 40)).toBe('-12,345.123450'); 102 | expect(thousandth(-123456.123456, 40)).toBe('-123,456.123456'); 103 | expect(thousandth(-1234567.123456, 40)).toBe('-1,234,567.123456'); 104 | expect(thousandth(-12345678.123456, 40)).toBe('-12,345,678.123456'); 105 | expect(thousandth(-123456789.123456, 40)).toBe('-123,456,789.123456'); 106 | }); 107 | 108 | test('exceptions', () => { 109 | expect(() => thousandth(0, -1)).toThrow(); 110 | expect(() => thousandth(0, 101)).toThrow(); 111 | }); 112 | -------------------------------------------------------------------------------- /src/utils/thousandth.ts: -------------------------------------------------------------------------------- 1 | import NP from 'number-precision'; 2 | 3 | if (process.env.env) { 4 | NP.enableBoundaryChecking(false); 5 | } 6 | 7 | // eslint-disable-next-line prefer-destructuring 8 | export const __CID__ = (window as any).__CID__; 9 | 10 | export const countrySignSettings = { 11 | id: { 12 | negative: '-', 13 | float: ',', 14 | thousand: '.', 15 | negativeRe: /-/g, 16 | floatRe: /,/g, 17 | thousandRe: /\./g, 18 | }, 19 | sg: { 20 | negative: '-', 21 | float: '.', 22 | thousand: ',', 23 | negativeRe: /-/g, 24 | floatRe: /\./g, 25 | thousandRe: /,/g, 26 | }, 27 | }; 28 | 29 | const MAX_PRECISION = 6; 30 | 31 | export default ( 32 | n: number | string, 33 | precision = Infinity, 34 | negative = countrySignSettings[__CID__].ngative || '-', 35 | float = countrySignSettings[__CID__].float || '.', 36 | thousand = countrySignSettings[__CID__].thousand || ',', 37 | ) => { 38 | if ((precision < 0 || precision > 100) && precision !== Infinity) { 39 | throw new Error('precision must between 0 and 100'); 40 | } 41 | let x = n; 42 | if (typeof x === 'string') { 43 | x = +x; 44 | } 45 | if (1 / x === -Infinity) { 46 | return '-0'; 47 | } 48 | const isNegative = x < 0; 49 | x = Math.abs(x); 50 | const restNumber = NP.minus(x, Math.floor(x)); 51 | const restStr = precision === Infinity 52 | ? String(NP.strip(restNumber)).substring(0, 2 + MAX_PRECISION) // 2 is for "0." 53 | : precision > MAX_PRECISION 54 | ? NP.strip(restNumber, MAX_PRECISION).toFixed(MAX_PRECISION) 55 | : NP.strip(restNumber, precision || undefined).toFixed(precision); 56 | const result = (Math.floor(x) || 0).toString().replace( 57 | /(\d)(?=(?:\d{3})+$)/g, 58 | `$1${thousand}`, 59 | ); 60 | return (isNegative ? negative : '') 61 | + result 62 | + (+restStr ? `${float}${restStr.substr(2)}` : ''); 63 | }; 64 | -------------------------------------------------------------------------------- /src/utils/time.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | declare type UnitType = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'; 4 | 5 | export const formatTime = (dateNumber: number, unit: UnitType = 'minute') => { 6 | if (!dateNumber) { 7 | return '/'; 8 | } 9 | 10 | const date = dayjs(dateNumber * 1000); 11 | 12 | if (unit === 'year') return date.format('YYYY'); 13 | if (unit === 'month') return date.format('MM/YYYY'); 14 | if (unit === 'day') return date.format('DD/MM/YYYY'); 15 | if (unit === 'hour') return date.format('HH DD/MM/YYYY'); 16 | if (unit === 'minute') return date.format('HH:mm DD/MM/YYYY'); 17 | if (unit === 'second') return date.format('HH:mm:ss DD/MM/YYYY'); 18 | 19 | return date.format(unit); 20 | }; 21 | 22 | export const getDaysFromNow = (timeStamp: number) => { 23 | if (timeStamp) { 24 | const seconds = Math.abs(dayjs(timeStamp).diff(+new Date(), 'second')); 25 | const days = seconds / 86400; 26 | return days <= 1 ? 1 : Math.ceil(days); 27 | } 28 | return 0; 29 | }; 30 | -------------------------------------------------------------------------------- /src/vars.less: -------------------------------------------------------------------------------- 1 | // layout sizes 2 | @layout-size-header: 60px; 3 | @layout-size-sider: 200px; 4 | @layout-size-content: 1104px; 5 | @layout-size-content-full: 1264px; 6 | @layout-size-gap: 16px; 7 | @layout-size-gap-large: 24px; 8 | @layout-size-grid-width: 64px; 9 | 10 | // basic colors 11 | @color-primary: #ee4d2d; 12 | @color-success: #5c7; 13 | @color-warning: #ffbf00; 14 | @color-info: #2673dd; 15 | @color-link: #2673dd; 16 | @color-error: #ff4742; 17 | @color-danger: #ff4742; 18 | 19 | // mask colors 20 | @color-mask-alpha: rgba(0, 0, 0, 0.5); 21 | @color-mask-hover: rgba(0, 0, 0, 0.04); 22 | @color-mask-focus: rgba(0, 0, 0, 0.08); 23 | @color-mask-active: rgba(0, 0, 0, 0.08); 24 | 25 | // extended colors 26 | @color-primary-hover: #d55639; 27 | @color-primary-active: #cd5236; 28 | @color-primary-focus: @color-primary-active; 29 | @color-success-hover: overlay(@color-mask-hover, @color-success); 30 | @color-success-active: overlay(@color-mask-active, @color-success); 31 | @color-success-focus: @color-success-active; 32 | @color-warning-hover: overlay(@color-mask-hover, @color-warning); 33 | @color-warning-active: overlay(@color-mask-active, @color-warning); 34 | @color-warning-focus: @color-warning-active; 35 | @color-info-hover: #0046ab; // provided by UI spec 36 | @color-info-active: overlay(@color-mask-active, @color-info); 37 | @color-info-focus: @color-info-active; 38 | @color-link-hover: #0046ab; // provided by UI spec 39 | @color-link-active: overlay(@color-mask-active, @color-info); 40 | @color-link-focus: @color-link-active; 41 | @color-error-hover: overlay(@color-mask-hover, @color-error); 42 | @color-error-active: overlay(@color-mask-active, @color-error); 43 | @color-error-focus: @color-error-active; 44 | @color-danger-hover: overlay(@color-mask-hover, @color-danger); 45 | @color-danger-active: overlay(@color-mask-active, @color-danger); 46 | @color-danger-focus: @color-danger-active; 47 | 48 | // text colors 49 | @color-text-main: #333; 50 | @color-text-secondary: #666; 51 | @color-text-hint: #999; 52 | @color-text-placeholder: #b7b7b7; 53 | @color-text-disabled: #b7b7b7; 54 | @color-text-cyan: #1baf9d; 55 | @color-text-gold: #eda500; 56 | 57 | // background colors 58 | @color-bg-global: #f6f6f6; 59 | @color-bg-success: #ebf9ef; 60 | @color-bg-warning: #fff7e0; 61 | @color-bg-info: #e5eefb; 62 | @color-bg-error: #ffe9e8; 63 | @color-bg-danger: #ffe9e8; 64 | @color-bg-hint: #eee; 65 | @color-bg-thead: #fafafa; 66 | @color-bg-disabled: #d8d8d8; 67 | @color-bg-disabled-primary: lighten(@color-primary, 30%); 68 | @color-bg-disabled-success: lighten(@color-success, 30%); 69 | @color-bg-disabled-warning: lighten(@color-warning, 30%); 70 | @color-bg-disabled-link: lighten(@color-link, 30%); 71 | @color-bg-disabled-error: lighten(@color-error, 30%); 72 | @color-bg-disabled-danger: lighten(@color-danger, 30%); 73 | 74 | // border colors 75 | @color-border-main: #e5e5e5; 76 | @color-border-secondary: #eee; 77 | @color-border-dash: #d8d8d8; 78 | @color-border-button: #d8d8d8; 79 | @color-border-disabled: #d8d8d8; 80 | 81 | // font-weight specification 82 | // https://docs.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass 83 | @font-weight-thin: 100; 84 | @font-weight-extra-light: 200; 85 | @font-weight-ultra-light: 200; 86 | @font-weight-light: 300; 87 | @font-weight-normal: 400; 88 | @font-weight-regular: 400; 89 | @font-weight-medium: 500; 90 | @font-weight-semi-bold: 600; 91 | @font-weight-demi-bold: 600; 92 | @font-weight-bold: 700; 93 | @font-weight-extra-bold: 800; 94 | @font-weight-ultra-bold: 800; 95 | @font-weight-black: 900; 96 | @font-weight-heavy: 900; 97 | 98 | // font-family config 99 | @font-family-normal: Roboto; 100 | 101 | // fonts 102 | // syntax: [font-weight] [font-size]/[line-height] [font-family] 103 | // https://developer.mozilla.org/en-US/docs/Web/CSS/font#live_sample 104 | @font-headline-extra-large: @font-weight-bold 30px/32px @font-family-normal; 105 | @font-headline-large: @font-weight-bold 26px/28px @font-family-normal; 106 | @font-headline-main: @font-weight-bold 22px/24px @font-family-normal; 107 | @font-headline-modal: @font-weight-medium 20px/22px @font-family-normal; 108 | @font-headline-card: @font-weight-bold 18px/20px @font-family-normal; 109 | @font-headline-card-secondary: @font-weight-medium 16px/18px @font-family-normal; 110 | @font-normal: @font-weight-regular 14px/18px @font-family-normal; 111 | @font-normal-loose: @font-weight-regular 14px/20px @font-family-normal; 112 | @font-hint: @font-weight-regular 12px/14px @font-family-normal; 113 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 8 | 9 | 10 | 11 |
12 | 56 |
57 | 58 | 59 | 60 |
61 |
62 | 63 | -------------------------------------------------------------------------------- /test/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /test/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | module.exports = []; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.paths.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "esModuleInterop": true, 6 | "experimentalDecorators": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "jsx": "react", 9 | "lib": ["es6", "dom"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noImplicitAny": true, 13 | "noImplicitReturns": true, 14 | "noImplicitThis": true, 15 | "noUnusedLocals": true, 16 | "preserveConstEnums": true, 17 | "removeComments": true, 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "sourceMap": true, 21 | "strictNullChecks": true, 22 | "suppressImplicitAnyIndexErrors": true, 23 | "target": "es6" 24 | }, 25 | "exclude": [ 26 | "node_modules", 27 | "next", 28 | "dist", 29 | "build", 30 | "out", 31 | "cache" 32 | ], 33 | "include": [ 34 | "**/*.ts", 35 | "**/*.tsx" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "paths": { 5 | "@/*": ["./*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // commonjs tree shaking 2 | const path = require('path'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const webpack = require('webpack'); 5 | const CopyPlugin = require('copy-webpack-plugin'); 6 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 7 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 8 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 9 | const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'); 10 | const HappyPack = require('happypack'); 11 | const os = require('os'); 12 | const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); 13 | const tsImportPluginFactory = require('ts-import-plugin'); 14 | const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin'); 15 | const autoprefixer = require('autoprefixer'); 16 | 17 | const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); 18 | 19 | const { NODE_ENV } = process.env; 20 | 21 | const plugins = [ 22 | new CleanWebpackPlugin(), 23 | new HtmlWebpackPlugin({ 24 | template: path.resolve(__dirname, 'template.html'), 25 | title: 'react-hooks-i18n-template', 26 | minify: { 27 | removeComments: true, 28 | collapseWhitespace: true, 29 | removeRedundantAttributes: true, 30 | useShortDoctype: true, 31 | removeEmptyAttributes: true, 32 | removeStyleLinkTypeAttributes: true, 33 | keepClosingSlash: true, 34 | minifyJS: true, 35 | minifyCSS: true, 36 | minifyURLs: true, 37 | }, 38 | }), 39 | new webpack.DefinePlugin({ 40 | 'window.__CID__': JSON.stringify(process.env.cid || 'sg'), 41 | }), 42 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 43 | new CopyPlugin([{ from: 'public', to: 'public' }]), 44 | new MiniCssExtractPlugin({ 45 | filename: 'chunk.[name].[contenthash:8].css', 46 | ignoreOrder: true, 47 | }), 48 | new OptimizeCssAssetsWebpackPlugin({ 49 | cssProcessPluginOptions: { 50 | preset: ['default', { discardComments: { removeAll: true } }], 51 | }, 52 | }), 53 | new webpack.optimize.LimitChunkCountPlugin({ 54 | maxChunks: 10, 55 | minChunkSize: 10000, 56 | }), 57 | new HappyPack({ 58 | id: 'babel', 59 | loaders: ['cache-loader', 'babel-loader?cacheDirectory'], 60 | threadPool: happyThreadPool, 61 | }), 62 | new HappyPack({ 63 | id: 'tsloader', 64 | loaders: ['cache-loader', 'ts-loader'], 65 | threadPool: happyThreadPool, 66 | }), 67 | new HappyPack({ 68 | id: 'css', 69 | loaders: ['style-loader', 'css-loader'], 70 | threadPool: happyThreadPool, 71 | }), 72 | new AntdDayjsWebpackPlugin(), 73 | ]; 74 | 75 | if (process.env.ANALYSE) { 76 | plugins.push( 77 | new BundleAnalyzerPlugin({ 78 | analyzerMode: 'server', 79 | generateStatsFile: true, 80 | statsOptions: { source: false }, 81 | }) 82 | ); 83 | } 84 | 85 | if (NODE_ENV !== 'production') { 86 | plugins.push(new webpack.SourceMapDevToolPlugin()); 87 | } 88 | 89 | const entry = ['@babel/polyfill', './src/index.tsx']; 90 | 91 | const smp = new SpeedMeasurePlugin(); 92 | 93 | module.exports = smp.wrap({ 94 | stats: { 95 | entrypoints: false, 96 | children: false, 97 | }, 98 | entry, 99 | resolve: { 100 | mainFields: ['module', 'main', 'browser'], 101 | alias: { 102 | root: path.resolve(__dirname), 103 | 'react-dom': '@hot-loader/react-dom', 104 | '@': path.resolve(__dirname, 'src'), 105 | }, 106 | extensions: ['.js', '.tsx', '.d.ts', '.ts', '.jsx', '.json', '.css'], 107 | }, 108 | output: { 109 | filename: 'main.[hash:8].js', 110 | path: path.resolve(__dirname, 'dist/static'), 111 | chunkFilename: 'chunk.[name].[contenthash:8].js', 112 | publicPath: '/', 113 | }, 114 | mode: NODE_ENV, 115 | devtool: false, 116 | plugins, 117 | module: { 118 | rules: [ 119 | // static assets comes first 120 | { 121 | test: /fonts\/.+?\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/, 122 | loader: 'url-loader', 123 | options: { 124 | limit: 10000, 125 | name: '[name].[hash:7].[ext]', 126 | outputPath: 'fonts/', 127 | }, 128 | }, 129 | { 130 | test: /\.(jpg|jpeg|bmp|png|webp|gif)$/i, 131 | use: [ 132 | { 133 | loader: 'url-loader', 134 | options: { 135 | limit: 10000, 136 | name: '[name].[hash:8].[ext]', 137 | outputPath: 'images/', 138 | }, 139 | }, 140 | { 141 | loader: 'img-loader', 142 | }, 143 | ], 144 | }, 145 | { 146 | test: /\.(js|jsx)$/, 147 | exclude: /node_modules/, 148 | use: 'happypack/loader?id=babel', 149 | }, 150 | { 151 | test: /\.(tsx|ts)?$/, 152 | loader: 'ts-loader', 153 | options: { 154 | getCustomTransformers: () => ({ 155 | before: [ 156 | tsImportPluginFactory({ 157 | libraryName: 'antd', 158 | libraryDirectory: 'lib', 159 | style: true, 160 | }), 161 | ], 162 | }), 163 | compilerOptions: require('./tsconfig.json').compilerOptions, 164 | }, 165 | }, 166 | { 167 | test: /\.less$/, 168 | use: [ 169 | MiniCssExtractPlugin.loader, 170 | { 171 | loader: 'css-loader', 172 | }, 173 | { 174 | loader: 'postcss-loader', 175 | options: { 176 | plugins: () => [autoprefixer()], 177 | }, 178 | }, 179 | { 180 | loader: 'less-loader', 181 | options: { 182 | javascriptEnabled: true, 183 | sourceMap: true, 184 | modifyVars: { 185 | // inject our own global vars 186 | // https://github.com/ant-design/ant-design/issues/16464#issuecomment-491656849 187 | hack: `true;@import '${require.resolve('./src/vars.less')}';`, 188 | }, 189 | limit: 10000, 190 | name: '[name].[hash:7].[ext]', 191 | outputPath: 'styles/', 192 | }, 193 | }, 194 | ], 195 | }, 196 | { 197 | test: /\.css$/, 198 | loader: 'style-loader!css-loader', 199 | }, 200 | { 201 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 202 | exclude: /fonts/, 203 | use: [ 204 | { 205 | loader: 'babel-loader', 206 | }, 207 | { 208 | loader: '@svgr/webpack', 209 | options: { 210 | babel: false, 211 | icon: true, 212 | }, 213 | }, 214 | ], 215 | }, 216 | { 217 | type: 'javascript/auto', 218 | test: /\.mjs$/, 219 | use: [], 220 | }, 221 | ], 222 | }, 223 | optimization: { 224 | runtimeChunk: { 225 | name: 'manifest', 226 | }, 227 | splitChunks: { 228 | chunks: 'all', 229 | minSize: 30000, 230 | minChunks: 1, 231 | maxAsyncRequests: 5, 232 | maxInitialRequests: 3, 233 | cacheGroups: { 234 | vendor: { 235 | test: /[\\/]node_modules/, 236 | enforce: true, 237 | priority: 5, 238 | }, 239 | antd: { 240 | test: /[\\/]node_modules[\\/]antd[\\/]/, 241 | priority: 10, 242 | }, 243 | antdIcons: { 244 | test: /[\\/]node_modules[\\/]@ant-design[\\/]/, 245 | priority: 15, 246 | }, 247 | styles: { 248 | test: /\.(scss|css)$/, 249 | minChunks: 1, 250 | reuseExistingChunk: true, 251 | enforce: true, 252 | priority: 20, 253 | }, 254 | }, 255 | }, 256 | }, 257 | performance: { 258 | hints: false, 259 | }, 260 | devServer: { 261 | hot: true, 262 | port: 8080, 263 | proxy: [ 264 | { 265 | context: ['/api'], 266 | target: 'your_config', 267 | changeOrigin: true, 268 | onProxyRes(proxyRes, _, res) { 269 | const cookies = proxyRes.headers['set-cookie'] || []; 270 | const re = /domain=[\w.]+;/i; 271 | const newCookie = cookies.map((cookie) => 272 | cookie.replace(re, 'Domain=localhost;')); 273 | res.writeHead(proxyRes.statusCode, { 274 | ...proxyRes.headers, 275 | 'set-cookie': newCookie, 276 | }); 277 | }, 278 | }, 279 | ], 280 | }, 281 | watchOptions: { 282 | ignored: /node_modules/, 283 | }, 284 | }); 285 | --------------------------------------------------------------------------------