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
- `SPA` env variable to have static page with loading spinner before redirect
- `de`, `fr`, `es`, `it`, `cn` base folders for translations
### Changed

View File

@ -35,3 +35,16 @@
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 {useRouter} from 'next/router'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {languages} from '../../i18n'
import {clearAuth, withAuth} from '../with.auth'
interface Props {
@ -13,7 +15,7 @@ interface Props {
}
const AuthFooterInner: React.FC<Props> = props => {
const { t } = useTranslation()
const { t, i18n } = useTranslation()
const router = useRouter()
const logout = () => {
@ -28,6 +30,8 @@ const AuthFooterInner: React.FC<Props> = props => {
bottom: 0,
left: 0,
right: 0,
display: 'flex',
flexDirection: 'row',
}}
>
<Link href={'/admin'}>
@ -72,14 +76,24 @@ const AuthFooterInner: React.FC<Props> = props => {
</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
type={'link'}
target={'_blank'}
ghost
href={'https://www.ohmyform.com'}
style={{
float: 'right',
color: '#FFF'
}}
>

View File

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

View File

@ -1,27 +1,30 @@
import {Form, Input, Tabs} from 'antd'
import {TabPaneProps} from 'antd/lib/tabs'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {InputColor} from '../../input/color'
export const DesignTab: React.FC<TabPaneProps> = props => {
const { t } = useTranslation()
return (
<Tabs.TabPane {...props}>
<Form.Item
label="Font"
label={t('form:design.font')}
name={['form', 'design', 'font']}
>
<Input />
</Form.Item>
{[
{name: 'backgroundColor', label: 'Background Color'},
{name: 'questionColor', label: 'Question Color'},
{name: 'answerColor', label: 'Answer Color'},
{name: 'buttonColor', label: 'Button Color'},
{name: 'buttonActiveColor', label: 'Button Active Color'},
{name: 'buttonTextColor', label: 'Button Text Color'},
].map(({label, name}) => (
<Form.Item key={name} label={label} name={['form', 'design', 'colors', name]}>
'backgroundColor',
'questionColor',
'answerColor',
'buttonColor',
'buttonActiveColor',
'buttonTextColor',
].map(name => (
<Form.Item key={name} label={t(`form:design.${name}`)} name={['form', 'design', 'colors', name]}>
<InputColor />
</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 {TabPaneProps} from 'antd/lib/tabs'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {InputColor} from '../../input/color'
export const EndPageTab: React.FC<TabPaneProps> = props => {
const { t } = useTranslation()
return (
<Tabs.TabPane {...props}>
<Form.Item
label={'Show'}
label={t('form:endPage.show')}
name={['form', 'endPage', 'show']}
valuePropName={'checked'}
>
@ -16,21 +19,21 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
</Form.Item>
<Form.Item
label={'Title'}
label={t('form:endPage.title')}
name={['form', 'endPage', 'title']}
>
<Input />
</Form.Item>
<Form.Item
label={'Paragraph'}
label={t('form:endPage.paragraph')}
name={['form', 'endPage', 'paragraph']}
>
<Input.TextArea autoSize />
</Form.Item>
<Form.Item
label={'Continue Button Text'}
label={t('form:endPage.continueButtonText')}
name={['form', 'endPage', 'buttonText']}
>
<Input />
@ -47,7 +50,7 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
wrapperCol={{
sm: { offset: index === 0 ? 0 : 6 },
}}
label={index === 0 ? 'Buttons' : ''}
label={index === 0 ? t('form:endPage.buttons') : ''}
key={field.key}
>
<Card
@ -56,28 +59,28 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
]}
>
<Form.Item
label={'Url'}
label={t('form:endPage.url')}
name={[field.key, 'url']}
rules={[
{type: 'url', message: 'Must be a valid url'}
{type: 'url', message: t('validation:invalidUrl')}
]}
labelCol={{ span: 6 }}
>
<Input />
</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 />
</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 />
</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 />
</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 />
</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 />
</Form.Item>
</Card>
@ -96,7 +99,7 @@ export const EndPageTab: React.FC<TabPaneProps> = props => {
}}
style={{ width: '60%' }}
>
<PlusOutlined /> Add Button
<PlusOutlined /> {t('form:endPage.addButton')}
</Button>
</Form.Item>
</div>

View File

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

View File

@ -3,6 +3,7 @@ import {Button, Form, Select, Space, Tabs} from 'antd'
import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs'
import React, {useCallback, useState} from 'react'
import {useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
import {FieldCard} from './field.card'
import {adminTypes} from './types'
@ -14,6 +15,7 @@ interface Props extends TabPaneProps {
}
export const FieldsTab: React.FC<Props> = props => {
const { t } = useTranslation()
const [nextType, setNextType] = useState('textfield')
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 }}>
{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>
<Button
type="dashed"

View File

@ -1,7 +1,9 @@
import {InfoCircleOutlined} from '@ant-design/icons/lib'
import {Form, Input, Select, Switch, Tabs} from 'antd'
import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs'
import React, {useEffect, useState} from 'react'
import {Trans, useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps {
@ -10,6 +12,7 @@ interface Props extends TabPaneProps {
}
export const RespondentNotificationsTab: React.FC<Props> = props => {
const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>()
useEffect(() => {
@ -40,7 +43,7 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
return (
<Tabs.TabPane {...props}>
<Form.Item
label={'Enabled'}
label={t('form:respondentNotifications.enabled')}
name={['form', 'respondentNotifications', 'enabled']}
valuePropName={'checked'}
>
@ -48,12 +51,12 @@ export const RespondentNotificationsTab: React.FC<Props> = props => {
</Form.Item>
<Form.Item
label={'Subject'}
label={t('form:respondentNotifications.subject')}
name={['form', 'respondentNotifications', 'subject']}
rules={[
{
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
label={'HTML Template'}
label={t('form:respondentNotifications.htmlTemplate')}
name={['form', 'respondentNotifications', 'htmlTemplate']}
rules={[
{
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 />
</Form.Item>
<Form.Item
label={'Email Field'}
label={t('form:respondentNotifications.toField')}
name={['form', 'respondentNotifications', 'toField']}
extra={'Field with Email for receipt'}
extra={t('form:respondentNotifications.toFieldInfo')}
rules={[
{
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
label={'Sender Email'}
label={t('form:respondentNotifications.fromEmail')}
name={['form', 'respondentNotifications', 'fromEmail']}
extra={'Make sure your mailserver can send from this email'}
extra={t('form:respondentNotifications.fromEmailInfo')}
>
<Input />
</Form.Item>

View File

@ -1,7 +1,9 @@
import {InfoCircleOutlined} from '@ant-design/icons/lib'
import {Form, Input, Select, Switch, Tabs} from 'antd'
import {FormInstance} from 'antd/lib/form'
import {TabPaneProps} from 'antd/lib/tabs'
import React, {useEffect, useState} from 'react'
import {Trans, useTranslation} from 'react-i18next'
import {AdminFormFieldFragment} from '../../../graphql/fragment/admin.form.fragment'
interface Props extends TabPaneProps {
@ -10,6 +12,7 @@ interface Props extends TabPaneProps {
}
export const SelfNotificationsTab: React.FC<Props> = props => {
const { t } = useTranslation()
const [enabled, setEnabled] = useState<boolean>()
useEffect(() => {
@ -38,7 +41,7 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
return (
<Tabs.TabPane {...props}>
<Form.Item
label={'Enabled'}
label={t('form:selfNotifications.enabled')}
name={['form', 'selfNotifications', 'enabled']}
valuePropName={'checked'}
>
@ -46,12 +49,12 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
</Form.Item>
<Form.Item
label={'Subject'}
label={t('form:selfNotifications.subject')}
name={['form', 'selfNotifications', 'subject']}
rules={[
{
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
label={'HTML Template'}
label={t('form:selfNotifications.htmlTemplate')}
name={['form', 'selfNotifications', 'htmlTemplate']}
rules={[
{
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 />
</Form.Item>
<Form.Item
label={'Email Field'}
label={t('form:selfNotifications.fromField')}
name={['form', 'selfNotifications', 'fromField']}
extra={'Field with Email, will set the Reply-To header'}
extra={t('form:selfNotifications.fromFieldInfo')}
>
<Select>
{Object.keys(groups).map(key => (
@ -89,9 +105,9 @@ export const SelfNotificationsTab: React.FC<Props> = props => {
</Form.Item>
<Form.Item
label={'Your Email'}
label={t('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 />
</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 {TabPaneProps} from 'antd/lib/tabs'
import React from 'react'
import {useTranslation} from 'react-i18next'
import {InputColor} from '../../input/color'
export const StartPageTab: React.FC<TabPaneProps> = props => {
const { t } = useTranslation()
return (
<Tabs.TabPane {...props}>
<Form.Item
label={'Show'}
label={t('form:startPage.show')}
name={['form', 'startPage', 'show']}
valuePropName={'checked'}
>
@ -16,21 +19,21 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
</Form.Item>
<Form.Item
label={'Title'}
label={t('form:startPage.title')}
name={['form', 'startPage', 'title']}
>
<Input />
</Form.Item>
<Form.Item
label={'Paragraph'}
label={t('form:startPage.paragraph')}
name={['form', 'startPage', 'paragraph']}
>
<Input.TextArea autoSize />
</Form.Item>
<Form.Item
label={'Continue Button Text'}
label={t('form:startPage.continueButtonText')}
name={['form', 'startPage', 'buttonText']}
>
<Input />
@ -47,7 +50,7 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
wrapperCol={{
sm: { offset: index === 0 ? 0 : 6 },
}}
label={index === 0 ? 'Buttons' : ''}
label={index === 0 ? t('form:startPage.buttons') : ''}
key={field.key}
>
<Card
@ -56,28 +59,28 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
]}
>
<Form.Item
label={'Url'}
label={t('form:startPage.url')}
name={[field.key, 'url']}
rules={[
{type: 'url', message: 'Must be a valid url'}
{type: 'url', message: t('validation:invalidUrl')}
]}
labelCol={{ span: 6 }}
>
<Input />
</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 />
</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 />
</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 />
</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 />
</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 />
</Form.Item>
</Card>
@ -96,7 +99,7 @@ export const StartPageTab: React.FC<TabPaneProps> = props => {
}}
style={{ width: '60%' }}
>
<PlusOutlined /> Add Button
<PlusOutlined /> {t('form:startPage.addButton')}
</Button>
</Form.Item>
</div>

View File

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

View File

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

View File

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

View File

@ -1,9 +1,11 @@
import {CaretDownOutlined, UserOutlined} from '@ant-design/icons'
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 {useRouter} from 'next/router'
import React, {FunctionComponent} from 'react'
import {useTranslation} from 'react-i18next'
import {languages} from '../i18n'
import {sideMenu, SideMenuElement} from './sidemenu'
import {useWindowSize} from './use.window.size'
import {clearAuth} from './with.auth'
@ -32,6 +34,7 @@ interface Props {
}
const Structure: FunctionComponent<Props> = (props) => {
const { t, i18n } = useTranslation()
const size = useWindowSize()
const [userMenu, setUserMenu] = React.useState(false)
const [open, setOpen] = React.useState<string[]>()
@ -120,7 +123,7 @@ const Structure: FunctionComponent<Props> = (props) => {
}
return (
<Layout style={{ height: '100vh' }}>
<Layout style={{ height: '100vh' }} className={'admin'}>
<Header
style={{
paddingLeft: 0,
@ -177,9 +180,11 @@ const Structure: FunctionComponent<Props> = (props) => {
maxHeight: '100%',
overflow: 'auto',
}}
className={'sidemenu'}
>
<Menu
mode="inline"
style={{ flex: 1 }}
defaultSelectedKeys={['1']}
selectedKeys={selected}
onSelect={(s): void => setSelected(s.keyPath)}
@ -192,11 +197,19 @@ const Structure: FunctionComponent<Props> = (props) => {
mode="inline"
selectable={false}
>
<Menu.Item
style={{
marginTop: 40,
}}
>
<Menu.Item className={'language-selector'}>
<Select
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>
</Menu.Item>
</Menu>

View File

@ -14,5 +14,7 @@ i18n
defaultNS: 'common',
react: {
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',
create: 'Create new form',
createNow: 'Save',
baseData: 'Base Data',
baseDataTab: 'Base Data',
selfNotificationsTab: 'Self Notifications',
respondentNotificationsTab: 'Respondent Notifications',
updated: 'Form updated',
updateError: 'Could not update form',
updateNow: 'Save',
design: 'Design',
startPage: 'Start Page',
endPage: 'End Page',
designTab: 'Design',
startPageTab: 'Start Page',
endPageTab: 'End Page',
confirmDelete: 'Are you sure delete this form with all submissions?',
deleted: 'Form deleted',
deleteError: 'could not delete form',
@ -28,5 +30,68 @@ export const form = {
created: 'Created',
lastModified: 'Last Modified',
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 {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'
@ -13,11 +15,13 @@ export const en = {
admin,
common,
form,
language,
login,
profile,
register,
statistic,
submission,
type,
user,
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 = {
'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',
welcomeBack: 'Welcome back!',
invalidLoginCredentials: 'username / password are invalid',
usernamePlaceholder: 'Username',
passwordPlaceholder: 'Password',
loginNow: 'Login Now',
}

View File

@ -1,8 +1,6 @@
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',
'credentialsAlreadyInUse': 'Some data already in use!',
'registerNow': 'Register Now',
'gotoLogin': 'Have an account? Go to login',
}

View File

@ -1,10 +1,18 @@
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: {
name: 'Date',
default: 'Default Date',
min: 'Min Date',
max: 'Max Date',
},
dropdown: {
name: 'Dropdown',
default: 'Default Value',
options: 'Options',
titlePlaceholder: 'Title',
@ -13,30 +21,45 @@ export const type = {
addOption: 'Add Option',
},
email: {
name: 'Email',
default: 'Default Email',
},
hidden: {
name: 'Hidden',
default: 'Default Value',
},
link: {
name: 'URL',
default: 'Default Link',
},
number: {
name: 'Number',
default: 'Default Number',
},
radio: {
name: 'Radio Switch',
default: 'Default Value',
options: 'Options',
titlePlaceholder: 'Title',
valuePlaceholder: 'Value',
removeOption: 'Remove',
addOption: 'Add Option',
},
rating: {
name: 'Rating',
default: 'Default Value',
clearNote: 'Click again to remove the default value'
},
text: {
textfield: {
name: 'Text Line',
default: 'Default Value',
},
textarea: {
name: 'Text Area',
default: 'Default Value',
},
yes_no: {
name: 'Yes / No',
default: 'Default Value',
},
}

View File

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

View File

@ -3,7 +3,13 @@ export const validation = {
mandatoryFieldsMissing: 'Mandatory fields missing',
usernameRequired: 'Please provide a Username',
emailRequired: 'Please provide an Email',
emailFieldRequired: 'Please select an Email Field',
languageRequired: 'Please select a Language',
valueRequired: 'Please provide a Value',
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>
<FieldsTab
key={'fields'}
tab={'Fields'}
tab={t('form:fieldsTab')}
fields={fields}
onChangeFields={setFields}
form={form}
/>
<BaseDataTab key={'base_data'} tab={t('form:baseData')} />
<DesignTab key={'design'} tab={'form:design'} />
<BaseDataTab key={'base_data'} tab={t('form:baseDataTab')} />
<DesignTab key={'design'} tab={t('form:designTab')} />
<SelfNotificationsTab
key={'self_notifications'}
tab={'Self Notifications'}
tab={t('form:selfNotificationsTab')}
fields={fields}
form={form}
/>
<RespondentNotificationsTab
key={'respondent_notifications'}
tab={'Respondent Notifications'}
tab={t('form:respondentNotificationsTab')}
fields={fields}
form={form}
/>
<StartPageTab key={'start_page'} tab={t('form:startPage')} />
<EndPageTab key={'end_page'} tab={t('form:endPage')} />
<StartPageTab key={'start_page'} tab={t('form:startPageTab')} />
<EndPageTab key={'end_page'} tab={t('form:endPageTab')} />
</Tabs>
</Form>
</Structure>

View File

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

View File

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

View File

@ -129,7 +129,7 @@ const Profile: NextPage = () => {
]}
>
<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>
</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 {NextPage} from 'next'
import {useRouter} from 'next/router'
import React, {useState} from 'react'
import React, {useEffect, useState} from 'react'
import {useTranslation} from 'react-i18next'
import Swiper from 'react-id-swiper'
import {ReactIdSwiperProps} from 'react-id-swiper/lib/types'
@ -19,7 +19,7 @@ interface Props {
}
const Index: NextPage<Props> = () => {
const { t } = useTranslation()
const { t, i18n } = useTranslation()
const router = useRouter()
const id = router.query.id as string
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) {
return (
<LoadingPage message={t('form:build')} />

View File

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

View File

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