add german ad english translations

This commit is contained in:
Michael Schramm 2020-06-05 12:54:01 +02:00
parent f346ed3900
commit 0698c337f8
44 changed files with 633 additions and 137 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added ### Added
- `SPA` env variable to have static page with loading spinner before redirect - `SPA` env variable to have static page with loading spinner before redirect
- `de`, `fr`, `es`, `it`, `cn` base folders for translations
### Changed ### Changed

View File

@ -35,3 +35,16 @@
position: fixed position: fixed
} }
} }
.admin {
.sidemenu {
.ant-layout-sider-children {
display: flex;
flex-direction: column;
.language-selector {
padding-left: 12px !important;
}
}
}
}

View File

@ -1,8 +1,10 @@
import {Button} from 'antd' import {UpOutlined} from '@ant-design/icons/lib'
import {Button, Menu, Select} from 'antd'
import Link from 'next/link' import Link from 'next/link'
import {useRouter} from 'next/router' import {useRouter} from 'next/router'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next' import {useTranslation} from 'react-i18next'
import {languages} from '../../i18n'
import {clearAuth, withAuth} from '../with.auth' import {clearAuth, withAuth} from '../with.auth'
interface Props { interface Props {
@ -13,7 +15,7 @@ interface Props {
} }
const AuthFooterInner: React.FC<Props> = props => { const AuthFooterInner: React.FC<Props> = props => {
const { t } = useTranslation() const { t, i18n } = useTranslation()
const router = useRouter() const router = useRouter()
const logout = () => { const logout = () => {
@ -28,6 +30,8 @@ const AuthFooterInner: React.FC<Props> = props => {
bottom: 0, bottom: 0,
left: 0, left: 0,
right: 0, right: 0,
display: 'flex',
flexDirection: 'row',
}} }}
> >
<Link href={'/admin'}> <Link href={'/admin'}>
@ -72,14 +76,24 @@ const AuthFooterInner: React.FC<Props> = props => {
</Link> </Link>
] ]
)} )}
<div style={{flex: 1}} />
<Select
bordered={false}
value={i18n.language.replace(/-.*/, '')}
onChange={next => i18n.changeLanguage(next)}
style={{
color: '#FFF',
}}
suffixIcon={false}
>
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
</Select>
<Button <Button
type={'link'} type={'link'}
target={'_blank'} target={'_blank'}
ghost ghost
href={'https://www.ohmyform.com'} href={'https://www.ohmyform.com'}
style={{ style={{
float: 'right',
color: '#FFF' color: '#FFF'
}} }}
> >

View File

@ -1,13 +1,16 @@
import {Form, Input, Select, Switch, Tabs} from 'antd' import {Form, Input, Select, Switch, Tabs} from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import {TabPaneProps} from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next'
import {languages} from '../../../i18n' import {languages} from '../../../i18n'
export const BaseDataTab: React.FC<TabPaneProps> = props => { export const BaseDataTab: React.FC<TabPaneProps> = props => {
const { t } = useTranslation()
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item
label="Is Live" label={t('form:baseData.isLive')}
name={['form', 'isLive']} name={['form', 'isLive']}
valuePropName={'checked'} valuePropName={'checked'}
> >
@ -15,12 +18,12 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Title" label={t('form:baseData.title')}
name={['form', 'title']} name={['form', 'title']}
rules={[ rules={[
{ {
required: true, required: true,
message: 'Please provide a Title', message: t('validation:titleRequired'),
}, },
]} ]}
> >
@ -28,22 +31,22 @@ export const BaseDataTab: React.FC<TabPaneProps> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Language" label={t('form:baseData.language')}
name={['form', 'language']} name={['form', 'language']}
rules={[ rules={[
{ {
required: true, required: true,
message: 'Please select a Language', message: t('validation:languageRequired'),
}, },
]} ]}
> >
<Select> <Select>
{languages.map(language => <Select.Option value={language} key={language}>{language.toUpperCase()}</Select.Option> )} {languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Show Footer" label={t('form:baseData.showFooter')}
name={['form', 'showFooter']} name={['form', 'showFooter']}
valuePropName={'checked'} valuePropName={'checked'}
> >

View File

@ -1,27 +1,30 @@
import {Form, Input, Tabs} from 'antd' import {Form, Input, Tabs} from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import {TabPaneProps} from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next'
import {InputColor} from '../../input/color' import {InputColor} from '../../input/color'
export const DesignTab: React.FC<TabPaneProps> = props => { export const DesignTab: React.FC<TabPaneProps> = props => {
const { t } = useTranslation()
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item
label="Font" label={t('form:design.font')}
name={['form', 'design', 'font']} name={['form', 'design', 'font']}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
{[ {[
{name: 'backgroundColor', label: 'Background Color'}, 'backgroundColor',
{name: 'questionColor', label: 'Question Color'}, 'questionColor',
{name: 'answerColor', label: 'Answer Color'}, 'answerColor',
{name: 'buttonColor', label: 'Button Color'}, 'buttonColor',
{name: 'buttonActiveColor', label: 'Button Active Color'}, 'buttonActiveColor',
{name: 'buttonTextColor', label: 'Button Text Color'}, 'buttonTextColor',
].map(({label, name}) => ( ].map(name => (
<Form.Item key={name} label={label} name={['form', 'design', 'colors', name]}> <Form.Item key={name} label={t(`form:design.${name}`)} name={['form', 'design', 'colors', name]}>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
))} ))}

View File

@ -2,13 +2,16 @@ import {DeleteOutlined, PlusOutlined} from '@ant-design/icons/lib'
import {Button, Card, Form, Input, Switch, Tabs} from 'antd' import {Button, Card, Form, Input, Switch, Tabs} from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import {TabPaneProps} from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next'
import {InputColor} from '../../input/color' import {InputColor} from '../../input/color'
export const EndPageTab: React.FC<TabPaneProps> = props => { export const EndPageTab: React.FC<TabPaneProps> = props => {
const { t } = useTranslation()
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item
label={'Show'} label={t('form:endPage.show')}
name={['form', 'endPage', 'show']} name={['form', 'endPage', 'show']}
valuePropName={'checked'} valuePropName={'checked'}
> >
@ -16,21 +19,21 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Title'} label={t('form:endPage.title')}
name={['form', 'endPage', 'title']} name={['form', 'endPage', 'title']}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Paragraph'} label={t('form:endPage.paragraph')}
name={['form', 'endPage', 'paragraph']} name={['form', 'endPage', 'paragraph']}
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Continue Button Text'} label={t('form:endPage.continueButtonText')}
name={['form', 'endPage', 'buttonText']} name={['form', 'endPage', 'buttonText']}
> >
<Input /> <Input />
@ -47,7 +50,7 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
wrapperCol={{ wrapperCol={{
sm: { offset: index === 0 ? 0 : 6 }, sm: { offset: index === 0 ? 0 : 6 },
}} }}
label={index === 0 ? 'Buttons' : ''} label={index === 0 ? t('form:endPage.buttons') : ''}
key={field.key} key={field.key}
> >
<Card <Card
@ -56,28 +59,28 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
]} ]}
> >
<Form.Item <Form.Item
label={'Url'} label={t('form:endPage.url')}
name={[field.key, 'url']} name={[field.key, 'url']}
rules={[ rules={[
{type: 'url', message: 'Must be a valid url'} {type: 'url', message: t('validation:invalidUrl')}
]} ]}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}> <Form.Item label={t('form:endPage.action')} name={[field.key, 'action']} labelCol={{ span: 6 }}>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={'Text'} name={[field.key, 'text']} labelCol={{ span: 6 }}> <Form.Item label={t('form:endPage.text')} name={[field.key, 'text']} labelCol={{ span: 6 }}>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}> <Form.Item label={t('form:endPage.bgColor')} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
<Form.Item label={'Active Color'} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}> <Form.Item label={t('form:endPage.activeColor')} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}> <Form.Item label={t('form:endPage.color')} name={[field.key, 'color']} labelCol={{ span: 6 }}>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
</Card> </Card>
@ -96,7 +99,7 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
}} }}
style={{ width: '60%' }} style={{ width: '60%' }}
> >
<PlusOutlined /> Add Button <PlusOutlined /> {t('form:endPage.addButton')}
</Button> </Button>
</Form.Item> </Form.Item>
</div> </div>

View File

@ -3,6 +3,7 @@ import {Button, Card, Checkbox, Form, Input, Popconfirm, Tag} from 'antd'
import {FormInstance} from 'antd/lib/form' import {FormInstance} from 'antd/lib/form'
import {FieldData} from 'rc-field-form/lib/interface' import {FieldData} from 'rc-field-form/lib/interface'
import React, {useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment' import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
import {adminTypes} from './types' import {adminTypes} from './types'
import {TextType} from './types/text.type' import {TextType} from './types/text.type'
@ -17,6 +18,7 @@ interface Props {
} }
export const FieldCard: React.FC<Props> = props => { export const FieldCard: React.FC<Props> = props => {
const { t } = useTranslation()
const { const {
form, form,
field, field,
@ -54,11 +56,11 @@ export const FieldCard: React.FC<Props> = props => {
type={'inner'} type={'inner'}
extra={( extra={(
<div> <div>
<Tag color={'blue'}>{type}</Tag> <Tag color={'blue'}>{t(`type:${type}.name`)}</Tag>
<Popconfirm <Popconfirm
placement={'left'} placement={'left'}
title={'Really remove this field? Check that it is not referenced anywhere!'} title={t('type:confirmDelete')}
okText={'Delete Field'} okText={t('type:deleteNow')}
okButtonProps={{ danger: true }} okButtonProps={{ danger: true }}
onConfirm={() => { onConfirm={() => {
remove(index) remove(index)
@ -75,7 +77,7 @@ export const FieldCard: React.FC<Props> = props => {
> >
<Form.Item name={[field.name as string, 'type']} noStyle><Input type={'hidden'} /></Form.Item> <Form.Item name={[field.name as string, 'type']} noStyle><Input type={'hidden'} /></Form.Item>
<Form.Item <Form.Item
label={'Title'} label={t('type:title')}
name={[field.name as string, 'title']} name={[field.name as string, 'title']}
rules={[ rules={[
{ required: true, message: 'Title is required' } { required: true, message: 'Title is required' }
@ -85,18 +87,18 @@ export const FieldCard: React.FC<Props> = props => {
<Input onChange={e => setNextTitle(e.target.value)}/> <Input onChange={e => setNextTitle(e.target.value)}/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Description'} label={t('type:description')}
name={[field.name as string, 'description']} name={[field.name as string, 'description']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Required'} label={t('type:required')}
name={[field.name as string, 'required']} name={[field.name as string, 'required']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
valuePropName={'checked'} valuePropName={'checked'}
extra={type === 'hidden' && 'If required, default value must be set to enable users to submit form!'} extra={type === 'hidden' && t('type:requiredInfo')}
> >
<Checkbox /> <Checkbox />
</Form.Item> </Form.Item>

View File

@ -3,6 +3,7 @@ import {Button, Form, Select, Space, Tabs} from 'antd'
import {FormInstance} from 'antd/lib/form' import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs' import {TabPaneProps} from 'antd/lib/tabs'
import React, {useCallback, useState} from 'react' import React, {useCallback, useState} from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment' import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
import {FieldCard} from './field.card' import {FieldCard} from './field.card'
import {adminTypes} from './types' import {adminTypes} from './types'
@ -14,6 +15,7 @@ interface Props extends TabPaneProps {
} }
export const FieldsTab: React.FC<Props> = props => { export const FieldsTab: React.FC<Props> = props => {
const { t } = useTranslation()
const [nextType, setNextType] = useState('textfield') const [nextType, setNextType] = useState('textfield')
const renderType = useCallback((field, index, remove) => { const renderType = useCallback((field, index, remove) => {
@ -41,7 +43,7 @@ export const FieldsTab: React.FC<Props> = props => {
}} }}
> >
<Select value={nextType} onChange={e => setNextType(e)} style={{ minWidth: 200 }}> <Select value={nextType} onChange={e => setNextType(e)} style={{ minWidth: 200 }}>
{Object.keys(adminTypes).map(type => <Select.Option value={type} key={type}>{type}</Select.Option> )} {Object.keys(adminTypes).map(type => <Select.Option value={type} key={type}>{t(`type:${type}.name`)}</Select.Option> )}
</Select> </Select>
<Button <Button
type="dashed" type="dashed"

View File

@ -1,7 +1,9 @@
import {InfoCircleOutlined} from '@ant-design/icons/lib'
import {Form, Input, Select, Switch, Tabs} from 'antd' import {Form, Input, Select, Switch, Tabs} from 'antd'
import {FormInstance} from 'antd/lib/form' import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs' import {TabPaneProps} from 'antd/lib/tabs'
import React, {useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import {Trans, useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment' import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps { interface Props extends TabPaneProps {
@ -10,6 +12,7 @@ interface Props extends TabPaneProps {
} }
export const RespondentNotificationsTab: React.FC<Props> = props => { export const RespondentNotificationsTab: React.FC<Props> = props => {
const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>() const [enabled, setEnabled] = useState<boolean>()
useEffect(() => { useEffect(() => {
@ -40,7 +43,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item
label={'Enabled'} label={t('form:respondentNotifications.enabled')}
name={['form', 'respondentNotifications', 'enabled']} name={['form', 'respondentNotifications', 'enabled']}
valuePropName={'checked'} valuePropName={'checked'}
> >
@ -48,12 +51,12 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Subject'} label={t('form:respondentNotifications.subject')}
name={['form', 'respondentNotifications', 'subject']} name={['form', 'respondentNotifications', 'subject']}
rules={[ rules={[
{ {
required: enabled, required: enabled,
message: 'Please provide a Subject', message: t('validation:subjectRequired'),
}, },
]} ]}
> >
@ -61,26 +64,40 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'HTML Template'} label={t('form:respondentNotifications.htmlTemplate')}
name={['form', 'respondentNotifications', 'htmlTemplate']} name={['form', 'respondentNotifications', 'htmlTemplate']}
rules={[ rules={[
{ {
required: enabled, required: enabled,
message: 'Please provide a Template', message: t('validation:templateRequired'),
}, },
]} ]}
extra={(
<div>
<Trans>form:respondentNotifications.htmlTemplateInfo</Trans>
<a
href={'https://mjml.io/try-it-live'}
target={'_blank'}
style={{
marginLeft: 16,
}}
>
<InfoCircleOutlined />
</a>
</div>
)}
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Email Field'} label={t('form:respondentNotifications.toField')}
name={['form', 'respondentNotifications', 'toField']} name={['form', 'respondentNotifications', 'toField']}
extra={'Field with Email for receipt'} extra={t('form:respondentNotifications.toFieldInfo')}
rules={[ rules={[
{ {
required: enabled, required: enabled,
message: 'Please provide a Email Field', message: t('validation:emailFieldRequired'),
}, },
]} ]}
> >
@ -96,9 +113,9 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Sender Email'} label={t('form:respondentNotifications.fromEmail')}
name={['form', 'respondentNotifications', 'fromEmail']} name={['form', 'respondentNotifications', 'fromEmail']}
extra={'Make sure your mailserver can send from this email'} extra={t('form:respondentNotifications.fromEmailInfo')}
> >
<Input /> <Input />
</Form.Item> </Form.Item>

View File

@ -1,7 +1,9 @@
import {InfoCircleOutlined} from '@ant-design/icons/lib'
import {Form, Input, Select, Switch, Tabs} from 'antd' import {Form, Input, Select, Switch, Tabs} from 'antd'
import {FormInstance} from 'antd/lib/form' import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs' import {TabPaneProps} from 'antd/lib/tabs'
import React, {useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import {Trans, useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment' import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps { interface Props extends TabPaneProps {
@ -10,6 +12,7 @@ interface Props extends TabPaneProps {
} }
export const SelfNotificationsTab: React.FC<Props> = props => { export const SelfNotificationsTab: React.FC<Props> = props => {
const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>() const [enabled, setEnabled] = useState<boolean>()
useEffect(() => { useEffect(() => {
@ -38,7 +41,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item
label={'Enabled'} label={t('form:selfNotifications.enabled')}
name={['form', 'selfNotifications', 'enabled']} name={['form', 'selfNotifications', 'enabled']}
valuePropName={'checked'} valuePropName={'checked'}
> >
@ -46,12 +49,12 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Subject'} label={t('form:selfNotifications.subject')}
name={['form', 'selfNotifications', 'subject']} name={['form', 'selfNotifications', 'subject']}
rules={[ rules={[
{ {
required: enabled, required: enabled,
message: 'Please provide a Subject', message: t('validation:subjectRequired'),
}, },
]} ]}
> >
@ -59,23 +62,36 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'HTML Template'} label={t('form:selfNotifications.htmlTemplate')}
name={['form', 'selfNotifications', 'htmlTemplate']} name={['form', 'selfNotifications', 'htmlTemplate']}
rules={[ rules={[
{ {
required: enabled, required: enabled,
message: 'Please provide a Template', message: t('validation:templateRequired'),
}, },
]} ]}
extra={'You can also use <a href="https://mjml.io/try-it-live">MJML</a> to create your email templates'} extra={(
<div>
<Trans>form:selfNotifications.htmlTemplateInfo</Trans>
<a
href={'https://mjml.io/try-it-live'}
target={'_blank'}
style={{
marginLeft: 16,
}}
>
<InfoCircleOutlined />
</a>
</div>
)}
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Email Field'} label={t('form:selfNotifications.fromField')}
name={['form', 'selfNotifications', 'fromField']} name={['form', 'selfNotifications', 'fromField']}
extra={'Field with Email, will set the Reply-To header'} extra={t('form:selfNotifications.fromFieldInfo')}
> >
<Select> <Select>
{Object.keys(groups).map(key => ( {Object.keys(groups).map(key => (
@ -89,9 +105,9 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Your Email'} label={t('form:selfNotifications.toEmail')}
name={['form', 'selfNotifications', 'toEmail']} name={['form', 'selfNotifications', 'toEmail']}
extra={'If not set will send to the admin of the form'} extra={t('form:selfNotifications.toEmailInfo')}
> >
<Input /> <Input />
</Form.Item> </Form.Item>

View File

@ -2,13 +2,16 @@ import {DeleteOutlined, PlusOutlined} from '@ant-design/icons/lib'
import {Button, Card, Form, Input, Switch, Tabs} from 'antd' import {Button, Card, Form, Input, Switch, Tabs} from 'antd'
import {TabPaneProps} from 'antd/lib/tabs' import {TabPaneProps} from 'antd/lib/tabs'
import React from 'react' import React from 'react'
import {useTranslation} from 'react-i18next'
import {InputColor} from '../../input/color' import {InputColor} from '../../input/color'
export const StartPageTab: React.FC<TabPaneProps> = props => { export const StartPageTab: React.FC<TabPaneProps> = props => {
const { t } = useTranslation()
return ( return (
<Tabs.TabPane {...props}> <Tabs.TabPane {...props}>
<Form.Item <Form.Item
label={'Show'} label={t('form:startPage.show')}
name={['form', 'startPage', 'show']} name={['form', 'startPage', 'show']}
valuePropName={'checked'} valuePropName={'checked'}
> >
@ -16,21 +19,21 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Title'} label={t('form:startPage.title')}
name={['form', 'startPage', 'title']} name={['form', 'startPage', 'title']}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Paragraph'} label={t('form:startPage.paragraph')}
name={['form', 'startPage', 'paragraph']} name={['form', 'startPage', 'paragraph']}
> >
<Input.TextArea autoSize /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={'Continue Button Text'} label={t('form:startPage.continueButtonText')}
name={['form', 'startPage', 'buttonText']} name={['form', 'startPage', 'buttonText']}
> >
<Input /> <Input />
@ -47,7 +50,7 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
wrapperCol={{ wrapperCol={{
sm: { offset: index === 0 ? 0 : 6 }, sm: { offset: index === 0 ? 0 : 6 },
}} }}
label={index === 0 ? 'Buttons' : ''} label={index === 0 ? t('form:startPage.buttons') : ''}
key={field.key} key={field.key}
> >
<Card <Card
@ -56,28 +59,28 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
]} ]}
> >
<Form.Item <Form.Item
label={'Url'} label={t('form:startPage.url')}
name={[field.key, 'url']} name={[field.key, 'url']}
rules={[ rules={[
{type: 'url', message: 'Must be a valid url'} {type: 'url', message: t('validation:invalidUrl')}
]} ]}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={'Action'} name={[field.key, 'action']} labelCol={{ span: 6 }}> <Form.Item label={t('form:startPage.action')} name={[field.key, 'action']} labelCol={{ span: 6 }}>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={'Text'} name={[field.key, 'text']} labelCol={{ span: 6 }}> <Form.Item label={t('form:startPage.text')} name={[field.key, 'text']} labelCol={{ span: 6 }}>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={'Background Color'} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}> <Form.Item label={t('form:startPage.bgColor')} name={[field.key, 'bgColor']} labelCol={{ span: 6 }}>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
<Form.Item label={'Active Color'} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}> <Form.Item label={t('form:startPage.activeColor')} name={[field.key, 'activeColor']} labelCol={{ span: 6 }}>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
<Form.Item label={'Color'} name={[field.key, 'color']} labelCol={{ span: 6 }}> <Form.Item label={t('form:startPage.color')} name={[field.key, 'color']} labelCol={{ span: 6 }}>
<InputColor /> <InputColor />
</Form.Item> </Form.Item>
</Card> </Card>
@ -96,7 +99,7 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
}} }}
style={{ width: '60%' }} style={{ width: '60%' }}
> >
<PlusOutlined /> Add Button <PlusOutlined /> {t('form:startPage.addButton')}
</Button> </Button>
</Form.Item> </Form.Item>
</div> </div>

View File

@ -29,7 +29,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
sm: { offset: index === 0 ? 0 : 6 }, sm: { offset: index === 0 ? 0 : 6 },
}} }}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
label={index === 0 ? 'Options' : ''} label={index === 0 ? t('type:radio:options') : ''}
key={field.key} key={field.key}
> >
<Row gutter={16}> <Row gutter={16}>
@ -39,7 +39,7 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
name={[field.name, 'title']} name={[field.name, 'title']}
style={{marginBottom: 0}} style={{marginBottom: 0}}
> >
<Input placeholder={'Title'} /> <Input placeholder={t('type:radio:titlePlaceholder')} />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={8}> <Col span={8}>
@ -48,15 +48,15 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
name={[field.name, 'value']} name={[field.name, 'value']}
style={{marginBottom: 0}} style={{marginBottom: 0}}
rules={[ rules={[
{ required: true, message: 'Please provide a value' } { required: true, message: t('validation:valueRequired') }
]} ]}
> >
<Input placeholder={'Value'} /> <Input placeholder={t('type:radio:valuePlaceholder')} />
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={4}> <Col span={4}>
<Button danger onClick={() => remove(index)}> <Button danger onClick={() => remove(index)}>
Remove {t('type:radio:removeOption')}
</Button> </Button>
</Col> </Col>
</Row> </Row>
@ -72,7 +72,9 @@ export const RadioType: React.FC<AdminFieldTypeProps> = props => {
<Button <Button
type={'dashed'} type={'dashed'}
onClick={() => add()} onClick={() => add()}
>Add Option</Button> >
{t('type:radio:addOption')}
</Button>
</Form.Item> </Form.Item>
</div> </div>
) )

View File

@ -13,7 +13,7 @@ export const RatingType: React.FC<AdminFieldTypeProps> = props => {
label={t('type:rating:default')} label={t('type:rating:default')}
name={[props.field.name, 'value']} name={[props.field.name, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
extra={'Click again to remove default value'} extra={t('type:rating.clearNote')}
> >
<Rate <Rate
allowHalf allowHalf

View File

@ -8,7 +8,7 @@ export const TextType: React.FC<AdminFieldTypeProps> = props => {
return ( return (
<Form.Item <Form.Item
label={t('type:text:default')} label={t('type:textfield:default')}
name={[props.field.name, 'value']} name={[props.field.name, 'value']}
labelCol={{ span: 6 }} labelCol={{ span: 6 }}
> >

View File

@ -1,9 +1,11 @@
import {CaretDownOutlined, UserOutlined} from '@ant-design/icons' import {CaretDownOutlined, UserOutlined} from '@ant-design/icons'
import {MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons/lib' import {MenuFoldOutlined, MenuUnfoldOutlined} from '@ant-design/icons/lib'
import {Dropdown, Layout, Menu, PageHeader, Spin, Tag} from 'antd' import {Dropdown, Layout, Menu, PageHeader, Select, Spin, Tag} from 'antd'
import Link from 'next/link' import Link from 'next/link'
import {useRouter} from 'next/router' import {useRouter} from 'next/router'
import React, {FunctionComponent} from 'react' import React, {FunctionComponent} from 'react'
import {useTranslation} from 'react-i18next'
import {languages} from '../i18n'
import {sideMenu, SideMenuElement} from './sidemenu' import {sideMenu, SideMenuElement} from './sidemenu'
import {useWindowSize} from './use.window.size' import {useWindowSize} from './use.window.size'
import {clearAuth} from './with.auth' import {clearAuth} from './with.auth'
@ -32,6 +34,7 @@ interface Props {
} }
const Structure: FunctionComponent<Props> = (props) => { const Structure: FunctionComponent<Props> = (props) => {
const { t, i18n } = useTranslation()
const size = useWindowSize() const size = useWindowSize()
const [userMenu, setUserMenu] = React.useState(false) const [userMenu, setUserMenu] = React.useState(false)
const [open, setOpen] = React.useState<string[]>() const [open, setOpen] = React.useState<string[]>()
@ -120,7 +123,7 @@ const Structure: FunctionComponent<Props> = (props) => {
} }
return ( return (
<Layout style={{ height: '100vh' }}> <Layout style={{ height: '100vh' }} className={'admin'}>
<Header <Header
style={{ style={{
paddingLeft: 0, paddingLeft: 0,
@ -177,9 +180,11 @@ const Structure: FunctionComponent<Props> = (props) => {
maxHeight: '100%', maxHeight: '100%',
overflow: 'auto', overflow: 'auto',
}} }}
className={'sidemenu'}
> >
<Menu <Menu
mode="inline" mode="inline"
style={{ flex: 1 }}
defaultSelectedKeys={['1']} defaultSelectedKeys={['1']}
selectedKeys={selected} selectedKeys={selected}
onSelect={(s): void => setSelected(s.keyPath)} onSelect={(s): void => setSelected(s.keyPath)}
@ -192,11 +197,19 @@ const Structure: FunctionComponent<Props> = (props) => {
mode="inline" mode="inline"
selectable={false} selectable={false}
> >
<Menu.Item <Menu.Item className={'language-selector'}>
style={{ <Select
marginTop: 40, bordered={false}
}} value={i18n.language.replace(/-.*/, '')}
> onChange={next => i18n.changeLanguage(next)}
style={{
width: '100%',
}}
>
{languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
</Select>
</Menu.Item>
<Menu.Item>
Version: <Tag color="gold">{process.env.version}</Tag> Version: <Tag color="gold">{process.env.version}</Tag>
</Menu.Item> </Menu.Item>
</Menu> </Menu>

View File

@ -14,5 +14,7 @@ i18n
defaultNS: 'common', defaultNS: 'common',
react: { react: {
useSuspense: process.browser, useSuspense: process.browser,
transSupportBasicHtmlNodes: true,
transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'em', 'u'],
} }
}) })

8
locales/de/admin.ts Normal file
View File

@ -0,0 +1,8 @@
export const admin = {
home: 'Home',
users: 'Benutzer',
forms: 'Formulare',
submissions: 'Eingaben',
profile: 'Profil',
username: 'Benutzername',
}

7
locales/de/common.ts Normal file
View File

@ -0,0 +1,7 @@
export const common = {
logout: 'Abmelden',
login: "Anmelden",
register: "Benutzer erstellen",
recover: 'Passwort vergessen',
admin: "Administration",
}

97
locales/de/form.ts Normal file
View File

@ -0,0 +1,97 @@
export const form = {
building: 'Formular wird aufgebaut',
submitted: 'Vielen dank für ihre Eingaben!',
restart: 'Formular neu starten',
loading: 'Formular wird geladen',
mange: 'Formular "{{title}}" bearbeiten',
new: 'Neues Formular',
created: 'Formular Erstellt',
creationError: 'Formular konnte nicht erstellt werden',
create: 'Neues Formular erstellen',
createNow: 'Jetzt Anlegen',
baseDataTab: 'Basis Daten',
selfNotificationsTab: 'Benachrichtigungen für Verwalter',
respondentNotificationsTab: 'Benarichtigungen für Nutzer',
updated: 'Formular aktualisiert',
updateError: 'Formular konnte nicht aktualisiert werden',
updateNow: 'Speichern',
designTab: 'Design',
startPageTab: 'Eröffnungs Seite',
endPageTab: 'Abschluss Seite',
confirmDelete: 'Wollen sie dieses Formular mit allen Eintragungen löschen?',
deleted: 'Formular gelöscht',
deleteError: 'Formular konnte nicht gelöscht werden',
deleteNow: 'Jetzt löschen!',
row: {
isLive: 'Öffentlich',
title: 'Titel',
admin: 'Verwalter',
language: 'Sprache',
created: 'Erstellt',
lastModified: 'Letzte Änderung',
menu: ''
},
baseData: {
isLive: 'Öffentlich',
title: 'Titel',
language: 'Sprache',
showFooter: 'Fußzeile anzeigen',
},
design: {
font: 'Schriftart',
backgroundColor: 'Hintergrund Farbe',
questionColor: 'Fragen Farbe',
answerColor: 'Antworten Farbe',
buttonColor: 'Aktions Farbe',
buttonActiveColor: 'Aktive Aktion Farbe',
buttonTextColor: 'Aktion Schrift Farbe',
},
endPage: {
show: 'Anzeigen',
continueButtonText: 'Weiter Text',
paragraph: 'Paragraf',
title: 'Titel',
buttons: 'Aktionen',
url: 'Url',
text: 'Text',
action: 'Aktion',
bgColor: 'Hintergrund Farbe',
activeColor: 'Aktive Farbe',
color: 'Text Farbe',
addButton: 'Aktion hinzufügen',
},
startPage: {
show: 'Anzeigen',
continueButtonText: 'Weiter Text',
paragraph: 'Paragraf',
title: 'Titel',
buttons: 'Aktionen',
url: 'Url',
text: 'Text',
action: 'Aktion',
bgColor: 'Hintergrund Farbe',
activeColor: 'Aktive Farbe',
color: 'Text Farbe',
addButton: 'Aktion hinzufügen',
},
respondentNotifications: {
enabled: 'Aktiv',
subject: 'Betreff',
htmlTemplate: 'HTML Template',
htmlTemplateInfo: 'Sie können auch <u>MJML</u> verwenden um das Template zu gestalten',
toField: 'Empfänger E-Mail',
toFieldInfo: 'Formular E-Mail Feld für Benachrichtigung',
fromEmail: 'Absender E-Mail',
fromEmailInfo: 'Der E-Mail Server muss den versand von dieser E-Mail zulassen',
},
selfNotifications: {
enabled: 'Aktiv',
subject: 'Betreff',
htmlTemplate: 'HTML Template',
htmlTemplateInfo: 'Sie können auch <u>MJML</u> verwenden um das Template zu gestalten',
toEmail: 'Empfänger E-Mail',
toEmailInfo: 'Als Standard wird die E-Mail des Verwalters verwendet',
fromField: 'Absender E-Mail',
fromFieldInfo: 'Formular E-Mail Feld für Benachrichtigung, wird as Reply-To gesetzt',
},
}

View File

@ -1 +1,27 @@
export const de = {} import {admin} from './admin'
import {common} from './common'
import {form} from './form'
import language from './language'
import {login} from './login'
import {profile} from './profile'
import {register} from './register'
import {statistic} from './statistic'
import {submission} from './submission'
import {type} from './type'
import {user} from './user'
import {validation} from './validation'
export const de = {
admin,
common,
form,
language,
login,
profile,
register,
statistic,
submission,
type,
user,
validation,
}

8
locales/de/language.ts Normal file
View File

@ -0,0 +1,8 @@
export default {
en: 'English',
de: 'Deutsch',
cn: '繁體中文',
es: 'Español',
fr: 'Français',
it: 'Italiano',
}

7
locales/de/login.ts Normal file
View File

@ -0,0 +1,7 @@
export const login = {
welcomeBack: 'Willkomen zurück!',
invalidLoginCredentials: 'Benutzername oder Passwort ist falsch',
usernamePlaceholder: 'Benutzername',
passwordPlaceholder: 'Passwort',
loginNow: 'Jetzt Anmelden',
}

10
locales/de/profile.ts Normal file
View File

@ -0,0 +1,10 @@
export const profile = {
updated: 'Profil Aktualisieren',
updateError: 'Profil konnte nicht aktualisiert werden',
updateNow: 'Speichern',
language: 'Sprache',
email: 'E-Mail',
username: 'Benutzername',
firstName: 'Vorname',
lastName: 'Nachname',
}

6
locales/de/register.ts Normal file
View File

@ -0,0 +1,6 @@
export const register = {
welcome: 'Willkommen, bitte bestätige noch deine E-Mail',
credentialsAlreadyInUse: 'Deine Daten werden bereits verwendet!',
registerNow: 'Jetzt Registrieren',
gotoLogin: 'Sie haben schon einen Account? Weiter zur Anmeldung',
}

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

@ -0,0 +1,5 @@
export const statistic = {
'total-forms': 'Anzahl der Formulare',
'total-users': 'Anzahl der Benutzer',
'total-submissions': 'Anzahl der Eingaben',
}

16
locales/de/submission.ts Normal file
View File

@ -0,0 +1,16 @@
export const submission = {
progress: 'Fortschritt',
created: 'Erstellt',
lastModified: 'Letzte Änderung',
submission: 'Eingabe',
add: 'Neue Eingabe starten',
edit: 'Formular bearbeiten',
field: 'Feld',
value: 'Wert',
country: 'Land',
city: 'Stadt',
device: {
type: 'Geräte Typ',
name: 'Geräte Name',
}
}

65
locales/de/type.ts Normal file
View File

@ -0,0 +1,65 @@
export const type = {
deleteNow: 'Feld löschen',
confirmDelete: 'Wollen sie das Feld wirklich löschen? Bitte beachten sie das es keine Referenzen auf das Feld gibt!',
title: 'Titel',
description: 'Beschreibung',
required: 'Pflichtfeld',
requiredInfo: 'Falls verpflichtend sollte ein Standard Wert gesetzt sein damit Nutzer fortfahren können!',
date: {
name: 'Datum',
default: 'Standard Dateum',
min: 'Ältest mögliche Datum',
max: 'Neuest mögliche Datum',
},
dropdown: {
name: 'Auswahl Liste',
default: 'Standard Wert',
options: 'Auswahl',
titlePlaceholder: 'Titel',
valuePlaceholder: 'Wert',
removeOption: 'Entfernen',
addOption: 'Auswahl hinzufügen',
},
email: {
name: 'E-Mail',
default: 'Standard E-Mail',
},
hidden: {
name: 'Versteckt',
default: 'Standard Wert',
},
link: {
name: 'URL',
default: 'Standard Url',
},
number: {
name: 'Zahl',
default: 'Standard Zahl',
},
radio: {
name: 'Knopf Auswahl Liste',
default: 'Standard Wert',
options: 'Auswahl',
titlePlaceholder: 'Titel',
valuePlaceholder: 'Wert',
removeOption: 'Entfernen',
addOption: 'Auswahl hinzufügen',
},
rating: {
name: 'Bewertung',
default: 'Standard Bewertung',
clearNote: 'Erneut klicken um Auswahl aufzuheben'
},
textfield: {
name: 'Einzeiliger Text',
default: 'Standard Wert',
},
textarea: {
name: 'Mehrzeiliger Text',
default: 'Standard Wert',
},
yes_no: {
name: 'Ja / Nein',
default: 'Standard Wert',
},
}

18
locales/de/user.ts Normal file
View File

@ -0,0 +1,18 @@
export const user = {
confirmDelete: 'Wollen sie diesen Benutzer wirklich löschen?',
deleted: 'Benutzer gelöscht',
deleteError: 'Benutzer konnte nicht gelöscht werden',
deleteNow: 'Jetzt löschen!',
loading: 'Benutzer wird geladen',
mange: 'Benutzer "{{email}}" bearbeiten',
row: {
roles: 'Rolle',
email: 'E-Mail',
created: 'Erstellt',
menu: ''
},
updated: 'Benutzer aktualisiert updated',
updateError: 'Konnte Benutzer nicht aktualisieren',
updateNow: 'Speichern',
baseData: 'Basis Daten',
}

15
locales/de/validation.ts Normal file
View File

@ -0,0 +1,15 @@
export const validation = {
invalidEmail: 'ungültige E-Mail!',
mandatoryFieldsMissing: 'Nicht alle Pflichfelder befüllt',
usernameRequired: 'Bitte einen Benutzernamen angeben',
emailRequired: 'Bitte eine E-Mail angeben',
emailFieldRequired: 'Bitte ein E-Mail Feld auswählen',
languageRequired: 'Bitte eine Sprache angeben',
valueRequired: 'Bitte einen Wert angeben',
invalidUrl: 'ungültige Url',
titleRequired: 'Bitte einen Titel angeben',
templateRequired: 'Bitte ein Template angeben',
subjectRequired: 'Bitte einen Betreff angeben',
passwordRequired: 'Bitte ein Passwort angeben',
passwordMinLength: 'Passwort muss mindestens 5 Zeichen haben!',
}

View File

@ -9,13 +9,15 @@ export const form = {
creationError: 'Could not create form', creationError: 'Could not create form',
create: 'Create new form', create: 'Create new form',
createNow: 'Save', createNow: 'Save',
baseData: 'Base Data', baseDataTab: 'Base Data',
selfNotificationsTab: 'Self Notifications',
respondentNotificationsTab: 'Respondent Notifications',
updated: 'Form updated', updated: 'Form updated',
updateError: 'Could not update form', updateError: 'Could not update form',
updateNow: 'Save', updateNow: 'Save',
design: 'Design', designTab: 'Design',
startPage: 'Start Page', startPageTab: 'Start Page',
endPage: 'End Page', endPageTab: 'End Page',
confirmDelete: 'Are you sure delete this form with all submissions?', confirmDelete: 'Are you sure delete this form with all submissions?',
deleted: 'Form deleted', deleted: 'Form deleted',
deleteError: 'could not delete form', deleteError: 'could not delete form',
@ -28,5 +30,68 @@ export const form = {
created: 'Created', created: 'Created',
lastModified: 'Last Modified', lastModified: 'Last Modified',
menu: '' menu: ''
} },
baseData: {
isLive: 'Is Live',
title: 'Title',
language: 'Language',
showFooter: 'Show Footer',
},
design: {
font: 'Font',
backgroundColor: 'Background Color',
questionColor: 'Question Color',
answerColor: 'Answer Color',
buttonColor: 'Button Color',
buttonActiveColor: 'Button Active Color',
buttonTextColor: 'Button Text Color',
},
endPage: {
show: 'Show',
continueButtonText: 'Continue Button Text',
paragraph: 'Paragraph',
title: 'Title',
buttons: 'Buttons',
url: 'Url',
text: 'Text',
action: 'Action',
bgColor: 'Background Color',
activeColor: 'Active Color',
color: 'Color',
removeButton: 'Add Button',
},
startPage: {
show: 'Show',
continueButtonText: 'Continue Button Text',
paragraph: 'Paragraph',
title: 'Title',
buttons: 'Buttons',
url: 'Url',
text: 'Text',
action: 'Action',
bgColor: 'Background Color',
activeColor: 'Active Color',
color: 'Color',
addButton: 'Add Button',
},
respondentNotifications: {
enabled: 'Enabled',
subject: 'Subject',
htmlTemplate: 'HTML Template',
htmlTemplateInfo: 'You can also use <u>MJML</u> to create your email templates',
toField: 'Email Field',
toFieldInfo: 'Field with Email for receipt',
fromEmail: 'Sender Email',
fromEmailInfo: 'Make sure your mailserver can send from this email',
},
selfNotifications: {
enabled: 'Enabled',
subject: 'Subject',
htmlTemplate: 'HTML Template',
htmlTemplateInfo: 'You can also use <strong>MJML</strong> to create your email templates',
toEmail: 'Your Email',
toEmailInfo: 'If not set will send to the admin of the form',
fromField: 'Sender Email',
fromFieldInfo: 'Field with Email, will set the Reply-To header',
},
} }

View File

@ -1,11 +1,13 @@
import {admin} from './admin' import {admin} from './admin'
import {common} from './common' import {common} from './common'
import {form} from './form' import {form} from './form'
import language from './language'
import {login} from './login' import {login} from './login'
import {profile} from './profile' import {profile} from './profile'
import {register} from './register' import {register} from './register'
import {statistic} from './statistic' import {statistic} from './statistic'
import {submission} from './submission' import {submission} from './submission'
import {type} from './type'
import {user} from './user' import {user} from './user'
import {validation} from './validation' import {validation} from './validation'
@ -13,11 +15,13 @@ export const en = {
admin, admin,
common, common,
form, form,
language,
login, login,
profile, profile,
register, register,
statistic, statistic,
submission, submission,
type,
user, user,
validation, validation,
} }

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

@ -0,0 +1,8 @@
export default {
en: 'English',
de: 'Deutsch',
cn: '繁體中文',
es: 'Español',
fr: 'Français',
it: 'Italiano',
}

View File

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

View File

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

View File

@ -1,10 +1,18 @@
export const type = { export const type = {
deleteNow: 'Delete Field',
confirmDelete: 'Really remove this field? Check that it is not referenced anywhere!',
title: 'Title',
description: 'Description',
required: 'Required',
requiredInfo: 'If required, default value must be set to enable users to submit form!',
date: { date: {
name: 'Date',
default: 'Default Date', default: 'Default Date',
min: 'Min Date', min: 'Min Date',
max: 'Max Date', max: 'Max Date',
}, },
dropdown: { dropdown: {
name: 'Dropdown',
default: 'Default Value', default: 'Default Value',
options: 'Options', options: 'Options',
titlePlaceholder: 'Title', titlePlaceholder: 'Title',
@ -13,30 +21,45 @@ export const type = {
addOption: 'Add Option', addOption: 'Add Option',
}, },
email: { email: {
name: 'Email',
default: 'Default Email', default: 'Default Email',
}, },
hidden: { hidden: {
name: 'Hidden',
default: 'Default Value', default: 'Default Value',
}, },
link: { link: {
name: 'URL',
default: 'Default Link', default: 'Default Link',
}, },
number: { number: {
name: 'Number',
default: 'Default Number', default: 'Default Number',
}, },
radio: { radio: {
name: 'Radio Switch',
default: 'Default Value', default: 'Default Value',
options: 'Options',
titlePlaceholder: 'Title',
valuePlaceholder: 'Value',
removeOption: 'Remove',
addOption: 'Add Option',
}, },
rating: { rating: {
name: 'Rating',
default: 'Default Value', default: 'Default Value',
clearNote: 'Click again to remove the default value'
}, },
text: { textfield: {
name: 'Text Line',
default: 'Default Value', default: 'Default Value',
}, },
textarea: { textarea: {
name: 'Text Area',
default: 'Default Value', default: 'Default Value',
}, },
yes_no: { yes_no: {
name: 'Yes / No',
default: 'Default Value', default: 'Default Value',
}, },
} }

View File

@ -11,8 +11,8 @@ export const user = {
created: 'Created', created: 'Created',
menu: '' menu: ''
}, },
updated: 'Form updated', updated: 'User updated',
updateError: 'Could not update form', updateError: 'Could not update user',
updateNow: 'Save', updateNow: 'Save',
baseData: 'Base Data', baseData: 'Base Data',
} }

View File

@ -3,7 +3,13 @@ export const validation = {
mandatoryFieldsMissing: 'Mandatory fields missing', mandatoryFieldsMissing: 'Mandatory fields missing',
usernameRequired: 'Please provide a Username', usernameRequired: 'Please provide a Username',
emailRequired: 'Please provide an Email', emailRequired: 'Please provide an Email',
emailFieldRequired: 'Please select an Email Field',
languageRequired: 'Please select a Language', languageRequired: 'Please select a Language',
valueRequired: 'Please provide a Value', valueRequired: 'Please provide a Value',
invalidUrl: 'Must be a valid URL', invalidUrl: 'Must be a valid URL',
titleRequired: 'Please provide a title',
templateRequired: 'Please provide a template',
subjectRequired: 'Please provide a subject',
passwordRequired: 'Please input your password!',
passwordMinLength: 'Must be longer than or equal to 5 characters!',
} }

View File

@ -153,27 +153,27 @@ const Index: NextPage = () => {
<Tabs> <Tabs>
<FieldsTab <FieldsTab
key={'fields'} key={'fields'}
tab={'Fields'} tab={t('form:fieldsTab')}
fields={fields} fields={fields}
onChangeFields={setFields} onChangeFields={setFields}
form={form} form={form}
/> />
<BaseDataTab key={'base_data'} tab={t('form:baseData')} /> <BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} />
<DesignTab key={'design'} tab={'form:design'} /> <DesignTab key={'design'} tab={t('form:designTab')} />
<SelfNotificationsTab <SelfNotificationsTab
key={'self_notifications'} key={'self_notifications'}
tab={'Self Notifications'} tab={t('form:selfNotificationsTab')}
fields={fields} fields={fields}
form={form} form={form}
/> />
<RespondentNotificationsTab <RespondentNotificationsTab
key={'respondent_notifications'} key={'respondent_notifications'}
tab={'Respondent Notifications'} tab={t('form:respondentNotificationsTab')}
fields={fields} fields={fields}
form={form} form={form}
/> />
<StartPageTab key={'start_page'} tab={t('form:startPage')} /> <StartPageTab key={'start_page'} tab={t('form:startPageTab')} />
<EndPageTab key={'end_page'} tab={t('form:endPage')} /> <EndPageTab key={'end_page'} tab={t('form:endPageTab')} />
</Tabs> </Tabs>
</Form> </Form>
</Structure> </Structure>

View File

@ -79,7 +79,7 @@ const Create: NextPage = () => {
<Form.Item noStyle name={['form', 'id']}><Input type={'hidden'} /></Form.Item> <Form.Item noStyle name={['form', 'id']}><Input type={'hidden'} /></Form.Item>
<Tabs> <Tabs>
<BaseDataTab key={'base_data'} tab={t('form:baseData')} /> <BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} />
</Tabs> </Tabs>
</Form> </Form>
</Structure> </Structure>

View File

@ -89,6 +89,7 @@ const Index: NextPage = () => {
{ {
title: t('form:row.language'), title: t('form:row.language'),
dataIndex: 'language', dataIndex: 'language',
render: lang => t(`language:${lang}`)
}, },
{ {
title: t('form:row.created'), title: t('form:row.created'),

View File

@ -129,7 +129,7 @@ const Profile: NextPage = () => {
]} ]}
> >
<Select> <Select>
{languages.map(language => <Select.Option value={language} key={language}>{language.toUpperCase()}</Select.Option> )} {languages.map(language => <Select.Option value={language} key={language}>{t(`language:${language}`)}</Select.Option> )}
</Select> </Select>
</Form.Item> </Form.Item>

View File

@ -7,7 +7,7 @@ import {LoadingPage} from 'components/loading.page'
import {FORM_QUERY, FormQueryData, FormQueryVariables} from 'graphql/query/form.query' import {FORM_QUERY, FormQueryData, FormQueryVariables} from 'graphql/query/form.query'
import {NextPage} from 'next' import {NextPage} from 'next'
import {useRouter} from 'next/router' import {useRouter} from 'next/router'
import React, {useState} from 'react' import React, {useEffect, useState} from 'react'
import {useTranslation} from 'react-i18next' import {useTranslation} from 'react-i18next'
import Swiper from 'react-id-swiper' import Swiper from 'react-id-swiper'
import {ReactIdSwiperProps} from 'react-id-swiper/lib/types' import {ReactIdSwiperProps} from 'react-id-swiper/lib/types'
@ -19,7 +19,7 @@ interface Props {
} }
const Index: NextPage<Props> = () => { const Index: NextPage<Props> = () => {
const { t } = useTranslation() const { t, i18n } = useTranslation()
const router = useRouter() const router = useRouter()
const id = router.query.id as string const id = router.query.id as string
const [swiper, setSwiper] = useState<OriginalSwiper.default>(null) const [swiper, setSwiper] = useState<OriginalSwiper.default>(null)
@ -31,6 +31,17 @@ const Index: NextPage<Props> = () => {
} }
}) })
useEffect(() => {
// check form language to switch to!
if (!data) {
return
}
if (i18n.language !== data.form.language) {
i18n.changeLanguage(data.form.language)
}
}, [data])
if (loading) { if (loading) {
return ( return (
<LoadingPage message={t('form:build')} /> <LoadingPage message={t('form:build')} />

View File

@ -30,11 +30,11 @@ const Index: NextPage = () => {
result.data.tokens.refresh result.data.tokens.refresh
) )
message.success(t('login:welcome-back')) message.success(t('login:welcomeBack'))
router.push('/admin') router.push('/admin')
} catch (e) { } catch (e) {
message.error(t('login:invalid-login-credentials')) message.error(t('login:invalidLoginCredentials'))
} }
setLoading(false) setLoading(false)
@ -71,21 +71,21 @@ const Index: NextPage = () => {
<Form.Item <Form.Item
name="username" name="username"
rules={[{ required: true, message: t('login:username-required') }]} rules={[{ required: true, message: t('validation:usernameRequired') }]}
> >
<Input <Input
size="large" size="large"
placeholder={t('login:username-placeholder')} placeholder={t('login:usernamePlaceholder')}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="password" name="password"
rules={[{ required: true, message: t('login:password-required') }]} rules={[{ required: true, message: t('validation:passwordRequired') }]}
> >
<Input.Password <Input.Password
size="large" size="large"
placeholder={t('login:password-placeholder')} placeholder={t('login:passwordPlaceholder')}
/> />
</Form.Item> </Form.Item>
@ -96,7 +96,7 @@ const Index: NextPage = () => {
htmlType="submit" htmlType="submit"
block block
> >
{t('login:login-now')} {t('login:loginNow')}
</Button> </Button>
</Form.Item> </Form.Item>

View File

@ -38,7 +38,7 @@ const Register: NextPage = () => {
router.push('/') router.push('/')
} catch (e) { } catch (e) {
message.error(t('register:credentials-already-in-use')) message.error(t('register:credentialsAlreadyInUse'))
setLoading(false) setLoading(false)
} }
} }
@ -74,18 +74,18 @@ const Register: NextPage = () => {
<Form.Item <Form.Item
name="username" name="username"
rules={[{ required: true, message: t('login:username-required') }]} rules={[{ required: true, message: t('validation:usernameRequired') }]}
> >
<Input <Input
size="large" size="large"
placeholder={t('login:username-placeholder')} placeholder={t('login:usernamePlaceholder')}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="email" name="email"
rules={[ rules={[
{ required: true, message: t('register:email-required') }, { required: true, message: t('validation:emailRequired') },
{ type: 'email', message: t('validation:invalidEmail') } { type: 'email', message: t('validation:invalidEmail') }
]} ]}
> >
@ -98,13 +98,13 @@ const Register: NextPage = () => {
<Form.Item <Form.Item
name="password" name="password"
rules={[ rules={[
{ required: true, message: t('login:password-required') }, { required: true, message: t('validation:passwordRequired') },
{ min: 5, message: t('register:password-min-length') }, { min: 5, message: t('validation:passwordMinLength') },
]} ]}
> >
<Input.Password <Input.Password
size="large" size="large"
placeholder={t('login:password-placeholder')} placeholder={t('login:passwordPlaceholder')}
/> />
</Form.Item> </Form.Item>
@ -115,7 +115,7 @@ const Register: NextPage = () => {
htmlType="submit" htmlType="submit"
block block
> >
{t('register:register-now')} {t('register:registerNow')}
</Button> </Button>
</Form.Item> </Form.Item>
@ -131,7 +131,7 @@ const Register: NextPage = () => {
type={'link'} type={'link'}
ghost ghost
> >
{t('register:goto-login')} {t('register:gotoLogin')}
</Button> </Button>
</Link> </Link>
</Button.Group> </Button.Group>