diff --git a/src/App.js b/src/App.js index c97f8b4..da14c3e 100644 --- a/src/App.js +++ b/src/App.js @@ -15,6 +15,9 @@ import Colors from 'constants/Colors'; import AuthErrorCodes from 'constants/AuthErrorCodes'; import CustomAuthenticator from 'components/Auth/CustomAuthenticator'; +import '@aws-amplify/ui-react/styles.css'; +import './i18n/Amplify'; + const useStyles = makeStyles((theme) => ({ root: { display: 'flex', @@ -40,7 +43,7 @@ const authListener = ({ payload: { event, data } }) => { } }; -export default function App({ user }) { +function App({ user }) { const classes = useStyles(); const { t } = useTranslation(); @@ -98,7 +101,7 @@ App.propTypes = { user: PropTypes.any, }; -function AuthenticatedApp({ user }) { +export default function AuthenticatedApp({ user }) { return ( diff --git a/src/components/Auth/CustomAuthenticator.js b/src/components/Auth/CustomAuthenticator.js index d442903..7b2a16a 100644 --- a/src/components/Auth/CustomAuthenticator.js +++ b/src/components/Auth/CustomAuthenticator.js @@ -5,6 +5,9 @@ import { } from '@aws-amplify/ui-react'; import querystring from 'query-string'; import { useTranslation } from 'react-i18next'; +import { I18n } from 'aws-amplify'; + +import { dict } from 'i18n/Amplify'; export default function CustomAuthenticator({ children = , @@ -46,6 +49,8 @@ export default function CustomAuthenticator({ }; useEffect(() => { + I18n.putVocabularies(dict); + const { state } = querystring.parse(window.location.search); setAuthState(state || 'signIn'); }, []); diff --git a/src/components/Avatar.js b/src/components/Avatar.js index ecaa09a..a44e00e 100644 --- a/src/components/Avatar.js +++ b/src/components/Avatar.js @@ -77,7 +77,7 @@ export default function CustomAvatar({ } CustomAvatar.propTypes = { - src: PropTypes.string.isRequired, + src: PropTypes.string, fallbackSrc: PropTypes.string, size: PropTypes.number, variant: PropTypes.string, diff --git a/src/components/LanguageSelector/LanguageSelector.js b/src/components/LanguageSelector/LanguageSelector.js index 9c76ba0..626e6a4 100644 --- a/src/components/LanguageSelector/LanguageSelector.js +++ b/src/components/LanguageSelector/LanguageSelector.js @@ -11,18 +11,15 @@ import { useHistory } from 'react-router-dom'; import { getCache, setCache } from 'utils/cache'; import list from 'i18n/list'; -import zhTwAuthString from 'i18n/Amplify_zh-TW'; -import jaAuthString from 'i18n/Amplify_ja'; -import koAuthString from 'i18n/Amplify_ko'; +// import zhTwAuthString from 'i18n/Amplify_zh-TW'; +// import jaAuthString from 'i18n/Amplify_ja'; +// import koAuthString from 'i18n/Amplify_ko'; -const dict = { - 'zh': zhTwAuthString, - 'ja': jaAuthString, - 'ko': koAuthString, -}; - -I18n.putVocabularies(dict); -I18n.setLanguage('zh-Hant'); +// const dict = { +// 'zh': zhTwAuthString, +// 'ja': jaAuthString, +// 'ko': koAuthString, +// }; const useStyles = makeStyles((theme) => ({ formControl: { diff --git a/src/i18n/Amplify.js b/src/i18n/Amplify.js new file mode 100644 index 0000000..bfe0bf1 --- /dev/null +++ b/src/i18n/Amplify.js @@ -0,0 +1,96 @@ +/* eslint-disable quote-props */ +import { I18n } from 'aws-amplify'; + +export const dict = { + 'zh': { + 'Account recovery requires verified contact information': '恢復帳戶需要驗證聯繫方式', + 'Back to Sign In': '回到登入', + 'Change Password': '改變密碼', + Changing: '變更中', + Code: '驗證碼', + Confirm: '確認', + 'Confirm Password': '確認密碼', + 'Please confirm your Password': '確認密碼', + 'Confirm SMS Code': 'Confirm SMS Code', + 'Confirm Sign In': '確認登入', + 'Confirm Sign Up': '確認註冊', + 'Confirm TOTP Code': 'Confirm TOTP Code', + 'Confirm a Code': '驗證碼', + 'Confirmation Code': '驗證碼', + Confirming: '確認中', + 'Create Account': '創建帳戶', + 'Create a new account': '創建帳戶', + 'Create account': '創建帳戶', + Email: '電子信箱', + 'Enter your code': '輸入驗證碼', + 'Enter your email': '輸入電子信箱', + 'Enter your phone number': '輸入電話號碼', + 'Enter your username': '輸入帳號', + 'Enter your Username': '輸入帳號', + 'Enter your Password': '輸入密碼', + 'Forgot Password': '忘記密碼', + 'Forgot your password?': '忘記密碼', + 'Hide password': '隱藏密碼', + 'Incorrect username or password': '帳號或密碼錯誤', + 'Invalid password format': '密碼格式錯誤', + 'Invalid phone number format': '電話格式錯誤,請使用格式 +12345678900', + 'Invalid email address format.': '電子信箱格式錯誤', + Loading: '載入中', + Name: '姓名', + 'Enter your Name': '姓名', + 'New Password': '新密碼', + 'New password': '新密碼', + Password: '密碼', + 'Phone': '電話', + 'Phone Number': '電話', + 'Enter your Phone Number': '電話', + 'Resend Code': '重新發送驗證碼', + 'Resend a Code': '重新發送驗證碼', + 'Reset your Password': '重設密碼', + 'Reset your password': '重設密碼', + 'Reset Password': '重設密碼', + 'Send Code': '發送驗證碼', + 'Send code': '發送驗證碼', + Sending: '發送中', + 'Setup TOTP': 'Setup TOTP', + 'Show password': '顯示密碼', + 'Sign In': '登入', + 'Sign In with Amazon': 'Sign In with Amazon', + 'Sign In with Apple': 'Sign In with Apple', + 'Sign In with Facebook': 'Sign In with Facebook', + 'Sign In with Google': 'Sign In with Google', + 'Sign Out': '退出', + 'Sign Up': '註冊', + 'Sign in': '登入', + 'Sign in to your account': 'Sign in to your account', + 'Signing in': '登入中', + Skip: '跳過', + Submit: '送出', + Submitting: '送出中', + 'User already exists': '帳號已經存在', + 'User does not exist': '帳號不存在', + Username: '帳號/電子信箱', + Verify: '驗證', + 'Verify Contact': '驗證聯繫方式', + or: '或者', + TypeError: '格式錯誤', + 'We Emailed You': '驗證電子信箱', + 'We Sent A Code': '驗證電子信箱', + 'We Texted You': '驗證電子信箱', + 'Your code is on the way. To log in, enter the code we sent you.': '請查看您的電子信箱,驗證碼需數分鐘才會寄達。', + 'Your code is on the way. To log in, enter the code we sent you': '請查看您的電子信箱', + 'Your passwords must match': '密碼不同', + 'Password must have at least 8 characters': '至少8碼', + 'Creating Account': '帳戶創建中', + 'User does not exist.': '用戶不存在', + 'Your code is on the way. To log in, enter the code we emailed to': '驗證碼已寄送至信箱', + 'It may take a minute to arrive.': '(數分鐘內將寄達)', + 'It may take a minute to arrive': '(數分鐘內將寄達)', + 'Invalid verification code provided, please try again.': '授權碼錯誤,請重試', + 'Password cannot be empty': '密碼不能為空白', + }, +}; + +I18n.putVocabularies(dict); + +I18n.setLanguage('zh-Hant'); diff --git a/src/i18n/i18n.js b/src/i18n/i18n.js index 2886402..6a6a3a8 100644 --- a/src/i18n/i18n.js +++ b/src/i18n/i18n.js @@ -5,30 +5,28 @@ import yaml from 'js-yaml'; const defaultLng = 'zh-TW'; -i18n - .use(Backend) - .use(initReactI18next) - .init({ - lng: defaultLng, - backend: { - /* translation file path */ - loadPath: `${process.env.PUBLIC_URL}/assets/i18n/{{ns}}/{{lng}}.yaml`, - parse: (data) => yaml.load(data), - }, - fallbackLng: defaultLng, - debug: process.env.NODE_ENV === 'development', - /* can have multiple namespace, in case you want to divide a huge translation into smaller pieces and load them on demand */ - ns: ['translations'], - defaultNS: 'translations', - keySeparator: false, - interpolation: { - escapeValue: false, - formatSeparator: ',', - }, - react: { - // wait: true, - useSuspense: false, - }, - }); +const i18nInit = async () => { + await i18n + .use(Backend) + .use(initReactI18next) + .init({ + lng: defaultLng, + backend: { + /* translation file path */ + loadPath: `${process.env.PUBLIC_URL}/assets/i18n/{{ns}}/{{lng}}.yaml`, + parse: (data) => yaml.load(data), + }, + fallbackLng: defaultLng, + debug: process.env.NODE_ENV === 'development', + /* can have multiple namespace, in case you want to divide a huge translation into smaller pieces and load them on demand */ + ns: ['translations'], + defaultNS: 'translations', + keySeparator: false, + interpolation: { + escapeValue: false, + formatSeparator: ',', + }, + }); +}; -export default i18n; +export default i18nInit; diff --git a/src/index.js b/src/index.js index 84c860d..2936168 100644 --- a/src/index.js +++ b/src/index.js @@ -25,8 +25,7 @@ import { AmplifyProvider } from '@aws-amplify/ui-react'; import 'react-calendar-heatmap/dist/styles.css'; import 'react-big-calendar/lib/css/react-big-calendar.css'; import './global'; -import './i18n/i18n'; -// import './i18n/Amplify'; +import i18nInit from './i18n/i18n'; import './index.css'; import './Amplify.css'; @@ -88,12 +87,22 @@ const useStyles = makeStyles((theme) => ({ }), marginLeft: 0, }, + root: { + display: 'flex', + [theme.breakpoints.up('md')]: { + overflow: 'auto', + }, + [theme.breakpoints.down('md')]: { + overflowX: 'hidden', + }, + height: 'calc(100vh - 64px)', // 64px appbar + 52px footer + }, })); const initialPath = history.location; // console.log(`initialPath`, initialPath); -// const publicRoutes = appRoutes.filter(({ roles }) => !roles || roles.length === 0); +const publicRoutes = appRoutes.filter(({ roles }) => !roles || roles.length === 0); function ReactApp() { const classes = useStyles(); @@ -103,6 +112,14 @@ function ReactApp() { const [user, setUser] = React.useState(); const [filteredRoutes, setFilteredRoutes] = React.useState([]); const [open, setOpen] = React.useState(false); + const [i18nReady, setI18nReady] = React.useState(false); + + React.useEffect(() => { + (async () => { + await i18nInit(); + setI18nReady(true); + })(); + }, []); React.useEffect(() => { (async () => { @@ -172,9 +189,9 @@ function ReactApp() { setOpen(false); history.push(initialPath); - }, [user]); + }, [user, i18nReady]); - if (isLoading) { + if (isLoading || !i18nReady) { return (); } @@ -192,18 +209,20 @@ function ReactApp() { className={clsx(classes.content, { [classes.contentShift]: !open, })}> - - - {/* {user ? - - - : - - {publicRoutes.map(({ path, exact, component }, index)=>( - - ))} - } */} - +
+ + + {user ? + + + : + + {publicRoutes.map(({ path, exact, component }, index)=>( + + ))} + } + +
);