add translation logic

This commit is contained in:
Michael Schramm 2020-06-02 02:12:02 +02:00
parent c1ba7f2556
commit 1f52969414
25 changed files with 252 additions and 67 deletions

View File

@ -1,8 +1,26 @@
import {Button} from 'antd'
import Link from 'next/link'
import {useRouter} from 'next/router'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {clearAuth, withAuth} from '../with.auth'
interface Props {
me?: {
id: string
username: string
}
}
const AuthFooterInner: React.FC<Props> = props => {
const { t } = useTranslation()
const router = useRouter()
const logout = () => {
clearAuth()
router.reload()
}
export const AuthFooter: React.FC = () => {
return (
<div
style={{
@ -17,25 +35,43 @@ export const AuthFooter: React.FC = () => {
type={'link'}
ghost
>
Admin
</Button>
</Link>
<Link href={'/login'}>
<Button
type={'link'}
ghost
>
Login
</Button>
</Link>
<Link href={'/register'}>
<Button
type={'link'}
ghost
>
Register
{t('admin')}
</Button>
</Link>
{props.me ? (
[
<span style={{color: '#FFF'}}>
Hi, {props.me.username}
</span>,
<Button
key={'Logout'}
type={'link'}
ghost
onClick={logout}
>
{t('logout')}
</Button>
]
): (
[
<Link href={'/login'}>
<Button
type={'link'}
ghost
>
{t('login')}
</Button>
</Link>,
<Link href={'/register'}>
<Button
type={'link'}
ghost
>
{t('register')}
</Button>
</Link>
]
)}
<Button
type={'link'}
@ -52,3 +88,5 @@ export const AuthFooter: React.FC = () => {
</div>
)
}
export const AuthFooter = withAuth(AuthFooterInner)

View File

@ -43,6 +43,10 @@ export const withAuth = (Component, roles: string[] = []): React.FC => {
const {loading, data, error} = useQuery<MeQueryData>(ME_QUERY)
useEffect(() => {
if (roles.length === 0) {
setAccess(true)
return
}
if (!error) {
return
}
@ -56,6 +60,7 @@ export const withAuth = (Component, roles: string[] = []): React.FC => {
useEffect(() => {
if (!data || roles.length === 0) {
setAccess(true)
return
}
@ -79,6 +84,6 @@ export const withAuth = (Component, roles: string[] = []): React.FC => {
return <LoadingPage message={'Checking Credentials'} />
}
return <Component {...props} />
return <Component me={data && data.me} {...props} />
};
}

View File

@ -3,7 +3,7 @@ import {gql} from 'apollo-boost'
export interface MeQueryData {
me: {
id: string
username: string
roles: string[]
}
}
@ -13,6 +13,7 @@ export const ME_QUERY = gql`
me {
id
roles
username
}
}
`

16
i18n.ts
View File

@ -1,5 +1,21 @@
import i18n from "i18next"
import detector from "i18next-browser-languagedetector"
import {initReactI18next} from "react-i18next"
import {resources} from './locales'
export const languages = [
'de',
'en',
]
i18n
.use(detector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
resources,
defaultNS: 'common',
react: {
useSuspense: process.browser,
}
})

6
locales/en/admin.ts Normal file
View File

@ -0,0 +1,6 @@
export const admin = {
home: 'Home',
users: 'Users',
forms: 'Forms',
submissions: 'Submissions',
}

8
locales/en/common.ts Normal file
View File

@ -0,0 +1,8 @@
export const common = {
logout: 'Logout',
login: "Login",
register: "Create Account",
recover: 'Lost Password',
admin: "Admin",
'mandatory-fields-missing': 'Mandatory fields missing',
}

5
locales/en/form.ts Normal file
View File

@ -0,0 +1,5 @@
export const form = {
building: 'Building Form',
submitted: 'Thank you for your submission!',
restart: 'Restart Form',
}

17
locales/en/index.ts Normal file
View File

@ -0,0 +1,17 @@
import {admin} from './admin'
import {common} from './common'
import {form} from './form'
import {login} from './login'
import {register} from './register'
import {statistic} from './statistic'
import {validation} from './validation'
export const en = {
admin,
common,
form,
login,
register,
statistic,
validation,
}

9
locales/en/login.ts Normal file
View File

@ -0,0 +1,9 @@
export const login = {
'welcome-back': 'Welcome back!',
'invalid-login-credentials': 'username / password are invalid',
'username-required': 'Please input your username!',
'username-placeholder': 'Username',
'password-required': 'Please input your password!',
'password-placeholder': 'Password',
'login-now': 'Login Now',
}

8
locales/en/register.ts Normal file
View File

@ -0,0 +1,8 @@
export const register = {
welcome: 'Welcome, please also confirm your email',
'credentials-already-in-use': 'Some data already in use!',
'password-min-length': 'Must be longer than or equal to 5 characters!',
'email-required': 'Please input your email!',
'register-now': 'Register Now',
'goto-login': 'Have an account? Go to login',
}

5
locales/en/statistic.ts Normal file
View File

@ -0,0 +1,5 @@
export const statistic = {
'total-forms': 'Total Forms',
'total-users': 'Total Users',
'total-submissions': 'Total Submissions',
}

3
locales/en/validation.ts Normal file
View File

@ -0,0 +1,3 @@
export const validation = {
'invalid-email': 'Must be a valid email!',
}

5
locales/index.ts Normal file
View File

@ -0,0 +1,5 @@
import {en} from './en'
export const resources = {
en,
}

View File

@ -19,12 +19,15 @@
"axios": "^0.19.2",
"dayjs": "^1.8.27",
"graphql": "^15.0.0",
"i18next": "^19.4.5",
"i18next-browser-languagedetector": "^4.2.0",
"next": "9.4.0",
"next-images": "^1.4.0",
"next-redux-wrapper": "^6.0.0",
"react": "16.13.1",
"react-color": "^2.18.1",
"react-dom": "16.13.1",
"react-i18next": "^11.5.0",
"react-icons": "^3.10.0",
"react-id-swiper": "^3.0.0",
"react-redux": "^7.2.0",

View File

@ -22,8 +22,10 @@ import {NextPage} from 'next'
import Link from 'next/link'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
const Index: NextPage = () => {
const { t } = useTranslation()
const router = useRouter()
const [form] = useForm()
const [saving, setSaving] = useState(false)
@ -70,8 +72,8 @@ const Index: NextPage = () => {
title={loading ? 'Loading Form' : `Edit Form "${data.form.title}"`}
selected={'forms'}
breadcrumbs={[
{ href: '/admin', name: 'Home' },
{ href: '/admin/forms', name: 'Form' },
{ href: '/admin', name: t('admin:home') },
{ href: '/admin/forms', name: t('admin:forms') },
]}
extra={[
<Link href={'/admin/forms/[id]/submissions'} as={`/admin/forms/${router.query.id}/submissions`}>

View File

@ -11,6 +11,7 @@ import {NextPage} from 'next'
import Link from 'next/link'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import {SubmissionValues} from '../../../../components/form/admin/submission.values'
import {
ADMIN_PAGER_SUBMISSION_QUERY,
@ -21,6 +22,7 @@ import {
} from '../../../../graphql/query/admin.pager.submission.query'
const Submissions: NextPage = () => {
const { t } = useTranslation()
const router = useRouter()
const [pagination, setPagination] = useState<PaginationProps>({
pageSize: 25,
@ -74,12 +76,12 @@ const Submissions: NextPage = () => {
return (
<Structure
title={loading ? 'Loading Submissions' : 'Submissions'}
title={t('admin:submissions')}
selected={'forms'}
loading={loading}
breadcrumbs={[
{ href: '/admin', name: 'Home' },
{ href: '/admin/forms', name: 'Form' },
{ href: '/admin', name: t('admin:home') },
{ href: '/admin/forms', name: t('admin:forms') },
{ href: '/admin/forms/[id]', name: loading || !form ? 'Loading Form' : `Edit Form "${form.title}"`, as: `/admin/forms/${router.query.id}` },
]}
padded={false}

View File

@ -9,6 +9,7 @@ import {AdminFormQueryData} from 'graphql/query/admin.form.query'
import {NextPage} from 'next'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import {
ADMIN_FORM_CREATE_MUTATION,
AdminFormCreateMutationData,
@ -16,6 +17,7 @@ import {
} from '../../../graphql/mutation/admin.form.create.mutation'
const Create: NextPage = () => {
const { t } = useTranslation()
const router = useRouter()
const [form] = useForm()
const [saving, setSaving] = useState(false)
@ -47,8 +49,8 @@ const Create: NextPage = () => {
title={'Create New Form'}
selected={'forms'}
breadcrumbs={[
{ href: '/admin', name: 'Home' },
{ href: '/admin/forms', name: 'Form' },
{ href: '/admin', name: t('admin:home') },
{ href: '/admin/forms', name: t('admin:forms') },
]}
extra={[
<Button

View File

@ -17,6 +17,7 @@ import {
import {NextPage} from 'next'
import Link from 'next/link'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import {
ADMIN_FORM_DELETE_MUTATION,
AdminFormDeleteMutationData,
@ -24,6 +25,7 @@ import {
} from '../../../graphql/mutation/admin.form.delete.mutation'
const Index: NextPage = () => {
const { t } = useTranslation()
const [pagination, setPagination] = useState<PaginationProps>({
pageSize: 25,
})
@ -144,11 +146,11 @@ const Index: NextPage = () => {
return (
<Structure
title={'Forms'}
title={t('admin:forms')}
selected={'forms'}
loading={loading}
breadcrumbs={[
{ href: '/admin', name: 'Home' },
{ href: '/admin', name: t('admin:home') },
]}
padded={false}
extra={[

View File

@ -4,6 +4,7 @@ import Structure from 'components/structure'
import {withAuth} from 'components/with.auth'
import {NextPage} from 'next'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {
ADMIN_STATISTIC_QUERY,
AdminStatisticQueryData,
@ -11,25 +12,26 @@ import {
} from '../../graphql/query/admin.statistic.query'
const Index: NextPage = () => {
const { t } = useTranslation()
const {data, loading} = useQuery<AdminStatisticQueryData, AdminStatisticQueryVariables>(ADMIN_STATISTIC_QUERY)
return (
<Structure
title={'Home'}
title={t('admin:home')}
selected={'home'}
loading={loading}
>
<Row gutter={16}>
<Col span={8}>
<Statistic title="Total Forms" value={data && data.forms.total} />
<Statistic title={t('statistic:total-forms')} value={data && data.forms.total} />
</Col>
<Col span={8}>
<Statistic title="Total Users" value={data && data.users.total} />
<Statistic title={t('statistic:total-users')} value={data && data.users.total} />
</Col>
<Col span={8}>
<Statistic title="Total Submissions" value={data && data.submissions.total} />
<Statistic title={t('statistic:total-submissions')} value={data && data.submissions.total} />
</Col>
</Row>
</Structure>

View File

@ -6,6 +6,7 @@ import {withAuth} from 'components/with.auth'
import {NextPage} from 'next'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import {cleanInput} from '../../../../components/clean.input'
import {BaseDataTab} from '../../../../components/user/admin/base.data.tab'
import {
@ -16,6 +17,7 @@ import {
import {ADMIN_USER_QUERY, AdminUserQueryData, AdminUserQueryVariables} from '../../../../graphql/query/admin.user.query'
const Index: NextPage = () => {
const { t } = useTranslation()
const router = useRouter()
const [form] = useForm()
const [saving, setSaving] = useState(false)
@ -33,11 +35,6 @@ const Index: NextPage = () => {
const save = async (formData: AdminUserQueryData) => {
setSaving(true)
console.log('data', formData)
try {
const next = (await update({
variables: cleanInput(formData),
@ -60,8 +57,8 @@ const Index: NextPage = () => {
title={loading ? 'Loading User' : `Edit User "${data.user.email}"`}
selected={'users'}
breadcrumbs={[
{ href: '/admin', name: 'Home' },
{ href: '/admin/users', name: 'Users' },
{ href: '/admin', name: t('admin:home') },
{ href: '/admin/users', name: t('admin:users') },
]}
extra={[
<Button

View File

@ -8,6 +8,7 @@ import {withAuth} from 'components/with.auth'
import {NextPage} from 'next'
import Link from 'next/link'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import {DateTime} from '../../../components/date.time'
import {UserRole} from '../../../components/user/role'
import {
@ -23,6 +24,7 @@ import {
} from '../../../graphql/query/admin.pager.user.query'
const Index: NextPage = () => {
const { t } = useTranslation()
const [pagination, setPagination] = useState<PaginationProps>({
pageSize: 10,
})
@ -104,10 +106,10 @@ const Index: NextPage = () => {
return (
<Structure
title={'Users'}
title={t('admin:users')}
loading={loading}
breadcrumbs={[
{ href: '/admin', name: 'Home' },
{ href: '/admin', name: t('admin:home') },
]}
padded={false}
>

View File

@ -8,6 +8,7 @@ import {FORM_QUERY, FormQueryData, FormQueryVariables} from 'graphql/query/form.
import {NextPage} from 'next'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
import Swiper from 'react-id-swiper'
import {ReactIdSwiperProps} from 'react-id-swiper/lib/types'
import * as OriginalSwiper from 'swiper'
@ -18,6 +19,7 @@ interface Props {
}
const Index: NextPage<Props> = () => {
const { t } = useTranslation()
const router = useRouter()
const id = router.query.id as string
const [swiper, setSwiper] = useState<OriginalSwiper.default>(null)
@ -31,7 +33,7 @@ const Index: NextPage<Props> = () => {
if (loading) {
return (
<LoadingPage message={'Building Form'} />
<LoadingPage message={t('form:build')} />
)
}
@ -99,8 +101,8 @@ const Index: NextPage<Props> = () => {
if (!data.form.endPage.show) {
Modal.success({
content: 'Thank you for your submission!',
okText: 'Restart Form',
content: t('form:submitted'),
okText: t('from:restart'),
onOk: () => {
window.location.reload()
}

View File

@ -9,8 +9,10 @@ import {NextPage} from 'next'
import Link from 'next/link'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
const Index: NextPage = () => {
const { t } = useTranslation()
const [form] = useForm()
const router = useRouter()
const [loading, setLoading] = useState(false)
@ -28,18 +30,18 @@ const Index: NextPage = () => {
result.data.tokens.refresh
)
message.success('Welcome back!')
message.success(t('login:welcome-back'))
router.push('/admin')
} catch (e) {
message.error('username / password are invalid')
message.error(t('login:invalid-login-credentials'))
}
setLoading(false)
}
const failed = () => {
message.error('mandatory fields missing')
message.error(t('mandatory-fields-missing'))
}
return (
@ -69,21 +71,21 @@ const Index: NextPage = () => {
<Form.Item
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
rules={[{ required: true, message: t('login:username-required') }]}
>
<Input
size="large"
placeholder="Username"
placeholder={t('login:username-placeholder')}
/>
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
rules={[{ required: true, message: t('login:password-required') }]}
>
<Input.Password
size="large"
placeholder={'Password'}
placeholder={t('login:password-placeholder')}
/>
</Form.Item>
@ -94,7 +96,7 @@ const Index: NextPage = () => {
htmlType="submit"
block
>
Login Now
{t('login:login-now')}
</Button>
</Form.Item>
@ -110,7 +112,7 @@ const Index: NextPage = () => {
type={'link'}
ghost
>
Create Account
{t('register')}
</Button>
</Link>
<Link href={'/login/recover'}>
@ -118,7 +120,7 @@ const Index: NextPage = () => {
type={'link'}
ghost
>
Lost password
{t('recover')}
</Button>
</Link>
</Button.Group>

View File

@ -9,8 +9,10 @@ import {NextPage} from 'next'
import Link from 'next/link'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import {useTranslation} from 'react-i18next'
const Register: NextPage = () => {
const { t } = useTranslation()
const [form] = useForm()
const router = useRouter()
const [loading, setLoading] = useState(false)
@ -32,17 +34,17 @@ const Register: NextPage = () => {
result.data.tokens.refresh
)
message.success('Welcome, please also confirm your email')
message.success(t('register:welcome'))
router.push('/')
} catch (e) {
message.error('Some data already in use!')
message.error(t('register:credentials-already-in-use'))
setLoading(false)
}
}
const failed = () => {
message.error('mandatory fields missing')
message.error(t('mandatory-fields-missing'))
}
return (
@ -72,19 +74,19 @@ const Register: NextPage = () => {
<Form.Item
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
rules={[{ required: true, message: t('login:username-required') }]}
>
<Input
size="large"
placeholder="Username"
placeholder={t('login:username-placeholder')}
/>
</Form.Item>
<Form.Item
name="email"
rules={[
{ required: true, message: 'Please input your email!' },
{ type: 'email', message: 'Must be a valid email!' }
{ required: true, message: t('register:email-required') },
{ type: 'email', message: t('validation:invalid-email') }
]}
>
<Input
@ -96,13 +98,13 @@ const Register: NextPage = () => {
<Form.Item
name="password"
rules={[
{ required: true, message: 'Please input your password!' },
{ min: 5, message: 'Must be longer than or equal to 5 characters!' },
{ required: true, message: t('login:password-required') },
{ min: 5, message: t('register:password-min-length') },
]}
>
<Input.Password
size="large"
placeholder={'Password'}
placeholder={t('login:password-placeholder')}
/>
</Form.Item>
@ -113,7 +115,7 @@ const Register: NextPage = () => {
htmlType="submit"
block
>
Register Now
{t('register:register-now')}
</Button>
</Form.Item>
@ -129,7 +131,7 @@ const Register: NextPage = () => {
type={'link'}
ghost
>
Have an account? Go to login
{t('register:goto-login')}
</Button>
</Link>
</Button.Group>

View File

@ -1021,6 +1021,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.3.1":
version "7.10.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.1":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@ -3368,6 +3375,13 @@ html-comment-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7"
integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==
html-parse-stringify2@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=
dependencies:
void-elements "^2.0.1"
htmlparser2@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78"
@ -3383,6 +3397,20 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
i18next-browser-languagedetector@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.2.0.tgz#82e35d31f88a1d7c2b6d5913bf8c8481cd40aafb"
integrity sha512-qRSCBWgDUSqVQb3sTxkDC+ImYLhF+wB387Y1RpOcJvyex+V3abi+W83n4Awy+dx719AOBbKTy97FjrUGrAhbyw==
dependencies:
"@babel/runtime" "^7.5.5"
i18next@^19.4.5:
version "19.4.5"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.4.5.tgz#f9ea8bbb48d1ec66bc3436f0bb74a16b11821e11"
integrity sha512-aLvSsURoupi3x9IndmV6+m3IGhzLzhYv7Gw+//K3ovdliyGcFRV0I1MuddI0Bk/zR7BG1U+kJOjeHFUcUIdEgg==
dependencies:
"@babel/runtime" "^7.3.1"
icss-utils@^4.0.0, icss-utils@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467"
@ -5434,6 +5462,14 @@ react-dom@16.13.1:
prop-types "^15.6.2"
scheduler "^0.19.1"
react-i18next@^11.5.0:
version "11.5.0"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.5.0.tgz#84a9bb535d44c0c1b336b94de164515c2cc2a714"
integrity sha512-V6rUT7MzYBdFCgUrhfr78FHRfnY3CFoR75ET9EP5Py5UPHKyaGiK1MvPx03TesLwsmIaVHlRFU/WLzqCedXevA==
dependencies:
"@babel/runtime" "^7.3.1"
html-parse-stringify2 "2.0.1"
react-icons@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.10.0.tgz#6c217a2dde2e8fa8d293210023914b123f317297"
@ -6548,6 +6584,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
void-elements@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
warning@^4.0.1, warning@^4.0.3, warning@~4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"