1
0

Add translations (#562)

Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/562
Co-authored-by: konrad <konrad@kola-entertainments.de>
Co-committed-by: konrad <konrad@kola-entertainments.de>
This commit is contained in:
konrad
2021-06-23 23:24:57 +00:00
parent 5badb65037
commit f0498fd767
103 changed files with 2306 additions and 973 deletions

View File

@ -1,7 +1,7 @@
<template>
<div class="content has-text-centered">
<h1>Not found</h1>
<p>The page you requested does not exist.</p>
<h1>{{ $t('404.title') }}</h1>
<p>{{ $t('404.text') }}</p>
</div>
</template>

View File

@ -1,26 +1,26 @@
<template>
<div class="content has-text-centered">
<h2>
Hi {{ userInfo.name !== '' ? userInfo.name : userInfo.username }}!
{{ $t('home.welcome', {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
</h2>
<template v-if="!hasTasks">
<p>You can create a new list for your new tasks:</p>
<p>{{ $t('home.list.newText') }}</p>
<x-button
:to="{name: 'list.create', params: { id: defaultNamespaceId }}"
:shadow="false"
class="ml-2"
v-if="defaultNamespaceId > 0"
>
Create a new list
{{ $t('home.list.new') }}
</x-button>
<p class="mt-4" v-if="migratorsEnabled">
Or import your lists and tasks from other services into Vikunja:
{{ $t('home.list.importText') }}
</p>
<x-button
v-if="migratorsEnabled"
:to="{ name: 'migrate.start' }"
:shadow="false">
Import your data into Vikunja
{{ $t('home.list.import') }}
</x-button>
</template>
<ShowTasks :show-all="true" v-if="hasLists"/>
@ -28,7 +28,7 @@
</template>
<script>
import { mapState } from 'vuex'
import {mapState} from 'vuex'
import ShowTasks from './tasks/ShowTasks'
export default {

View File

@ -2,13 +2,12 @@
<div class="modal-mask keyboard-shortcuts-modal">
<div @click.self="$router.back()" class="modal-container">
<div class="modal-content">
<card class="has-background-white has-no-shadow" title="Create A Saved Filter">
<card class="has-background-white has-no-shadow" :title="$t('filters.create.title')">
<p>
A saved filter is a virtual list which is computed from a set of filters each time it is
accessed. Once created, it will appear in a special namespace.
{{ $t('filters.create.description') }}
</p>
<div class="field">
<label class="label" for="title">Title</label>
<label class="label" for="title">{{ $t('filters.attributes.title') }}</label>
<div class="control">
<input
v-model="savedFilter.title"
@ -16,14 +15,14 @@
:disabled="savedFilterService.loading"
class="input"
id="Title"
placeholder="The saved filter title goes here..."
:placeholder="$t('filters.attributes.titlePlaceholder')"
type="text"
v-focus
/>
</div>
</div>
<div class="field">
<label class="label" for="description">Description</label>
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
<div class="control">
<editor
v-model="savedFilter.description"
@ -31,13 +30,13 @@
:disabled="savedFilterService.loading"
:preview-is-default="false"
id="description"
placeholder="The description goes here..."
:placeholder="$t('filters.attributes.descriptionPlaceholder')"
v-if="editorActive"
/>
</div>
</div>
<div class="field">
<label class="label" for="filters">Filters</label>
<label class="label" for="filters">{{ $t('filters.title') }}</label>
<div class="control">
<filters
:class="{ 'disabled': savedFilterService.loading}"
@ -53,7 +52,7 @@
@click="create()"
class="is-fullwidth"
>
Create new saved filter
{{ $t('filters.create.action') }}
</x-button>
</card>
</div>

View File

@ -3,9 +3,9 @@
@close="$router.back()"
@submit="deleteSavedFilter()"
>
<span slot="header">Delete this saved filter</span>
<span slot="header">{{ $t('filters.delete.header') }}</span>
<p slot="text">
Are you sure you want to delete this saved filter?
{{ $t('filters.delete.text') }}
</p>
</modal>
</template>
@ -34,7 +34,7 @@ export default {
this.filterService.delete(filter)
.then(() => {
this.$store.dispatch('namespaces/loadNamespaces')
this.success({message: 'The filter was deleted successfully.'})
this.success({message: this.$t('filters.delete.success')})
this.$router.push({name: 'namespaces.index'})
})
.catch(e => this.error(e))

View File

@ -1,15 +1,15 @@
<template>
<create-edit
title="Edit This Saved Filter"
:title="$t('filters.edit.title')"
primary-icon=""
primary-label="Save"
:primary-label="$t('misc.save')"
@primary="save"
tertary="Delete"
:tertary="$t('misc.delete')"
@tertary="$router.push({ name: 'filter.list.settings.delete', params: { id: $route.params.listId } })"
>
<form @submit.prevent="save()">
<div class="field">
<label class="label" for="title">Filter Title</label>
<label class="label" for="title">{{ $t('filters.attributes.title') }}</label>
<div class="control">
<input
:class="{ 'disabled': filterService.loading}"
@ -17,27 +17,27 @@
@keyup.enter="save"
class="input"
id="title"
placeholder="The title goes here..."
:placeholder="$t('filters.attributes.titlePlaceholder')"
type="text"
v-focus
v-model="filter.title"/>
</div>
</div>
<div class="field">
<label class="label" for="description">Description</label>
<label class="label" for="description">{{ $t('filters.attributes.description') }}</label>
<div class="control">
<editor
:class="{ 'disabled': filterService.loading}"
:disabled="filterService.loading"
:preview-is-default="false"
id="description"
placeholder="The description goes here..."
:placeholder="$t('filters.attributes.descriptionPlaceholder')"
v-model="filter.description"
/>
</div>
</div>
<div class="field">
<label class="label" for="filters">Filters</label>
<label class="label" for="filters">{{ $t('filters.title') }}</label>
<div class="control">
<filters
:class="{ 'disabled': filterService.loading}"
@ -117,7 +117,7 @@ export default {
this.filterService.update(this.filter)
.then(r => {
this.$store.dispatch('namespaces/loadNamespaces')
this.success({message: 'The filter was saved successfully.'})
this.success({message: this.$t('filters.attributes.edit.success')})
this.filter = r
this.filters = objectToSnakeCase(this.filter.filters)
this.$router.back()

View File

@ -5,19 +5,17 @@
class="is-pulled-right"
icon="plus"
>
New label
{{ $t('label.create.header') }}
</x-button>
<div class="content">
<h1>Manage labels</h1>
<p v-if="labels.length > 0">
Click on a label to edit it.
You can edit all labels you created, you can use all labels which are associated with a task to whose
list you have access.
<h1>{{ $t('label.manage') }}</h1>
<p v-if="Object.entries(labels).length > 0">
{{ $t('label.description') }}
</p>
<p v-else class="has-text-centered has-text-grey is-italic">
You currently do not have any labels.
<router-link :to="{name:'labels.create'}">Create a new label.</router-link>
{{ $t('label.newCTA') }}
<router-link :to="{name:'labels.create'}">{{ $t('label.create.title') }}.</router-link>
</p>
</div>
@ -31,7 +29,7 @@
>
<span
v-if="userInfo.id !== l.createdBy.id"
v-tooltip.bottom="'You are not allowed to edit this label because you dont own it.'">
v-tooltip.bottom="$t('label.edit.forbidden')">
{{ l.title }}
</span>
<a
@ -44,31 +42,31 @@
</span>
</div>
<div class="column is-4" v-if="isLabelEdit">
<card title="Edit Label" :has-close="true" @close="() => isLabelEdit = false">
<card :title="$t('label.edit.header')" :has-close="true" @close="() => isLabelEdit = false">
<form @submit.prevent="editLabelSubmit()">
<div class="field">
<label class="label">Title</label>
<label class="label">{{ $t('label.attributes.title') }}</label>
<div class="control">
<input
class="input"
placeholder="Label title"
:placeholder="$t('label.attributes.titlePlaceholder')"
type="text"
v-model="labelEditLabel.title"/>
</div>
</div>
<div class="field">
<label class="label">Description</label>
<label class="label">{{ $t('label.attributes.description') }}</label>
<div class="control">
<editor
:preview-is-default="false"
placeholder="Label description"
:placeholder="$t('label.attributes.description')"
v-if="editorActive"
v-model="labelEditLabel.description"
/>
</div>
</div>
<div class="field">
<label class="label">Color</label>
<label class="label">{{ $t('label.attributes.color') }}</label>
<div class="control">
<color-picker v-model="labelEditLabel.hexColor"/>
</div>
@ -80,7 +78,7 @@
class="is-fullwidth"
@click="editLabelSubmit()"
>
Save
{{ $t('misc.save') }}
</x-button>
</div>
<div class="control">
@ -130,7 +128,7 @@ export default {
this.loadLabels()
},
mounted() {
this.setTitle('Labels')
this.setTitle(this.$t('label.title'))
},
computed: mapState({
userInfo: state => state.auth.info,
@ -147,7 +145,7 @@ export default {
deleteLabel(label) {
this.$store.dispatch('labels/deleteLabel', label)
.then(() => {
this.success({message: 'The label was successfully deleted.'})
this.success({message: this.$t('label.deleteSuccess')})
})
.catch(e => {
this.error(e)
@ -156,7 +154,7 @@ export default {
editLabelSubmit() {
this.$store.dispatch('labels/updateLabel', this.labelEditLabel)
.then(() => {
this.success({message: 'The label was successfully updated.'})
this.success({message: this.$t('label.edit.success')})
})
.catch(e => {
this.error(e)

View File

@ -1,11 +1,11 @@
<template>
<create-edit
title="Create a new label"
:title="$t('label.create.title')"
@create="newLabel()"
:create-disabled="label.title === ''"
>
<div class="field">
<label class="label" for="labelTitle">Label Title</label>
<label class="label" for="labelTitle">{{ $t('label.attributes.title') }}</label>
<div
class="control is-expanded"
:class="{ 'is-loading': loading }"
@ -13,7 +13,7 @@
<input
:class="{ disabled: loading }"
class="input"
placeholder="The label title goes here..."
:placeholder="$t('label.attributes.titlePlaceholder')"
type="text"
id="labelTitle"
v-focus
@ -23,10 +23,10 @@
</div>
</div>
<p class="help is-danger" v-if="showError && label.title === ''">
Please specify a title.
{{ $t('label.create.titleRequired') }}
</p>
<div class="field">
<label class="label">Color</label>
<label class="label">{{ $t('label.attributes.color') }}</label>
<div class="control">
<color-picker v-model="label.hexColor"/>
</div>
@ -58,7 +58,7 @@ export default {
this.label = new LabelModel()
},
mounted() {
this.setTitle('Create a new label')
this.setTitle(this.$t('label.create.title'))
},
computed: mapState({
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
@ -77,7 +77,7 @@ export default {
name: 'labels.index',
params: {id: r.id},
})
this.success({message: 'The label was successfully created.'})
this.success({message: this.$t('label.create.success')})
})
.catch((e) => {
this.error(e)

View File

@ -1,7 +1,7 @@
<template>
<create-edit title="Create a new list" @create="newList()" :create-disabled="list.title === ''">
<create-edit :title="$t('list.create.header')" @create="newList()" :create-disabled="list.title === ''">
<div class="field">
<label class="label" for="listTitle">List Title</label>
<label class="label" for="listTitle">{{ $t('list.title') }}</label>
<div
:class="{ 'is-loading': listService.loading }"
class="control"
@ -11,7 +11,7 @@
@keyup.enter="newList()"
@keyup.esc="$router.back()"
class="input"
placeholder="The list's title goes here..."
:placeholder="$t('list.create.titlePlaceholder')"
type="text"
name="listTitle"
v-focus
@ -20,10 +20,10 @@
</div>
</div>
<p class="help is-danger" v-if="showError && list.title === ''">
Please specify a title.
{{ $t('list.create.addTitleRequired') }}
</p>
<div class="field">
<label class="label">Color</label>
<label class="label">{{ $t('list.color') }}</label>
<div class="control">
<color-picker v-model="list.hexColor" />
</div>
@ -55,7 +55,7 @@ export default {
this.listService = new ListService()
},
mounted() {
this.setTitle('Create a new list')
this.setTitle(this.$t('list.create.header'))
},
methods: {
newList() {
@ -69,7 +69,7 @@ export default {
this.$store
.dispatch('lists/createList', this.list)
.then((r) => {
this.success({message: 'The list was successfully created.'})
this.success({message: this.$t('list.create.createdSuccess') })
this.$router.push({
name: 'list.index',
params: { listId: r.id },

View File

@ -8,29 +8,28 @@
<router-link
:class="{'is-active': $route.name.includes('list.list')}"
:to="{ name: 'list.list', params: { listId: listId } }">
List
{{ $t('list.list.title') }}
</router-link>
<router-link
:class="{'is-active': $route.name.includes('list.gantt')}"
:to="{ name: 'list.gantt', params: { listId: listId } }">
Gantt
{{ $t('list.gantt.title') }}
</router-link>
<router-link
:class="{'is-active': $route.name.includes('list.table')}"
:to="{ name: 'list.table', params: { listId: listId } }">
Table
{{ $t('list.table.title') }}
</router-link>
<router-link
:class="{'is-active': $route.name.includes('list.kanban')}"
:to="{ name: 'list.kanban', params: { listId: listId } }">
Kanban
{{ $t('list.kanban.title') }}
</router-link>
</div>
</div>
<transition name="fade">
<div class="notification is-warning" v-if="currentList.isArchived">
This list is archived.
It is not possible to create new or edit tasks or it.
{{ $t('list.archived') }}
</div>
</transition>

View File

@ -3,12 +3,12 @@
@close="$router.back()"
@submit="archiveList()"
>
<span slot="header">{{ list.isArchived ? 'Un-' : '' }}Archive this list</span>
<span slot="header">{{ list.isArchived ? $t('list.archive.unarchive') : $t('list.archive.archive') }}</span>
<p slot="text" v-if="list.isArchived">
You will be able to create new tasks or edit it.
{{ $t('list.archive.unarchiveText') }}
</p>
<p slot="text" v-else>
You won't be able to edit this list or create new tasks until you un-archive it.
{{ $t('list.archive.archiveText') }}
</p>
</modal>
</template>
@ -27,7 +27,7 @@ export default {
created() {
this.listService = new ListService()
this.list = this.$store.getters['lists/getListById'](this.$route.params.listId)
this.setTitle(`Archive "${this.list.title}"`)
this.setTitle(this.$t('list.archive.title', {list: this.list.title}))
},
methods: {
archiveList() {
@ -38,7 +38,7 @@ export default {
.then(r => {
this.$store.commit('currentList', r)
this.$store.commit('namespaces/setListInNamespaceById', r)
this.success({message: 'The list was successfully archived.'})
this.success({message: this.$t('list.archive.success')})
})
.catch(e => {
this.error(e)

View File

@ -1,12 +1,12 @@
<template>
<create-edit
title="Set list background"
:title="$t('list.background.title')"
primary-label=""
:loading="backgroundService.loading"
class="list-background-setting"
:wide="true"
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
:tertary="hasBackground ? 'Remove Background' : ''"
:tertary="hasBackground ? $t('list.background.remove') : ''"
@tertary="removeBackground()"
>
<div class="mb-4" v-if="uploadBackgroundEnabled">
@ -22,7 +22,7 @@
@click="$refs.backgroundUploadInput.click()"
type="primary"
>
Choose a background from your pc
{{ $t('list.background.upload') }}
</x-button>
</div>
<template v-if="unsplashBackgroundEnabled">
@ -30,11 +30,13 @@
:class="{'is-loading': backgroundService.loading}"
@keyup="() => newBackgroundSearch()"
class="input is-expanded"
placeholder="Search for a background..."
:placeholder="$t('list.background.searchPlaceholder')"
type="text"
v-model="backgroundSearchTerm"
/>
<p class="unsplash-link"><a href="https://unsplash.com" target="_blank">Powered by Unsplash</a></p>
<p class="unsplash-link">
<a href="https://unsplash.com" target="_blank">{{ $t('list.background.poweredByUnsplash') }}</a>
</p>
<div class="image-search-result">
<a
:key="im.id"
@ -55,7 +57,7 @@
type="secondary"
v-if="backgroundSearchResult.length > 0"
>
{{ backgroundService.loading ? 'Loading...' : 'Load more photos' }}
{{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }}
</x-button>
</template>
</create-edit>
@ -95,7 +97,7 @@ export default {
this.backgroundService = new BackgroundUnsplashService()
this.backgroundUploadService = new BackgroundUploadService()
this.listService = new ListService()
this.setTitle('Set a list background')
this.setTitle(this.$t('list.background.title'))
// Show the default collection of backgrounds
this.newBackgroundSearch()
},
@ -144,7 +146,7 @@ export default {
.then(l => {
this.$store.commit(CURRENT_LIST, l)
this.$store.commit('namespaces/setListInNamespaceById', l)
this.success({message: 'The background has been set successfully!'})
this.success({message: this.$t('list.background.success')})
})
.catch(e => {
this.error(e)
@ -159,7 +161,7 @@ export default {
.then(l => {
this.$store.commit(CURRENT_LIST, l)
this.$store.commit('namespaces/setListInNamespaceById', l)
this.success({message: 'The background has been set successfully!'})
this.success({message: this.$t('list.background.success')})
})
.catch(e => {
this.error(e)
@ -170,7 +172,7 @@ export default {
.then(l => {
this.$store.commit(CURRENT_LIST, l)
this.$store.commit('namespaces/setListInNamespaceById', l)
this.success({message: 'The background has been removed successfully!'})
this.success({message: this.$t('list.background.removeSuccess')})
this.$router.back()
})
.catch(e => {

View File

@ -3,9 +3,11 @@
@close="$router.back()"
@submit="deleteList()"
>
<span slot="header">Delete this list</span>
<p slot="text">Are you sure you want to delete this list and all of its contents?
<br/>This includes all tasks and <b>CANNOT BE UNDONE!</b></p>
<span slot="header">{{ $t('list.delete.header') }}</span>
<p slot="text">
{{ $t('list.delete.text1') }}<br/>
{{ $t('list.delete.text2') }}
</p>
</modal>
</template>
@ -22,7 +24,7 @@ export default {
created() {
this.listService = new ListService()
const list = this.$store.getters['lists/getListById'](this.$route.params.listId)
this.setTitle(`Delete "${list.title}"`)
this.setTitle(this.$t('list.delete.title', {list: list.title}))
},
methods: {
deleteList() {
@ -31,7 +33,7 @@ export default {
this.listService.delete(list)
.then(() => {
this.$store.commit('namespaces/removeListFromNamespaceById', list)
this.success({message: 'The list was successfully deleted.'})
this.success({message: this.$t('list.delete.success')})
this.$router.push({name: 'home'})
})
.catch(e => {

View File

@ -1,12 +1,14 @@
<template>
<create-edit
title="Duplicate this list"
:title="$t('list.duplicate.title')"
primary-icon="paste"
primary-label="Duplicate"
:primary-label="$t('list.duplicate.label')"
@primary="duplicateList"
:loading="listDuplicateService.loading"
>
<p>Select a namespace which should hold the duplicated list:</p>
<p>
{{ $t('list.duplicate.text') }}
</p>
<namespace-search @selected="selectNamespace"/>
</create-edit>
</template>
@ -31,7 +33,7 @@ export default {
},
created() {
this.listDuplicateService = new ListDuplicateService()
this.setTitle('Duplicate List')
this.setTitle(this.$t('list.duplicate.title'))
},
methods: {
selectNamespace(namespace) {
@ -46,7 +48,7 @@ export default {
.then(r => {
this.$store.commit('namespaces/addListToNamespace', r.list)
this.$store.commit('lists/setList', r.list)
this.success({message: 'The list was successfully duplicated.'})
this.success({message: this.$t('list.duplicate.success')})
this.$router.push({name: 'list.index', params: {listId: r.list.id}})
})
.catch(e => {

View File

@ -1,14 +1,14 @@
<template>
<create-edit
title="Edit This List"
:title="$t('list.edit.header')"
primary-icon=""
primary-label="Save"
:primary-label="$t('misc.save')"
@primary="save"
tertary="Delete"
:tertary="$t('misc.delete')"
@tertary="$router.push({ name: 'list.list.settings.delete', params: { id: $route.params.listId } })"
>
<div class="field">
<label class="label" for="listtext">List Name</label>
<label class="label" for="listtext">{{ $t('list.title') }}</label>
<div class="control">
<input
:class="{ 'disabled': listService.loading}"
@ -16,7 +16,7 @@
@keyup.enter="save"
class="input"
id="listtext"
placeholder="The list title goes here..."
:placeholder="$t('list.edit.titlePlaceholder')"
type="text"
v-focus
v-model="list.title"/>
@ -26,8 +26,8 @@
<label
class="label"
for="listtext"
v-tooltip="'The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.'">
List Identifier
v-tooltip="$t('list.edit.identifierTooltip')">
{{ $t('list.edit.identifier') }}
</label>
<div class="control">
<input
@ -36,27 +36,27 @@
@keyup.enter="save"
class="input"
id="listtext"
placeholder="The list identifier goes here..."
:placeholder="$t('list.edit.identifierPlaceholder')"
type="text"
v-focus
v-model="list.identifier"/>
</div>
</div>
<div class="field">
<label class="label" for="listdescription">Description</label>
<label class="label" for="listdescription">{{ $t('list.edit.description') }}</label>
<div class="control">
<editor
:class="{ 'disabled': listService.loading}"
:disabled="listService.loading"
:preview-is-default="false"
id="listdescription"
placeholder="The lists description goes here..."
:placeholder="$t('list.edit.descriptionPlaceholder')"
v-model="list.description"
/>
</div>
</div>
<div class="field">
<label class="label">Color</label>
<label class="label">{{ $t('list.edit.color') }}</label>
<div class="control">
<color-picker v-model="list.hexColor"/>
</div>
@ -106,7 +106,7 @@ export default {
.then(r => {
this.$set(this, 'list', r)
this.$store.commit(CURRENT_LIST, r)
this.setTitle(`Edit "${this.list.title}"`)
this.setTitle(this.$t('list.edit.title', {list: this.list.title}))
})
.catch(e => {
this.error(e)
@ -115,7 +115,7 @@ export default {
save() {
this.$store.dispatch('lists/updateList', this.list)
.then(() => {
this.success({message: 'The list was successfully updated.'})
this.success({message: this.$t('list.edit.success')})
this.$router.back()
})
.catch(e => {

View File

@ -1,6 +1,6 @@
<template>
<create-edit
title="Share this list"
:title="$t('list.share.header')"
primary-label=""
>
<component
@ -67,7 +67,7 @@ export default {
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
this.manageTeamsComponent = 'userTeam'
this.manageUsersComponent = 'userTeam'
this.setTitle(`Share "${this.list.title}"`)
this.setTitle(this.$t('list.share.title', {list: this.list.title}))
})
.catch(e => {
this.error(e)

View File

@ -3,41 +3,41 @@
<card :padding="false" class="has-overflow">
<div class="gantt-options p-4">
<fancycheckbox class="is-block" v-model="showTaskswithoutDates">
Show tasks which don't have dates set
{{ $t('list.gantt.showTasksWithoutDates') }}
</fancycheckbox>
<div class="range-picker">
<div class="field">
<label class="label" for="dayWidth">Size</label>
<label class="label" for="dayWidth">{{ $t('list.gantt.size') }}</label>
<div class="control">
<div class="select">
<select id="dayWidth" v-model.number="dayWidth">
<option value="35">Default</option>
<option value="10">Month</option>
<option value="80">Day</option>
<option value="35">{{ $t('list.gantt.default') }}</option>
<option value="10">{{ $t('list.gantt.month') }}</option>
<option value="80">{{ $t('list.gantt.day') }}</option>
</select>
</div>
</div>
</div>
<div class="field">
<label class="label" for="fromDate">From</label>
<label class="label" for="fromDate">{{ $t('list.gantt.from') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
class="input"
id="fromDate"
placeholder="From"
:placeholder="$t('list.gantt.from')"
v-model="dateFrom"
/>
</div>
</div>
<div class="field">
<label class="label" for="toDate">To</label>
<label class="label" for="toDate">{{ $t('list.gantt.to') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
class="input"
id="toDate"
placeholder="To"
:placeholder="$t('list.gantt.to')"
v-model="dateTo"
/>
</div>
@ -66,7 +66,6 @@ import GanttChart from '../../../components/tasks/gantt-component'
import flatPickr from 'vue-flatpickr-component'
import Fancycheckbox from '../../../components/input/fancycheckbox'
import {saveListView} from '@/helpers/saveListView'
import {mapState} from 'vuex'
export default {
name: 'Gantt',
@ -88,17 +87,19 @@ export default {
dateTo: null,
}
},
computed: mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y',
altInput: true,
dateFormat: 'Y-m-d',
enableTime: false,
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
},
})
}),
computed: {
flatPickerConfig() {
return {
altFormat: this.$t('date.altFormatShort'),
altInput: true,
dateFormat: 'Y-m-d',
enableTime: false,
locale: {
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
},
}
},
},
beforeMount() {
this.dateFrom = new Date((new Date()).setDate((new Date()).getDate() - 15))
this.dateTo = new Date((new Date()).setDate((new Date()).getDate() + 30))

View File

@ -7,7 +7,7 @@
icon="filter"
type="secondary"
>
Filters
{{ $t('filters.title') }}
</x-button>
</div>
<filter-popup
@ -22,7 +22,7 @@
<span
v-if="bucket.isDoneBucket"
class="icon is-small has-text-success mr-2"
v-tooltip="'All tasks moved into this bucket will automatically marked as done.'"
v-tooltip="$t('list.kanban.doneBucketHint')"
>
<icon icon="check-double"/>
</span>
@ -71,26 +71,26 @@
</div>
</div>
<template v-else>
Limit: {{ bucket.limit > 0 ? bucket.limit : 'Not set' }}
{{ $t('list.kanban.limit', {limit: bucket.limit > 0 ? bucket.limit : $t('list.kanban.noLimit') }) }}
</template>
</a>
<a
@click="toggleDoneBucket(bucket)"
class="dropdown-item"
v-tooltip="'All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.'"
v-tooltip="$t('list.kanban.doneBucketHintExtended')"
>
<span class="icon is-small" :class="{'has-text-success': bucket.isDoneBucket}"><icon
icon="check-double"/></span>
Done bucket
{{ $t('list.kanban.doneBucket') }}
</a>
<a
:class="{'is-disabled': buckets.length <= 1}"
@click="() => deleteBucketModal(bucket.id)"
class="dropdown-item has-text-danger"
v-tooltip="buckets.length <= 1 ? 'You cannot remove the last bucket.' : ''"
v-tooltip="buckets.length <= 1 ? $t('list.kanban.deleteLast') : ''"
>
<span class="icon is-small"><icon icon="trash-alt"/></span>
Delete
{{ $t('misc.delete') }}
</a>
</dropdown>
</div>
@ -192,14 +192,14 @@
@focusout="toggleShowNewTaskInput(bucket.id)"
@keyup.enter="addTaskToBucket(bucket.id)"
@keyup.esc="toggleShowNewTaskInput(bucket.id)"
placeholder="Enter the new task text..."
:placeholder="$t('list.kanban.addTaskPlaceholder')"
type="text"
v-focus.always
v-model="newTaskText"
/>
</div>
<p class="help is-danger" v-if="newTaskError[bucket.id] && newTaskText === ''">
Please specify a title.
{{ $t('list.list.addTitleRequired') }}
</p>
</div>
<x-button
@ -210,12 +210,7 @@
icon="plus"
type="secondary"
>
<template v-if="bucket.tasks.length === 0">
Add a task
</template>
<template v-else>
Add another task
</template>
{{ bucket.tasks.length === 0 ? $t('list.kanban.addTask') : $t('list.kanban.addAnotherTask') }}
</x-button>
</div>
</div>
@ -228,7 +223,7 @@
@keyup.enter="createNewBucket"
@keyup.esc="() => showNewBucketInput = false"
class="input"
placeholder="Enter the new bucket title..."
:placeholder="$t('list.kanban.addBucketPlaceholder')"
type="text"
v-focus.always
v-if="showNewBucketInput"
@ -242,7 +237,7 @@
type="secondary"
icon="plus"
>
Create a new bucket
{{ $t('list.kanban.addBucket') }}
</x-button>
</div>
</div>
@ -257,10 +252,10 @@
@close="showBucketDeleteModal = false"
@submit="deleteBucket()"
v-if="showBucketDeleteModal">
<span slot="header">Delete the bucket</span>
<span slot="header">{{ $t('list.kanban.deleteHeaderBucket') }}</span>
<p slot="text">
Are you sure you want to delete this bucket?<br/>
This will not delete any tasks but move them into the default bucket.
{{ $t('list.kanban.deleteBucketText1') }}<br/>
{{ $t('list.kanban.deleteBucketText2') }}
</p>
</modal>
</transition>
@ -560,7 +555,7 @@ export default {
this.$store.dispatch('kanban/deleteBucket', {bucket: bucket, params: this.params})
.then(() => {
this.success({message: 'The bucket has been deleted successfully.'})
this.success({message: this.$t('list.kanban.deleteBucketSuccess')})
})
.catch(e => {
this.error(e)
@ -590,7 +585,7 @@ export default {
.then(r => {
realBucket.title = r.title
bucketTitleElement.blur()
this.success({message: 'The bucket title has been saved successfully.'})
this.success({message: this.$t('list.kanban.bucketTitleSavedSuccess')})
})
.catch(e => {
this.error(e)
@ -600,7 +595,7 @@ export default {
bucket.limit = parseInt(bucket.limit)
this.$store.dispatch('kanban/updateBucket', bucket)
.then(() => {
this.success({message: 'The bucket limit been saved successfully.'})
this.success({message: this.$t('list.kanban.bucketLimitSavedSuccess')})
})
.catch(e => {
this.error(e)
@ -622,7 +617,7 @@ export default {
bucket.isDoneBucket = !bucket.isDoneBucket
this.$store.dispatch('kanban/updateBucket', bucket)
.then(() => {
this.success({message: 'The done bucket has been saved successfully.'})
this.success({message: this.$t('list.kanban.doneBucketSavedSuccess')})
})
.catch(e => {
this.error(e)

View File

@ -11,7 +11,7 @@
@blur="hideSearchBar()"
@keyup.enter="searchTasks"
class="input"
placeholder="Search"
:placeholder="$t('misc.search')"
type="text"
v-focus
v-model="searchTerm"/>
@ -25,7 +25,7 @@
@click="searchTasks"
:shadow="false"
>
Search
{{ $t('misc.search') }}
</x-button>
</div>
</div>
@ -41,7 +41,7 @@
type="secondary"
icon="filter"
>
Filters
{{ $t('filters.title') }}
</x-button>
</div>
<filter-popup
@ -59,7 +59,7 @@
:class="{ 'disabled': taskService.loading}"
@keyup.enter="addTask()"
class="input"
placeholder="Add a new task..."
:placeholder="$t('list.list.addPlaceholder')"
type="text"
v-focus
v-model="newTaskText"
@ -75,18 +75,20 @@
@click="addTask()"
icon="plus"
>
Add
{{ $t('list.list.add') }}
</x-button>
</p>
</div>
<p class="help is-danger" v-if="showError && newTaskText === ''">
Please specify a list title.
{{ $t('list.list.addTitleRequired') }}
</p>
</div>
<nothing v-if="ctaVisible && tasks.length === 0 && !taskCollectionService.loading">
This list is currently empty.
<a @click="$refs.newTaskInput.focus()">Create a new task.</a>
{{ $t('list.list.empty') }}
<a @click="$refs.newTaskInput.focus()">
{{ $t('list.list.newTaskCta') }}
</a>
</nothing>
<div class="tasks-container">
@ -107,7 +109,7 @@
</div>
<card
v-if="isTaskEdit"
class="taskedit mt-0" title="Edit Task" :has-close="true" @close="() => isTaskEdit = false"
class="taskedit mt-0" :title="$t('list.list.editTask')" :has-close="true" @close="() => isTaskEdit = false"
:shadow="false">
<edit-task :task="taskEditTask"/>
</card>
@ -123,14 +125,14 @@
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous"
tag="button">
Previous
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === taskCollectionService.totalPages"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next"
tag="button">
Next page
{{ $t('misc.next') }}
</router-link>
<ul class="pagination-list">
<template v-for="(p, i) in pages">

View File

@ -7,31 +7,31 @@
icon="th"
type="secondary"
>
Columns
{{ $t('list.table.columns') }}
</x-button>
<x-button
@click.prevent.stop="() => {showTaskFilter = !showTaskFilter; showActiveColumnsFilter = false}"
icon="filter"
type="secondary"
>
Filters
{{ $t('filters.title') }}
</x-button>
</div>
<transition name="fade">
<card v-if="showActiveColumnsFilter">
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.id">#</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.done">Done</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.title">Title</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.priority">Priority</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.labels">Labels</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.assignees">Assignees</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.dueDate">Due Date</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.startDate">Start Date</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.endDate">End Date</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.percentDone">% Done</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.created">Created</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.updated">Updated</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">Created By</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.done">{{ $t('task.attributes.done') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.title">{{ $t('task.attributes.title') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.priority">{{ $t('task.attributes.priority') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.labels">{{ $t('task.attributes.labels') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.assignees">{{ $t('task.attributes.assignees') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.dueDate">{{ $t('task.attributes.dueDate') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.startDate">{{ $t('task.attributes.startDate') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.endDate">{{ $t('task.attributes.endDate') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.percentDone">{{ $t('task.attributes.percentDone') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.created">{{ $t('task.attributes.created') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.updated">{{ $t('task.attributes.updated') }}</fancycheckbox>
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">{{ $t('task.attributes.createdBy') }}</fancycheckbox>
</card>
</transition>
<filter-popup
@ -50,49 +50,49 @@
<sort :order="sortBy.id" @click="sort('id')"/>
</th>
<th v-if="activeColumns.done">
Done
{{ $t('task.attributes.done') }}
<sort :order="sortBy.done" @click="sort('done')"/>
</th>
<th v-if="activeColumns.title">
Name
{{ $t('task.attributes.title') }}
<sort :order="sortBy.title" @click="sort('title')"/>
</th>
<th v-if="activeColumns.priority">
Priority
{{ $t('task.attributes.priority') }}
<sort :order="sortBy.priority" @click="sort('priority')"/>
</th>
<th v-if="activeColumns.labels">
Labels
{{ $t('task.attributes.labels') }}
</th>
<th v-if="activeColumns.assignees">
Assignees
{{ $t('task.attributes.assignees') }}
</th>
<th v-if="activeColumns.dueDate">
Due&nbsp;Date
{{ $t('task.attributes.dueDate') }}
<sort :order="sortBy.due_date" @click="sort('due_date')"/>
</th>
<th v-if="activeColumns.startDate">
Start&nbsp;Date
{{ $t('task.attributes.startDate') }}
<sort :order="sortBy.start_date" @click="sort('start_date')"/>
</th>
<th v-if="activeColumns.endDate">
End&nbsp;Date
{{ $t('task.attributes.endDate') }}
<sort :order="sortBy.end_date" @click="sort('end_date')"/>
</th>
<th v-if="activeColumns.percentDone">
%&nbsp;Done
{{ $t('task.attributes.percentDone') }}
<sort :order="sortBy.percent_done" @click="sort('percent_done')"/>
</th>
<th v-if="activeColumns.created">
Created
{{ $t('task.attributes.created') }}
<sort :order="sortBy.created" @click="sort('created')"/>
</th>
<th v-if="activeColumns.updated">
Updated
{{ $t('task.attributes.updated') }}
<sort :order="sortBy.updated" @click="sort('updated')"/>
</th>
<th v-if="activeColumns.createdBy">
Created&nbsp;By
{{ $t('task.attributes.createdBy') }}
</th>
</tr>
</thead>
@ -156,14 +156,14 @@
:to="getRouteForPagination(currentPage - 1, 'table')"
class="pagination-previous"
tag="button">
Previous
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === taskCollectionService.totalPages"
:to="getRouteForPagination(currentPage + 1, 'table')"
class="pagination-next"
tag="button">
Next page
{{ $t('mist.next') }}
</router-link>
<ul class="pagination-list">
<template v-for="(p, i) in pages">

View File

@ -1,7 +1,7 @@
<template>
<div class="content">
<h1>Import your data from other services to Vikunja</h1>
<p>Click on the logo of one of the third-party services below to get started.</p>
<h1>{{ $t('migrate.title') }}</h1>
<p>{{ $t('migrate.description') }}</p>
<div class="migration-services-overview">
<router-link :key="m" :to="{name: 'migrate.service', params: {service: m}}" v-for="m in availableMigrators">
<img :alt="m" :src="`/images/migration/${m}.png`"/>
@ -15,7 +15,7 @@
export default {
name: 'migrate.service',
mounted() {
this.setTitle('Import your data to Vikunja')
this.setTitle(this.$t('migrate.title'))
},
computed: {
availableMigrators() {

View File

@ -21,7 +21,7 @@ export default {
}
},
mounted() {
this.setTitle(`Import your data from ${this.name} into Vikunja`)
this.setTitle(this.$t('migrate.titleService', {name: this.name}))
},
created() {
switch (this.$route.params.service) {

View File

@ -1,20 +1,20 @@
<template>
<div class="content namespaces-list loader-container" :class="{'is-loading': loading}">
<x-button :to="{name: 'namespace.create'}" class="new-namespace" icon="plus">
Create namespace
{{ $t('namespace.create.title') }}
</x-button>
<x-button :to="{name: 'filters.create'}" class="new-namespace" icon="filter">
Create saved filter
{{ $t('filters.create.title') }}
</x-button>
<fancycheckbox class="show-archived-check" v-model="showArchived" @change="saveShowArchivedState">
Show Archived
{{ $t('namespace.showArchived') }}
</fancycheckbox>
<p class="has-text-centered has-text-grey mt-4 is-italic" v-if="namespaces.length === 0">
You don't have any namespaces right now.
{{ $t('namespace.noneAvailable') }}
<router-link :to="{name: 'namespace.create'}">
Create a namespace.
{{ $t('namespace.create.title') }}.
</router-link>
</p>
@ -26,7 +26,7 @@
v-if="n.id > 0 && n.lists.length > 0"
icon="plus"
>
Create list
{{ $t('list.create.header') }}
</x-button>
<x-button
:to="{name: 'namespace.settings.archive', params: {id: n.id}}"
@ -35,20 +35,20 @@
v-if="n.isArchived"
icon="archive"
>
Un-Archive
{{ $t('namespace.unarchive') }}
</x-button>
<h1>
<span>{{ n.title }}</span>
<span class="is-archived" v-if="n.isArchived">
Archived
{{ $t('namespace.archived') }}
</span>
</h1>
<p class="has-text-centered has-text-grey mt-4 is-italic" v-if="n.lists.length === 0">
This namespace does not contain any lists.
{{ $t('namespaces.noLists') }}
<router-link :to="{name: 'list.create', params: {id: n.id}}">
Create a new list in this namespace.
{{ $t('namespace.createList') }}
</router-link>
</p>
@ -71,7 +71,7 @@
>
<div class="is-archived-container">
<span class="is-archived" v-if="l.isArchived">
Archived
{{ $t('namespace.archived') }}
</span>
<span
:class="{'is-favorite': l.isFavorite, 'is-archived': l.isArchived}"
@ -112,7 +112,7 @@ export default {
this.loadBackgroundsForLists()
},
mounted() {
this.setTitle('Namespaces & Lists')
this.setTitle(this.$t('namespace.title'))
},
computed: mapState({
namespaces(state) {

View File

@ -1,11 +1,11 @@
<template>
<create-edit
title="Create a new namespace"
:title="$t('namespace.create.title')"
@create="newNamespace()"
:create-disabled="namespace.title === ''"
>
<div class="field">
<label class="label" for="namespaceTitle">Namespace Title</label>
<label class="label" for="namespaceTitle">{{ $t('namespace.attributes.title') }}</label>
<div
class="control is-expanded"
:class="{ 'is-loading': namespaceService.loading }"
@ -14,7 +14,7 @@
@keyup.enter="newNamespace()"
@keyup.esc="back()"
class="input"
placeholder="The namespace's name goes here..."
:placeholder="$t('namespace.attributes.titlePlaceholder')"
type="text"
:class="{ disabled: namespaceService.loading }"
v-focus
@ -23,21 +23,19 @@
</div>
</div>
<p class="help is-danger" v-if="showError && namespace.title === ''">
Please specify a title.
{{ $t('namespace.create.titleRequired') }}
</p>
<div class="field">
<label class="label">Color</label>
<label class="label">{{ $t('namespace.attributes.color') }}</label>
<div class="control">
<color-picker v-model="namespace.hexColor" />
</div>
</div>
<p
class="is-small has-text-centered"
v-tooltip.bottom="
'A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namepace.'
"
v-tooltip.bottom="$t('namespace.create.explanation')"
>
What's a namespace?
{{ $t('namespace.create.tooltip') }}
</p>
</create-edit>
</template>
@ -66,7 +64,7 @@ export default {
this.namespaceService = new NamespaceService()
},
mounted() {
this.setTitle('Create a new namespace')
this.setTitle(this.$t('namespace.create.title'))
},
methods: {
newNamespace() {
@ -80,7 +78,7 @@ export default {
.create(this.namespace)
.then((r) => {
this.$store.commit('namespaces/addNamespace', r)
this.success({message: 'The namespace was successfully created.'})
this.success({message: this.$t('namespace.create.success') })
this.$router.back()
})
.catch((e) => {

View File

@ -3,13 +3,12 @@
@close="$router.back()"
@submit="archiveNamespace()"
>
<span slot="header">{{ namespace.isArchived ? 'Un-' : '' }}Archive this namespace</span>
<span slot="header">{{ title }}</span>
<p slot="text" v-if="namespace.isArchived">
You will be able to create new lists or edit it.
{{ $t('namespace.archive.unarchiveText') }}
</p>
<p slot="text" v-else>
You won't be able to edit this namespace or create new list until you un-archive it.<br/>
This will also archive all lists in this namespace.
{{ $t('namespace.archive.archiveText') }}
</p>
</modal>
</template>
@ -23,12 +22,16 @@ export default {
return {
namespaceService: NamespaceService,
namespace: null,
title: ''
}
},
created() {
this.namespaceService = new NamespaceService()
this.namespace = this.$store.getters['namespaces/getNamespaceById'](this.$route.params.id)
this.setTitle(`Archive "${this.namespace.title}"`)
this.title = this.namespace.isArchived ?
this.$t('namespace.archive.titleUnarchive', { namespace: this.namespace.title }) :
this.$t('namespace.archive.titleArchive', { namespace: this.namespace.title })
this.setTitle(this.title)
},
methods: {
archiveNamespace() {
@ -38,7 +41,7 @@ export default {
this.namespaceService.update(this.namespace)
.then(r => {
this.$store.commit('namespaces/setNamespaceById', r)
this.success({message: 'The namespace was successfully archived.'})
this.success({message: this.$t('namespace.archive.success')})
})
.catch(e => {
this.error(e)

View File

@ -3,9 +3,11 @@
@close="$router.back()"
@submit="deleteNamespace()"
>
<span slot="header">Delete this namespace</span>
<p slot="text">Are you sure you want to delete this namespace and all of its contents?
<br/>This includes all tasks and <b>CANNOT BE UNDONE!</b></p>
<span slot="header">{{ title }}</span>
<p slot="text">
{{ $t('namespace.delete.text1') }}<br/>
{{ $t('namespace.delete.text2') }}
</p>
</modal>
</template>
@ -17,13 +19,15 @@ export default {
data() {
return {
namespaceService: NamespaceService,
title: '',
}
},
created() {
this.namespaceService = new NamespaceService()
const namespace = this.$store.getters['namespaces/getNamespaceById'](this.$route.params.id)
this.setTitle(`Delete "${namespace.title}"`)
this.title = this.$t('namespace.delete.title', {namespace: namespace.title})
this.setTitle(this.title)
},
methods: {
deleteNamespace() {
@ -31,7 +35,7 @@ export default {
this.$store.dispatch('namespaces/deleteNamespace', namespace)
.then(() => {
this.success({message: 'The namespace was successfully deleted.'})
this.success({message: this.$t('namespace.delete.success')})
this.$router.push({name: 'home'})
})
.catch(e => {

View File

@ -1,53 +1,53 @@
<template>
<create-edit
title="Edit This Namespace"
:title="title"
primary-icon=""
primary-label="Save"
:primary-label="$t('misc.save')"
@primary="save"
tertary="Delete"
:tertary="$t('misc.delete')"
@tertary="$router.push({ name: 'namespace.settings.delete', params: { id: $route.params.id } })"
>
<form @submit.prevent="save()">
<div class="field">
<label class="label" for="namespacetext">Namespace Name</label>
<label class="label" for="namespacetext">{{ $t('namespace.attributes.title') }}</label>
<div class="control">
<input
:class="{ 'disabled': namespaceService.loading}"
:disabled="namespaceService.loading"
class="input"
id="namespacetext"
placeholder="The namespace text is here..."
:placeholder="$t('namespace.attributes.titlePlaceholder')"
type="text"
v-focus
v-model="namespace.title"/>
</div>
</div>
<div class="field">
<label class="label" for="namespacedescription">Description</label>
<label class="label" for="namespacedescription">{{ $t('namespace.attributes.description') }}</label>
<div class="control">
<editor
:class="{ 'disabled': namespaceService.loading}"
:disabled="namespaceService.loading"
:preview-is-default="false"
id="namespacedescription"
placeholder="The namespaces description goes here..."
:placeholder="$t('namespace.attributes.descriptionPlaceholder')"
v-if="editorActive"
v-model="namespace.description"
/>
</div>
</div>
<div class="field">
<label class="label" for="isArchivedCheck">Is Archived</label>
<label class="label" for="isArchivedCheck">{{ $t('namespace.attributes.archived') }}</label>
<div class="control">
<fancycheckbox
v-model="namespace.isArchived"
v-tooltip="'If a namespace is archived, you cannot create new lists or edit it.'">
This namespace is archived
v-tooltip="$t('namespace.archive.description')">
{{ $t('namespace.attributes.isArchived') }}
</fancycheckbox>
</div>
</div>
<div class="field">
<label class="label">Color</label>
<label class="label">{{ $t('namespace.attributes.color') }}</label>
<div class="control">
<color-picker v-model="namespace.hexColor"/>
</div>
@ -70,9 +70,9 @@ export default {
data() {
return {
namespaceService: NamespaceService,
namespace: NamespaceModel,
editorActive: false,
title: '',
}
},
components: {
@ -115,7 +115,8 @@ export default {
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
this.manageTeamsComponent = 'manageSharing'
this.manageUsersComponent = 'manageSharing'
this.setTitle(`Edit "${r.title}"`)
this.title = this.$t('namespace.edit.title', {namespace: r.title})
this.setTitle(this.title)
})
.catch(e => {
this.error(e)
@ -126,7 +127,7 @@ export default {
.then(r => {
// Update the namespace in the parent
this.$store.commit('namespaces/setNamespaceById', r)
this.success({message: 'The namespace was successfully updated.'})
this.success({message: this.$t('namespace.edit.success')})
this.$router.back()
})
.catch(e => {

View File

@ -1,6 +1,6 @@
<template>
<create-edit
title="Share this Namespace"
:title="title"
primary-label=""
>
<component
@ -30,10 +30,10 @@ export default {
data() {
return {
namespaceService: NamespaceService,
namespace: NamespaceModel,
manageUsersComponent: '',
manageTeamsComponent: '',
namespace: NamespaceModel,
title: '',
}
},
components: {
@ -66,7 +66,8 @@ export default {
// This will trigger the dynamic loading of components once we actually have all the data to pass to them
this.manageTeamsComponent = 'manageSharing'
this.manageUsersComponent = 'manageSharing'
this.setTitle(`Share "${this.namespace.title}"`)
this.title = this.$t('namespace.share.title', { namespace: this.namespace.title })
this.setTitle(this.title)
})
.catch(e => {
this.error(e)

View File

@ -1,11 +1,11 @@
<template>
<div>
<div class="notification is-info is-light has-text-centered" v-if="loading">
Authenticating...
{{ $t('sharing.authenticating') }}
</div>
<div v-if="authenticateWithPassword" class="box">
<p class="pb-2">
This shared list requires a password. Please enter it below:
{{ $t('sharing.passwordRequired') }}
</p>
<div class="field">
<div class="control">
@ -13,7 +13,7 @@
id="linkSharePassword"
type="password"
class="input"
placeholder="e.g. ••••••••••••"
:placeholder="$t('user.auth.passwordPlaceholder')"
v-model="password"
v-focus
@keyup.enter.prevent="auth"
@ -22,7 +22,7 @@
</div>
<x-button @click="auth" :loading="loading">
Login
{{ $t('user.auth.login') }}
</x-button>
<div class="notification is-danger mt-4" v-if="error !== ''">
@ -52,7 +52,7 @@ export default {
this.auth()
},
mounted() {
this.setTitle('Authenticating...')
this.setTitle(this.$t('sharing.authenticating'))
},
computed: mapState({
authLinkShare: state => state.auth.authenticated && (state.auth.info && state.auth.info.type === authTypes.LINK_SHARE),
@ -77,12 +77,13 @@ export default {
return
}
let error = 'An error occured.'
// TODO: Put this logic in a global error handler method which checks all auth codes
let error = this.$t('sharing.error')
if (e.response && e.response.data && e.response.data.message) {
error = e.response.data.message
}
if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13002) {
error = 'The password is invalid.'
error = this.$t('sharing.invalidPassword')
}
this.error = error
})

View File

@ -6,11 +6,13 @@
v-if="!showAll"
v-model="showNulls"
>
Show tasks without dates
{{ $t('task.show.noDates') }}
</fancycheckbox>
<h3 v-if="showAll && tasks.length > 0">Current tasks</h3>
<h3 v-if="showAll && tasks.length > 0">
{{ $t('task.show.current') }}
</h3>
<h3 v-else-if="!showAll" class="mb-2">
Tasks from
{{ $t('task.show.from') }}
<flat-pickr
:class="{ 'disabled': taskService.loading}"
:config="flatPickerConfig"
@ -19,7 +21,7 @@
class="input"
v-model="cStartDate"
/>
until
{{ $t('task.show.until') }}
<flat-pickr
:class="{ 'disabled': taskService.loading}"
:config="flatPickerConfig"
@ -30,12 +32,12 @@
/>
</h3>
<div v-if="!showAll" class="mb-4">
<x-button type="secondary" @click="showTodaysTasks()" class="mr-2">Today</x-button>
<x-button type="secondary" @click="setDatesToNextWeek()" class="mr-2">Next Week</x-button>
<x-button type="secondary" @click="setDatesToNextMonth()">Next Month</x-button>
<x-button type="secondary" @click="showTodaysTasks()" class="mr-2">{{ $t('task.show.today') }}</x-button>
<x-button type="secondary" @click="setDatesToNextWeek()" class="mr-2">{{ $t('task.show.nextWeek') }}</x-button>
<x-button type="secondary" @click="setDatesToNextMonth()">{{ $t('task.show.nextMonth') }}</x-button>
</div>
<template v-if="!taskService.loading && (!tasks || tasks.length === 0) && showNothingToDo">
<h3 class="nothing">Nothing to do - Have a nice day!</h3>
<h3 class="nothing">{{ $t('task.show.noTasks') }}</h3>
<img alt="" src="/images/cool.svg"/>
</template>
<div :class="{ 'is-loading': taskService.loading}" class="spinner"></div>
@ -106,19 +108,23 @@ export default {
this.cEndDate = newVal
},
},
computed: mapState({
userAuthenticated: state => state.auth.authenticated,
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
},
})
}),
computed: {
flatPickerConfig() {
return {
altFormat: this.$t('date.altFormatLong'),
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
locale: {
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
},
}
},
...mapState({
userAuthenticated: state => state.auth.authenticated,
}),
},
methods: {
setDate() {
this.$router.push({
@ -151,9 +157,9 @@ export default {
this.showNulls = this.$route.query.showNulls
if (this.showAll) {
this.setTitle('Current Tasks')
this.setTitle(this.$t('task.show.titleCurrent'))
} else {
this.setTitle(`Tasks from ${this.cStartDate.toLocaleDateString()} until ${this.cEndDate.toLocaleDateString()}`)
this.setTitle(this.$t('task.show.titleDates', { from: this.cStartDate.toLocaleDateString(), to: this.cEndDate.toLocaleDateString()}))
}
const params = {

View File

@ -18,7 +18,7 @@
<!-- Assignees -->
<div class="detail-title">
<icon icon="users"/>
Assignees
{{ $t('task.attributes.assignees') }}
</div>
<edit-assignees
:disabled="!canWrite"
@ -33,7 +33,7 @@
<!-- Priority -->
<div class="detail-title">
<icon :icon="['far', 'star']"/>
Priority
{{ $t('task.attributes.priority') }}
</div>
<priority-select
:disabled="!canWrite"
@ -47,13 +47,13 @@
<!-- Due Date -->
<div class="detail-title">
<icon icon="calendar"/>
Due Date
{{ $t('task.attributes.dueDate') }}
</div>
<div class="date-input">
<datepicker
v-model="task.dueDate"
@close-on-change="() => saveTask()"
choose-date-label="Click here to set a due date"
:choose-date-label="$t('task.detail.chooseDueDate')"
:disabled="taskService.loading || !canWrite"
ref="dueDate"
/>
@ -73,7 +73,7 @@
<!-- Percent Done -->
<div class="detail-title">
<icon icon="percent"/>
Percent Done
{{ $t('task.attributes.percentDone') }}
</div>
<percent-done-select
:disabled="!canWrite"
@ -87,13 +87,13 @@
<!-- Start Date -->
<div class="detail-title">
<icon icon="calendar-week"/>
Start Date
{{ $t('task.attributes.startDate') }}
</div>
<div class="date-input">
<datepicker
v-model="task.startDate"
@close-on-change="() => saveTask()"
choose-date-label="Click here to set a start date"
:choose-date-label="$t('task.detail.chooseStartDate')"
:disabled="taskService.loading || !canWrite"
ref="startDate"
/>
@ -114,13 +114,13 @@
<!-- End Date -->
<div class="detail-title">
<icon icon="calendar-week"/>
End Date
{{ $t('task.attributes.endDate') }}
</div>
<div class="date-input">
<datepicker
v-model="task.endDate"
@close-on-change="() => saveTask()"
choose-date-label="Click here to set an end date"
:choose-date-label="$t('task.detail.chooseEndDate')"
:disabled="taskService.loading || !canWrite"
ref="endDate"
/>
@ -140,7 +140,7 @@
<!-- Reminders -->
<div class="detail-title">
<icon icon="history"/>
Reminders
{{ $t('task.attributes.reminders') }}
</div>
<reminders
:disabled="!canWrite"
@ -154,7 +154,7 @@
<!-- Repeat after -->
<div class="detail-title">
<icon :icon="['far', 'clock']"/>
Repeat
{{ $t('task.attributes.repeat') }}
</div>
<repeat-after
:disabled="!canWrite"
@ -168,7 +168,7 @@
<!-- Color -->
<div class="detail-title">
<icon icon="fill-drip"/>
Color
{{ $t('task.attributes.color') }}
</div>
<color-picker
@change="saveTask"
@ -185,7 +185,7 @@
<span class="icon is-grey">
<icon icon="tags"/>
</span>
Labels
{{ $t('task.attributes.labels') }}
</div>
<edit-labels :disabled="!canWrite" :task-id="taskId" ref="labels" v-model="task.labels"/>
</div>
@ -214,7 +214,7 @@
<span class="icon is-grey">
<icon icon="tasks"/>
</span>
Related Tasks
{{ $t('task.attributes.relatedTasks') }}
</h3>
<related-tasks
:edit-enabled="canWrite"
@ -232,7 +232,7 @@
<span class="icon is-grey">
<icon icon="list"/>
</span>
Move task to a different list
{{ $t('task.detail.move') }}
</h3>
<div class="field has-addons">
<div class="control is-expanded">
@ -253,7 +253,7 @@
icon="check-double"
type="secondary"
>
{{ task.done ? 'Mark as undone' : 'Done!' }}
{{ task.done ? $t('task.detail.undone') : $t('task.detail.done') }}
</x-button>
<task-subscription
entity="task"
@ -267,7 +267,7 @@
type="secondary"
v-shortkey="['a']">
<span class="icon is-small"><icon icon="users"/></span>
Assign this task to a user
{{ $t('task.detail.actions.assign') }}
</x-button>
<x-button
@click="setFieldActive('labels')"
@ -276,14 +276,14 @@
v-shortkey="['l']"
icon="tags"
>
Add labels
{{ $t('task.detail.actions.label') }}
</x-button>
<x-button
@click="setFieldActive('priority')"
type="secondary"
:icon="['far', 'star']"
>
Set Priority
{{ $t('task.detail.actions.priority') }}
</x-button>
<x-button
@click="setFieldActive('dueDate')"
@ -292,42 +292,42 @@
v-shortkey="['d']"
icon="calendar"
>
Set Due Date
{{ $t('task.detail.actions.dueDate') }}
</x-button>
<x-button
@click="setFieldActive('startDate')"
type="secondary"
icon="calendar-week"
>
Set a Start Date
{{ $t('task.detail.actions.startDate') }}
</x-button>
<x-button
@click="setFieldActive('endDate')"
type="secondary"
icon="calendar-week"
>
Set an End Date
{{ $t('task.detail.actions.endDate') }}
</x-button>
<x-button
@click="setFieldActive('reminders')"
type="secondary"
icon="history"
>
Set Reminders
{{ $t('task.detail.actions.reminders') }}
</x-button>
<x-button
@click="setFieldActive('repeatAfter')"
type="secondary"
:icon="['far', 'clock']"
>
Set a repeating interval
{{ $t('task.detail.actions.repeatAfter') }}
</x-button>
<x-button
@click="setFieldActive('percentDone')"
type="secondary"
icon="percent"
>
Set Percent Done
{{ $t('task.detail.actions.percentDone') }}
</x-button>
<x-button
@click="setFieldActive('attachments')"
@ -336,7 +336,7 @@
v-shortkey="['f']"
icon="paperclip"
>
Add attachments
{{ $t('task.detail.actions.attachments') }}
</x-button>
<x-button
@click="setFieldActive('relatedTasks')"
@ -345,21 +345,21 @@
v-shortkey="['r']"
icon="tasks"
>
Add task relations
{{ $t('task.detail.actions.relatedTasks') }}
</x-button>
<x-button
@click="setFieldActive('moveList')"
type="secondary"
icon="list"
>
Move task
{{ $t('task.detail.actions.moveList') }}
</x-button>
<x-button
@click="setFieldActive('color')"
type="secondary"
icon="fill-drip"
>
Set task color
{{ $t('task.detail.actions.color') }}
</x-button>
<x-button
@click="showDeleteModal = true"
@ -367,21 +367,27 @@
:shadow="false"
class="is-danger is-outlined has-no-border"
>
Delete task
{{ $t('task.detail.actions.delete') }}
</x-button>
<!-- Created / Updated [by] -->
<p class="created">
Created <span v-tooltip="formatDate(task.created)">{{ formatDateSince(task.created) }}</span>
by {{ task.createdBy.getDisplayName() }}
<i18n path="task.detail.created">
<span v-tooltip="formatDate(task.created)">{{ formatDateSince(task.created) }}</span>
{{ task.createdBy.getDisplayName() }}
</i18n>
<template v-if="+new Date(task.created) !== +new Date(task.updated)">
<br/>
<!-- Computed properties to show the actual date every time it gets updated -->
Updated <span v-tooltip="updatedFormatted">{{ updatedSince }}</span>
<i18n path="task.detail.updated">
<span v-tooltip="updatedFormatted">{{ updatedSince }}</span>
</i18n>
</template>
<template v-if="task.done">
<br/>
Done <span v-tooltip="doneFormatted">{{ doneSince }}</span>
<i18n path="task.detail.doneAt">
<span v-tooltip="doneFormatted">{{ doneSince }}</span>
</i18n>
</template>
</p>
</div>
@ -393,11 +399,10 @@
@close="showDeleteModal = false"
@submit="deleteTask()"
v-if="showDeleteModal">
<span slot="header">Delete this task</span>
<span slot="header">{{ $t('task.detail.delete.header') }}</span>
<p slot="text">
Are you sure you want to remove this task? <br/>
This will also remove all attachments, reminders and relations associated with this task and
<b>cannot be undone!</b>
{{ $t('task.detail.delete.text1') }}<br/>
{{ $t('task.detail.delete.text2') }}
</p>
</modal>
</transition>
@ -620,7 +625,7 @@ export default {
callback: undoCallback,
}]
}
this.success({message: 'The task was saved successfully.'}, actions)
this.success({message: this.$t('task.detail.updateSuccess')}, actions)
})
.catch(e => {
this.error(e)
@ -648,7 +653,7 @@ export default {
deleteTask() {
this.$store.dispatch('tasks/delete', this.task)
.then(() => {
this.success({message: 'The task has been deleted successfully.'})
this.success({message: this.$t('task.detail.deleteSuccess')})
this.$router.push({name: 'list.index', params: {listId: this.task.listId}})
})
.catch(e => {

View File

@ -3,21 +3,17 @@
class="loader-container is-max-width-desktop"
:class="{ 'is-loading': teamService.loading }"
>
<card class="is-fullwidth" v-if="userIsAdmin" title="Edit Team">
<card class="is-fullwidth" v-if="userIsAdmin" :title="title">
<form @submit.prevent="save()">
<div class="field">
<label class="label" for="teamtext"
>Team Name</label
>
<label class="label" for="teamtext">{{ $t('team.attributes.name') }}</label>
<div class="control">
<input
:class="{
disabled: teamMemberService.loading,
}"
:class="{ disabled: teamMemberService.loading }"
:disabled="teamMemberService.loading"
class="input"
id="teamtext"
placeholder="The team text is here..."
:placeholder="$t('team.attributes.namePlaceholder')"
type="text"
v-focus
v-model="team.name"
@ -28,19 +24,17 @@
class="help is-danger"
v-if="showError && team.name === ''"
>
Please specify a name.
{{ $t('team.attributes.nameRequired') }}
</p>
<div class="field">
<label class="label" for="teamdescription"
>Description</label
>
<label class="label" for="teamdescription">{{ $t('team.attributes.description') }}</label>
<div class="control">
<editor
:class="{ disabled: teamService.loading }"
:disabled="teamService.loading"
:preview-is-default="false"
id="teamdescription"
placeholder="The teams description goes here..."
:placeholder="$t('team.attributes.descriptionPlaceholder')"
v-model="team.description"
/>
</div>
@ -54,7 +48,7 @@
:loading="teamService.loading"
class="is-fullwidth"
>
Save
{{ $t('misc.save') }}
</x-button>
</div>
<div class="control">
@ -68,13 +62,13 @@
</div>
</card>
<card class="is-fullwidth has-overflow" title="Team Members" :padding="false">
<card class="is-fullwidth has-overflow" :title="$t('team.edit.members')" :padding="false">
<div class="p-4" v-if="userIsAdmin">
<div class="field has-addons">
<div class="control is-expanded">
<multiselect
:loading="userService.loading"
placeholder="Type to search..."
:placeholder="$t('team.edit.search')"
@search="findUser"
:search-results="foundUsers"
label="username"
@ -83,7 +77,7 @@
</div>
<div class="control">
<x-button @click="addUser" icon="plus">
Add To Team
{{ $t('team.edit.addUser') }}
</x-button>
</div>
</div>
@ -102,13 +96,13 @@
<span class="icon is-small">
<icon icon="lock"/>
</span>
Admin
{{ $t('team.attributes.admin') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="user"/>
</span>
Member
{{ $t('team.attributes.member') }}
</template>
</td>
<td class="actions" v-if="userIsAdmin">
@ -118,7 +112,7 @@
class="mr-2"
v-if="m.id !== userInfo.id"
>
Make {{ m.admin ? 'Member' : 'Admin' }}
{{ m.admin ? $t('team.edit.makeMember') : $t('team.edit.makeAdmin') }}
</x-button>
<x-button
:loading="teamMemberService.loading"
@ -140,13 +134,10 @@
@submit="deleteTeam()"
v-if="showDeleteModal"
>
<span slot="header">Delete the team</span>
<span slot="header">{{ $t('team.edit.delete.header') }}</span>
<p slot="text">
Are you sure you want to delete this team and all of its
members?<br/>
All team members will loose access to lists and namespaces
shared with this team.<br/>
<b>This CANNOT BE UNDONE!</b>
{{ $t('team.edit.delete.text1') }}<br/>
{{ $t('team.edit.delete.text2') }}
</p>
</modal>
</transition>
@ -157,12 +148,10 @@
@submit="deleteUser()"
v-if="showUserDeleteModal"
>
<span slot="header">Remove a user from the team</span>
<span slot="header">{{ $t('team.edit.deleteUser.header') }}</span>
<p slot="text">
Are you sure you want to remove this user from the team?<br/>
They will loose access to all lists and namespaces this team has
access to.<br/>
<b>This CANNOT BE UNDONE!</b>
{{ $t('team.edit.deleteUser.text1') }}
{{ $t('team.edit.deleteUser.text2') }}
</p>
</modal>
</transition>
@ -170,7 +159,6 @@
</template>
<script>
import router from '../../router'
import {mapState} from 'vuex'
import TeamService from '../../services/team'
@ -204,14 +192,13 @@ export default {
userService: UserService,
showError: false,
title: '',
}
},
components: {
Multiselect,
editor: () => ({
component: import(
/* webpackChunkName: "editor" */ '../../components/input/editor'
),
component: import(/* webpackChunkName: "editor" */ '../../components/input/editor'),
loading: LoadingComponent,
error: ErrorComponent,
timeout: 60000,
@ -246,7 +233,8 @@ export default {
.get(this.team)
.then((response) => {
this.$set(this, 'team', response)
this.setTitle(`Edit Team ${this.team.name}`)
this.title = this.$t('team.edit.title', {team: this.team.name})
this.setTitle(this.title)
})
.catch((e) => {
this.error(e)
@ -263,7 +251,7 @@ export default {
.update(this.team)
.then((response) => {
this.team = response
this.success({message: 'The team was successfully updated.'})
this.success({message: this.$t('team.edit.success')})
})
.catch((e) => {
this.error(e)
@ -273,8 +261,8 @@ export default {
this.teamService
.delete(this.team)
.then(() => {
this.success({message: 'The team was successfully deleted.'})
router.push({name: 'teams.index'})
this.success({message: this.$t('team.edit.delete.success')})
this.$router.push({name: 'teams.index'})
})
.catch((e) => {
this.error(e)
@ -284,10 +272,7 @@ export default {
this.teamMemberService
.delete(this.member)
.then(() => {
this.success({
message:
'The user was successfully deleted from the team.',
})
this.success({message: this.$t('team.edit.deleteUser.success')})
this.loadTeam()
})
.catch((e) => {
@ -306,7 +291,7 @@ export default {
.create(newMember)
.then(() => {
this.loadTeam()
this.success({message: 'The team member was successfully added.'})
this.success({message: this.$t('team.edit.userAddedSuccess')})
})
.catch((e) => {
this.error(e)
@ -325,10 +310,9 @@ export default {
}
}
this.success({
message:
'The team member was successfully made ' +
(member.admin ? 'admin' : 'member') +
'.',
message: member.admin ?
this.$t('team.edit.madeAdmin') :
this.$t('team.edit.madeMember')
})
})
.catch((e) => {

View File

@ -5,10 +5,10 @@
class="is-pulled-right"
icon="plus"
>
New Team
{{ $t('team.create.title') }}
</x-button>
<h1>Teams</h1>
<h1>{{ $t('team.title') }}</h1>
<ul class="teams box" v-if="teams.length > 0">
<li :key="t.id" v-for="t in teams">
<router-link :to="{name: 'teams.edit', params: {id: t.id}}">
@ -17,9 +17,9 @@
</li>
</ul>
<p v-else-if="!teamService.loading" class="has-text-centered has-text-grey is-italic">
You are currently not part of any teams.
{{ $t('team.noTeams') }}
<router-link :to="{name: 'teams.create'}">
Create a new team.
{{ $t('team.create.title') }}.
</router-link>
</p>
</div>
@ -41,7 +41,7 @@ export default {
this.loadTeams()
},
mounted() {
this.setTitle('Teams')
this.setTitle(this.$t('team.title'))
},
methods: {
loadTeams() {

View File

@ -1,11 +1,11 @@
<template>
<create-edit
title="Create a new team"
:title="$t('team.create.title')"
@create="newTeam()"
:create-disabled="team.name === ''"
>
<div class="field">
<label class="label" for="teamName">Team Name</label>
<label class="label" for="teamName">{{ $t('team.attributes.name') }}</label>
<div
class="control is-expanded"
:class="{ 'is-loading': teamService.loading }"
@ -14,7 +14,7 @@
:class="{ 'disabled': teamService.loading }"
class="input"
id="teamName"
placeholder="The team's name goes here..."
:placeholder="$t('team.attributes.namePlaceholder')"
type="text"
v-focus
v-model="team.name"
@ -23,7 +23,7 @@
</div>
</div>
<p class="help is-danger" v-if="showError && team.name === ''">
Please specify a name.
{{ $t('team.attributes.nameRequired') }}
</p>
</create-edit>
</template>
@ -50,7 +50,7 @@ export default {
this.team = new TeamModel()
},
mounted() {
this.setTitle('Create a new Team')
this.setTitle(this.$t('team.create.title'))
},
methods: {
newTeam() {
@ -67,7 +67,7 @@ export default {
name: 'teams.edit',
params: { id: response.id },
})
this.success({message: 'The team was successfully created.'})
this.success({message: this.$t('team.create.success') })
})
.catch((e) => {
this.error(e)

View File

@ -3,17 +3,17 @@
<h2 class="title has-text-centered">Login</h2>
<div class="box">
<div class="notification is-success has-text-centered" v-if="confirmedEmailSuccess">
You successfully confirmed your email! You can log in now.
{{ $t('user.auth.confirmEmailSuccess') }}
</div>
<api-config @foundApi="hasApiUrl = true"/>
<form @submit.prevent="submit" id="loginform" v-if="hasApiUrl && localAuthEnabled">
<div class="field">
<label class="label" for="username">Username Or Email Address</label>
<label class="label" for="username">{{ $t('user.auth.usernameEmail') }}</label>
<div class="control">
<input
class="input" id="username"
name="username"
placeholder="e.g. frederick"
:placeholder="$t('user.auth.usernamePlaceholder')"
ref="username"
required
type="text"
@ -24,13 +24,13 @@
</div>
</div>
<div class="field">
<label class="label" for="password">Password</label>
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password"
name="password"
placeholder="e.g. ••••••••••••"
:placeholder="$t('user.auth.passwordPlaceholder')"
ref="password"
required
type="password"
@ -40,12 +40,12 @@
</div>
</div>
<div class="field" v-if="needsTotpPasscode">
<label class="label" for="totpPasscode">Two Factor Authentication Code</label>
<label class="label" for="totpPasscode">{{ $t('user.auth.totpTitle') }}</label>
<div class="control">
<input
class="input"
id="totpPasscode"
placeholder="e.g. 123456"
:placeholder="$t('user.auth.totpPlaceholder')"
ref="totpPasscode"
required
type="text"
@ -61,19 +61,19 @@
@click="submit"
:loading="loading"
>
Login
{{ $t('user.auth.login') }}
</x-button>
<x-button
:to="{ name: 'user.register' }"
v-if="registrationEnabled"
type="secondary"
>
Register
{{ $t('user.auth.register') }}
</x-button>
</div>
<div class="control">
<router-link :to="{ name: 'user.password-reset.request' }" class="reset-password-link">
Reset your password
{{ $t('user.auth.resetPassword') }}
</router-link>
</div>
</div>
@ -92,7 +92,7 @@
type="secondary"
class="is-fullwidth mt-2"
>
Log in with {{ p.name }}
{{ $t('user.auth.loginWith', {provider: p.name}) }}
</x-button>
</div>
@ -109,6 +109,7 @@ import {HTTPFactory} from '@/http-common'
import {ERROR_MESSAGE, LOADING} from '@/store/mutation-types'
import legal from '../../components/misc/legal'
import ApiConfig from '@/components/misc/api-config'
import {getErrorText} from '@/message'
export default {
components: {
@ -147,7 +148,7 @@ export default {
},
created() {
this.hasApiUrl = window.API_URL !== ''
this.setTitle('Login')
this.setTitle(this.$t('user.auth.login'))
},
computed: mapState({
registrationEnabled: state => state.config.registrationEnabled,
@ -183,7 +184,14 @@ export default {
}
this.$store.dispatch('auth/login', credentials)
.catch(() => {
.catch(e => {
const err = getErrorText(e, p => this.$t(p))
if (typeof err[1] !== 'undefined') {
this.$store.commit(ERROR_MESSAGE, err[1])
return
}
this.$store.commit(ERROR_MESSAGE, err[0])
})
},
redirectToProvider(provider) {

View File

@ -4,7 +4,7 @@
{{ errorMessage }}
</div>
<div class="notification is-info" v-if="loading">
Authenticating...
{{ $t('user.auth.authenticating') }}
</div>
</div>
</template>
@ -13,6 +13,7 @@
import {mapState} from 'vuex'
import {ERROR_MESSAGE, LOADING} from '@/store/mutation-types'
import {getErrorText} from '@/message'
export default {
name: 'Auth',
@ -41,7 +42,7 @@ export default {
const state = localStorage.getItem('state')
if(typeof this.$route.query.state === 'undefined' || this.$route.query.state !== state) {
localStorage.removeItem('authenticating')
this.$store.commit(ERROR_MESSAGE, 'State does not match, refusing to continue!')
this.$store.commit(ERROR_MESSAGE, this.$t('user.auth.openIdStateError'))
return
}
@ -54,8 +55,14 @@ export default {
.then(() => {
this.$router.push({name: 'home'})
})
.catch(() => {
// Handled through global state
.catch(e => {
const err = getErrorText(e, p => this.$t(p))
if (typeof err[1] !== 'undefined') {
this.$store.commit(ERROR_MESSAGE, err[1])
return
}
this.$store.commit(ERROR_MESSAGE, err[0])
})
.finally(() => {
localStorage.removeItem('authenticating')

View File

@ -1,16 +1,16 @@
<template>
<div>
<h2 class="title has-text-centered">Reset your password</h2>
<h2 class="title has-text-centered">{{ $t('user.auth.resetPassword') }}</h2>
<div class="box">
<form @submit.prevent="submit" id="form" v-if="!successMessage">
<div class="field">
<label class="label" for="password1">Password</label>
<label class="label" for="password1">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password1"
name="password1"
placeholder="e.g. ••••••••••••"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
@ -19,13 +19,13 @@
</div>
</div>
<div class="field">
<label class="label" for="password2">Retype your password</label>
<label class="label" for="password2">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control">
<input
class="input"
id="password2"
name="password2"
placeholder="e.g. ••••••••••••"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
@ -39,12 +39,12 @@
:loading="this.passwordResetService.loading"
@click="submit"
>
Reset your password
{{ $t('user.auth.resetPassoword') }}
</x-button>
</div>
</div>
<div class="notification is-info" v-if="this.passwordResetService.loading">
Loading...
{{ $t('misc.loading') }}
</div>
<div class="notification is-danger" v-if="errorMsg">
{{ errorMsg }}
@ -55,7 +55,7 @@
{{ successMessage }}
</div>
<x-button :to="{ name: 'user.login' }">
Login
{{ $t('user.auth.login') }}
</x-button>
</div>
<legal/>
@ -87,14 +87,14 @@ export default {
this.passwordResetService = new PasswordResetService()
},
mounted() {
this.setTitle('Reset your password')
this.setTitle(this.$t('user.auth.resetPassword'))
},
methods: {
submit() {
this.errorMsg = ''
if (this.credentials.password2 !== this.credentials.password) {
this.errorMsg = 'Passwords don\'t match'
this.errorMsg = this.$t('user.auth.passwordsDontMatch')
return
}

View File

@ -1,16 +1,16 @@
<template>
<div>
<h2 class="title has-text-centered">Register</h2>
<h2 class="title has-text-centered">{{ $t('user.auth.register') }}</h2>
<div class="box">
<form @submit.prevent="submit" id="registerform">
<div class="field">
<label class="label" for="username">Username</label>
<label class="label" for="username">{{ $t('user.auth.username') }}</label>
<div class="control">
<input
class="input"
id="username"
name="username"
placeholder="e.g. frederick"
:placeholder="$t('user.auth.usernamePlaceholder')"
required
type="text"
autocomplete="username"
@ -21,13 +21,13 @@
</div>
</div>
<div class="field">
<label class="label" for="email">E-mail address</label>
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
<div class="control">
<input
class="input"
id="email"
name="email"
placeholder="e.g. frederic@vikunja.io"
:placeholder="$t('user.auth.emailPlaceholder')"
required
type="email"
v-model="credentials.email"
@ -36,13 +36,13 @@
</div>
</div>
<div class="field">
<label class="label" for="password1">Password</label>
<label class="label" for="password1">{{ $t('user.auth.password') }}</label>
<div class="control">
<input
class="input"
id="password1"
name="password1"
placeholder="e.g. ••••••••••••"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
@ -52,13 +52,13 @@
</div>
</div>
<div class="field">
<label class="label" for="password2">Retype your password</label>
<label class="label" for="password2">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control">
<input
class="input"
id="password2"
name="password2"
placeholder="e.g. ••••••••••••"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
type="password"
autocomplete="new-password"
@ -76,13 +76,15 @@
@click="submit"
class="mr-2"
>
Register
{{ $t('user.auth.register') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
{{ $t('user.auth.login') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">Login</x-button>
</div>
</div>
<div class="notification is-info" v-if="loading">
Loading...
{{ $t('misc.loading') }}
</div>
<div class="notification is-danger" v-if="errorMessage !== ''">
{{ errorMessage }}
@ -120,7 +122,7 @@ export default {
}
},
mounted() {
this.setTitle('Register')
this.setTitle(this.$t('user.auth.register'))
},
computed: mapState({
authenticated: state => state.auth.authenticated,
@ -133,7 +135,7 @@ export default {
this.$store.commit(ERROR_MESSAGE, '')
if (this.credentials.password2 !== this.credentials.password) {
this.$store.commit(ERROR_MESSAGE, 'Passwords don\'t match.')
this.$store.commit(ERROR_MESSAGE, this.$t('user.auth.passwordsDontMatch'))
this.$store.commit(LOADING, false)
return
}

View File

@ -1,16 +1,16 @@
<template>
<div>
<h2 class="title has-text-centered">Reset your password</h2>
<h2 class="title has-text-centered">{{ $t('user.auth.resetPassword') }}</h2>
<div class="box">
<form @submit.prevent="submit" v-if="!isSuccess">
<div class="field">
<label class="label" for="email">E-mail address</label>
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
<div class="control">
<input
class="input"
id="email"
name="email"
placeholder="e.g. frederic@vikunja.io"
:placeholder="$t('user.auth.emailPlaceholder')"
required
type="email"
v-focus
@ -24,9 +24,11 @@
@click="submit"
:loading="passwordResetService.loading"
>
Send me a password reset link
{{ $t('user.auth.resetPasswordAction') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">
{{ $t('user.auth.login') }}
</x-button>
<x-button :to="{ name: 'user.login' }" type="secondary">Login</x-button>
</div>
</div>
<div class="notification is-danger" v-if="errorMsg">
@ -35,9 +37,11 @@
</form>
<div class="has-text-centered" v-if="isSuccess">
<div class="notification is-success">
Check your inbox! You should have a mail with instructions on how to reset your password.
{{ $t('user.auth.resetPasswordSuccess') }}
</div>
<x-button :to="{ name: 'user.login' }">Login</x-button>
<x-button :to="{ name: 'user.login' }">
{{ $t('user.auth.login') }}
</x-button>
</div>
<legal/>
</div>
@ -66,7 +70,7 @@ export default {
this.passwordReset = new PasswordResetModel()
},
mounted() {
this.setTitle('Reset your password')
this.setTitle(this.$t('user.auth.resetPassword'))
},
methods: {
submit() {

View File

@ -3,15 +3,15 @@
:class="{ 'is-loading': passwordUpdateService.loading || emailUpdateService.loading || totpService.loading }"
class="loader-container is-max-width-desktop">
<!-- General -->
<card title="General Settings" class="general-settings">
<card :title="$t('user.settings.general.title')" class="general-settings">
<div class="field">
<label class="label" for="newName">Name</label>
<label class="label" for="newName">{{ $t('user.settings.general.name') }}</label>
<div class="control">
<input
@keyup.enter="updateSettings"
class="input"
id="newName"
placeholder="The new name"
:placeholder="$t('user.settings.general.newName')"
type="text"
v-model="settings.name"/>
</div>
@ -19,42 +19,54 @@
<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="settings.emailRemindersEnabled"/>
Send me reminders for tasks via Email
{{ $t('user.settings.general.emailReminders') }}
</label>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="settings.overdueTasksRemindersEnabled"/>
Send me reminders for overdue undone tasks via email each morning
{{ $t('user.settings.general.overdueReminders') }}
</label>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="settings.discoverableByName"/>
Let other users find me when they search for my name
{{ $t('user.settings.general.discoverableByName') }}
</label>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="settings.discoverableByEmail"/>
Let other users find me when they search for my full email
{{ $t('user.settings.general.discoverableByEmail') }}
</label>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="playSoundWhenDone"/>
Play a sound when marking tasks as done
{{ $t('user.settings.general.playSoundWhenDone') }}
</label>
</div>
<div class="field">
<label class="is-flex is-align-items-center">
<span>
Week starts on
{{ $t('user.settings.general.weekStart') }}
</span>
<div class="select ml-2">
<select v-model.number="settings.weekStart">
<option value="0">Sunday</option>
<option value="1">Monday</option>
<option value="0">{{ $t('user.settings.general.weekStartSunday') }}</option>
<option value="1">{{ $t('user.settings.general.weekStartMonday') }}</option>
</select>
</div>
</label>
</div>
<div class="field">
<label class="is-flex is-align-items-center">
<span>
{{ $t('user.settings.general.language') }}
</span>
<div class="select ml-2">
<select v-model.number="language">
<option :value="l" v-for="(lang, l) in availableLanguages" :key="l">{{ lang }}</option>
</select>
</div>
</label>
@ -65,7 +77,7 @@
@click="updateSettings()"
class="is-fullwidth mt-4"
>
Save
{{ $t('misc.save') }}
</x-button>
</card>
@ -73,40 +85,40 @@
<avatar-settings/>
<!-- Password update -->
<card title="Update Your Password">
<card :title="$t('user.settings.newPasswordTitle')">
<form @submit.prevent="updatePassword()">
<div class="field">
<label class="label" for="newPassword">New Password</label>
<label class="label" for="newPassword">{{ $t('user.settings.newPassword') }}</label>
<div class="control">
<input
@keyup.enter="updatePassword"
class="input"
id="newPassword"
placeholder="The new password..."
:placeholder="$t('user.auth.passwordPlaceholder')"
type="password"
v-model="passwordUpdate.newPassword"/>
</div>
</div>
<div class="field">
<label class="label" for="newPasswordConfirm">New Password Confirmation</label>
<label class="label" for="newPasswordConfirm">{{ $t('user.settings.newPasswordConfirm') }}</label>
<div class="control">
<input
@keyup.enter="updatePassword"
class="input"
id="newPasswordConfirm"
placeholder="Confirm your new password..."
:placeholder="$t('user.auth.passwordPlaceholder')"
type="password"
v-model="passwordConfirm"/>
</div>
</div>
<div class="field">
<label class="label" for="currentPassword">Current Password</label>
<label class="label" for="currentPassword">{{ $t('user.settings.currentPassword') }}</label>
<div class="control">
<input
@keyup.enter="updatePassword"
class="input"
id="currentPassword"
placeholder="Your current password"
:placeholder="$t('user.settings.currentPasswordPlaceholder')"
type="password"
v-model="passwordUpdate.oldPassword"/>
</div>
@ -117,33 +129,33 @@
:loading="passwordUpdateService.loading"
@click="updatePassword()"
class="is-fullwidth mt-4">
Save
{{ $t('misc.save') }}
</x-button>
</card>
<!-- Update E-Mail -->
<card title="Update Your E-Mail Address">
<card :title="$t('user.settings.updateEmailTitle')">
<form @submit.prevent="updateEmail()">
<div class="field">
<label class="label" for="newEmail">New Email Address</label>
<label class="label" for="newEmail">{{ $t('user.settings.updateEmailNew') }}</label>
<div class="control">
<input
@keyup.enter="updateEmail"
class="input"
id="newEmail"
placeholder="The new email address..."
:placeholder="$t('user.auth.emailPlaceholder')"
type="email"
v-model="emailUpdate.newEmail"/>
</div>
</div>
<div class="field">
<label class="label" for="currentPasswordEmail">Current Password</label>
<label class="label" for="currentPasswordEmail">{{ $t('user.settings.currentPassword') }}</label>
<div class="control">
<input
@keyup.enter="updateEmail"
class="input"
id="currentPasswordEmail"
placeholder="Your current password"
:placeholder="$t('user.settings.currentPasswordPlaceholder')"
type="password"
v-model="emailUpdate.password"/>
</div>
@ -154,82 +166,83 @@
:loading="emailUpdateService.loading"
@click="updateEmail()"
class="is-fullwidth mt-4">
Save
{{ $t('misc.save') }}
</x-button>
</card>
<!-- TOTP -->
<card title="Two Factor Authentication" v-if="totpEnabled">
<card :title="$t('user.settings.totp.title')" v-if="totpEnabled">
<x-button
:loading="totpService.loading"
@click="totpEnroll()"
v-if="!totpEnrolled && totp.secret === ''">
Enroll
{{ $t('user.settings.totp.enroll') }}
</x-button>
<template v-else-if="totp.secret !== '' && !totp.enabled">
<p>
To finish your setup, use this secret in your totp app (Google Authenticator or similar):
{{ $t('user.settings.totp.finishSetupPart1') }}
<strong>{{ totp.secret }}</strong><br/>
After that, enter a code from your app below.
{{ $t('user.settings.totp.finishSetupPart2') }}
</p>
<p>
Alternatively you can scan this QR code:<br/>
{{ $t('user.settings.totp.scanQR') }}<br/>
<img :src="totpQR" alt=""/>
</p>
<div class="field">
<label class="label" for="totpConfirmPasscode">Passcode</label>
<label class="label" for="totpConfirmPasscode">{{ $t('user.settings.totp.passcode') }}</label>
<div class="control">
<input
@keyup.enter="totpConfirm()"
class="input"
id="totpConfirmPasscode"
placeholder="A code generated by your totp application"
:placeholder="$t('user.settings.totp.passcodePlaceholder')"
type="text"
v-model="totpConfirmPasscode"/>
</div>
</div>
<x-button @click="totpConfirm()">Confirm</x-button>
<x-button @click="totpConfirm()">{{ $t('misc.confirm') }}</x-button>
</template>
<template v-else-if="totp.secret !== '' && totp.enabled">
<p>
You've sucessfully set up two factor authentication!
{{ $t('user.settings.totp.setupSuccess') }}
</p>
<p v-if="!totpDisableForm">
<x-button @click="totpDisableForm = true" class="is-danger">Disable</x-button>
<x-button @click="totpDisableForm = true" class="is-danger">{{ $t('misc.disable') }}</x-button>
</p>
<div v-if="totpDisableForm">
<div class="field">
<label class="label" for="currentPassword">Please Enter Your Password</label>
<label class="label" for="currentPassword">{{ $t('user.settings.totp.enterPassword') }}</label>
<div class="control">
<input
@keyup.enter="totpDisable"
class="input"
id="currentPassword"
placeholder="Your current password"
:placeholder="$t('user.settings.currentPasswordPlaceholder')"
type="password"
v-focus
v-model="totpDisablePassword"/>
</div>
</div>
<x-button @click="totpDisable()" class="is-danger">Disable two factor authentication</x-button>
<x-button @click="totpDisable()" class="is-danger">
{{ $t('user.settings.totp.disable') }}
</x-button>
</div>
</template>
</card>
<!-- Migration -->
<card title="Migrate from other services to Vikunja" v-if="migratorsEnabled">
<card :title="$t('migrate.title')" v-if="migratorsEnabled">
<x-button
:to="{name: 'migrate.start'}"
>
Import your data into Vikunja
{{ $t('migrate.import') }}
</x-button>
</card>
<!-- Caldav -->
<card v-if="caldavEnabled" title="Caldav">
<card v-if="caldavEnabled" :title="$t('user.settings.caldav.title')">
<p>
You can connect Vikunja to caldav clients to view and manage all tasks from different clients.
Enter this url into your client:
{{ $t('user.settings.caldav.howTo') }}
</p>
<div class="field has-addons no-input-mobile">
<div class="control is-expanded">
@ -239,14 +252,14 @@
<x-button
@click="copy(caldavUrl)"
:shadow="false"
v-tooltip="'Copy to clipboard'"
v-tooltip="$t('misc.copy')"
icon="paste"
/>
</div>
</div>
<p>
<a href="https://vikunja.io/docs/caldav/" target="_blank">
More information about caldav in Vikunja
{{ $t('user.settings.caldav.more') }}
</a>
</p>
</card>
@ -263,6 +276,7 @@ import TotpService from '../../services/totp'
import UserSettingsService from '../../services/userSettings'
import UserSettingsModel from '../../models/userSettings'
import {playSoundWhenDoneKey} from '@/helpers/playPop'
import {availableLanguages, saveLanguage, getCurrentLanguage} from '@/i18n/setup'
import {mapState} from 'vuex'
@ -288,6 +302,7 @@ export default {
totpDisableForm: false,
totpDisablePassword: '',
playSoundWhenDone: false,
language: getCurrentLanguage(),
settings: UserSettingsModel,
userSettingsService: UserSettingsService,
@ -314,7 +329,7 @@ export default {
this.totpStatus()
},
mounted() {
this.setTitle('Settings')
this.setTitle(this.$t('user.settings.title'))
},
computed: {
caldavUrl() {
@ -328,6 +343,9 @@ export default {
return `${apiBase}/dav/principals/${this.userInfo.username}/`
},
availableLanguages() {
return availableLanguages
},
...mapState({
totpEnabled: state => state.config.totpEnabled,
migratorsEnabled: state => state.config.availableMigrators !== null && state.config.availableMigrators.length > 0,
@ -338,20 +356,20 @@ export default {
methods: {
updatePassword() {
if (this.passwordConfirm !== this.passwordUpdate.newPassword) {
this.error({message: 'The new password and its confirmation don\'t match.'})
this.error({message: this.$t('user.settings.passwordsDontMatch')})
return
}
this.passwordUpdateService.update(this.passwordUpdate)
.then(() => {
this.success({message: 'The password was successfully updated.'})
this.success({message: this.$t('user.settings.passwordUpdateSuccess')})
})
.catch(e => this.error(e))
},
updateEmail() {
this.emailUpdateService.update(this.emailUpdate)
.then(() => {
this.success({message: 'Your email address was successfully updated. We\'ve sent you a link to confirm it.'})
this.success({message: this.$t('user.settings.updateEmailSuccess')})
})
.catch(e => this.error(e))
},
@ -394,7 +412,7 @@ export default {
this.totpService.enable({passcode: this.totpConfirmPasscode})
.then(() => {
this.$set(this.totp, 'enabled', true)
this.success({message: 'You\'ve successfully confirmed your totp setup and can use it from now on!'})
this.success({message: this.$t('user.settings.totp.confirmSuccess')})
})
.catch(e => this.error(e))
},
@ -403,17 +421,18 @@ export default {
.then(() => {
this.totpEnrolled = false
this.$set(this, 'totp', new TotpModel())
this.success({message: 'Two factor authentication was sucessfully disabled.'})
this.success({message: this.$t('user.settings.totp.disableSuccess')})
})
.catch(e => this.error(e))
},
updateSettings() {
localStorage.setItem(playSoundWhenDoneKey, this.playSoundWhenDone)
saveLanguage(this.language)
this.userSettingsService.update(this.settings)
.then(() => {
this.$store.commit('auth/setUserSettings', this.settings)
this.success({message: 'The name was successfully changed.'})
this.success({message: this.$t('user.settings.general.savedSuccess')})
})
.catch(e => this.error(e))
},