Add translations (#562)

Reviewed-on: vikunja/frontend#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

@ -528,3 +528,34 @@ steps:
status:
- success
- failure
---
kind: pipeline
name: ping-weblate
depends_on:
- build
trigger:
branch:
- main
event:
- push
steps:
- name: update-translation-base
image: appleboy/drone-git-push
settings:
branch: translations
remote: ssh://git@kolaente.dev:9022/vikunja/frontend.git
ssh_key:
from_secret: translations_branch_update_ssh_key
- name: notify-weblate
image: curlimages/curl
depends_on:
- update-translation-base
environment:
WEBLATE_TOKEN:
from_secret: weblate_token
commands:
- 'curl -d operation=pull -H "Authorization: Token ${WEBLATE_TOKEN}" https://hosted.weblate.org/api/projects/vikunja/repository/'
- 'curl -d operation=push -H "Authorization: Token ${WEBLATE_TOKEN}" https://hosted.weblate.org/api/projects/vikunja/repository/'

View File

@ -5,6 +5,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/frontend/status.svg)](https://drone.kolaente.de/vikunja/frontend)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.17.0-brightgreen.svg)](https://dl.vikunja.io)
[![Translation](https://hosted.weblate.org/widgets/vikunja/-/frontend/svg-badge.svg)](https://hosted.weblate.org/engage/vikunja/)
This is the web frontend for Vikunja, written in Vue.js.

View File

@ -388,7 +388,7 @@ describe('Lists', () => {
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Limit: Not set')
.contains('Limit: Not Set')
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
.first()

View File

@ -24,7 +24,7 @@ describe('Namepaces', () => {
cy.visit('/namespaces')
cy.get('a.button')
.contains('Create namespace')
.contains('Create a new namespace')
.click()
cy.url()

View File

@ -11,7 +11,7 @@ describe('Team', () => {
const newTeamName = 'New Team'
cy.get('a.button')
.contains('New Team')
.contains('Create a new team')
.click()
cy.url()
.should('contain', '/teams/new')
@ -113,7 +113,7 @@ describe('Team', () => {
cy.get('.card')
.contains('Team Members')
.get('.card-content .button')
.contains('Add To Team')
.contains('Add to team')
.click()
cy.get('table.table td')

View File

@ -27,7 +27,7 @@ describe('Task', () => {
it('Should be created new', () => {
cy.visit('/lists/1/list')
cy.get('input.input[placeholder="Add a new task..."')
cy.get('input.input[placeholder="Add a new task"')
.type('New Task')
cy.get('.button')
.contains('Add')
@ -43,7 +43,7 @@ describe('Task', () => {
cy.visit('/lists/1/list')
cy.get('.list-is-empty-notice')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task..."')
cy.get('input.input[placeholder="Add a new task"')
.type('New Task')
cy.get('.button')
.contains('Add')

View File

@ -30,6 +30,7 @@
"vue-advanced-cropper": "1.7.0",
"vue-drag-resize": "1.5.4",
"vue-easymde": "1.4.0",
"vue-i18n": "^8.24.4",
"vue-shortkey": "3.1.7",
"vue-smooth-dnd": "0.8.1",
"vuex": "3.6.2"

View File

@ -34,6 +34,7 @@ import TopNavigation from '@/components/home/topNavigation'
import ContentAuth from '@/components/home/contentAuth'
import ContentLinkShare from '@/components/home/contentLinkShare'
import ContentNoAuth from '@/components/home/contentNoAuth'
import {setLanguage} from '@/i18n/setup'
export default {
name: 'app',
@ -53,6 +54,8 @@ export default {
beforeCreate() {
this.$store.dispatch('config/update')
this.$store.dispatch('auth/checkAuth')
setLanguage()
},
created() {
// Make sure to always load the home route when running with electron

View File

@ -10,12 +10,12 @@
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? 'Loading...' : currentList.title }}
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
</h1>
<div class="box has-text-left view">
<div class="logout">
<x-button @click="logout()" type="secondary">
<span>Logout</span>
<span>{{ $t('user.auth.logout') }}</span>
<span class="icon is-small">
<icon icon="sign-out-alt"/>
</span>
@ -23,7 +23,7 @@
</div>
<router-view/>
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank">
Powered by Vikunja
{{ $t('misc.poweredBy') }}
</a>
</div>
</div>

View File

@ -4,7 +4,7 @@
<img alt="Vikunja" src="/images/logo-full.svg"/>
<div class="message is-info" v-if="motd !== ''">
<div class="message-header">
<p>Info</p>
<p>{{ $t('misc.info') }}</p>
</div>
<div class="message-body">
{{ motd }}

View File

@ -10,7 +10,7 @@
<span class="icon">
<icon icon="calendar"/>
</span>
Overview
{{ $t('navigation.overview') }}
</router-link>
</li>
<li>
@ -18,7 +18,7 @@
<span class="icon">
<icon :icon="['far', 'calendar-alt']"/>
</span>
Upcoming
{{ $t('navigation.upcoming') }}
</router-link>
</li>
<li>
@ -26,7 +26,7 @@
<span class="icon">
<icon icon="layer-group"/>
</span>
Namespaces & Lists
{{ $t('namespace.title') }}
</router-link>
</li>
<li>
@ -34,7 +34,7 @@
<span class="icon">
<icon icon="tags"/>
</span>
Labels
{{ $t('label.title') }}
</router-link>
</li>
<li>
@ -42,7 +42,7 @@
<span class="icon">
<icon icon="users"/>
</span>
Teams
{{ $t('team.title') }}
</router-link>
</li>
</ul>
@ -109,7 +109,9 @@
</div>
</template>
</aside>
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank">Powered by Vikunja</a>
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank">
{{ $t('misc.poweredBy') }}
</a>
</div>
</template>

View File

@ -25,14 +25,16 @@
>
<icon icon="bars"></icon>
</a>
<div class="list-title" v-if="currentList.id" ref="listTitle">
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? 'Loading...' : currentList.title }}
</h1>
<div class="list-title" ref="listTitle" :style="{'display': currentList.id ? '': 'none'}">
<template v-if="currentList.id">
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
</h1>
<list-settings-dropdown v-if="canWriteCurrentList && currentList.id !== -1" :list="currentList"/>
<list-settings-dropdown v-if="canWriteCurrentList && currentList.id !== -1" :list="currentList"/>
</template>
</div>
<div class="navbar-end">
@ -61,27 +63,27 @@
</template>
<router-link :to="{name: 'user.settings'}" class="dropdown-item">
Settings
{{ $t('user.settings.title') }}
</router-link>
<a
:href="imprintUrl"
class="dropdown-item"
target="_blank"
v-if="imprintUrl">
Imprint
{{ $t('navigation.imprint') }}
</a>
<a
:href="privacyPolicyUrl"
class="dropdown-item"
target="_blank"
v-if="privacyPolicyUrl">
Privacy policy
{{ $t('navigation.privacy') }}
</a>
<a @click="$store.commit('keyboardShortcutsActive', true)" class="dropdown-item">
Keyboard Shortcuts
{{ $t('keyboardShortcuts.title') }}
</a>
<a @click="logout()" class="dropdown-item">
Logout
{{ $t('user.auth.logout') }}
</a>
</dropdown>
</div>
@ -117,8 +119,14 @@ export default {
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
}),
mounted() {
const usernameWidth = this.$refs.usernameDropdown.$el.clientWidth
this.$refs.listTitle.style.setProperty('--nav-username-width', `${usernameWidth}px`)
this.$nextTick(() => {
if (typeof this.$refs.usernameDropdown === 'undefined' || typeof this.$refs.listTitle === 'undefined') {
return
}
const usernameWidth = this.$refs.usernameDropdown.$el.clientWidth
this.$refs.listTitle.style.setProperty('--nav-username-width', `${usernameWidth}px`)
})
},
methods: {
logout() {

View File

@ -1,8 +1,8 @@
<template>
<div class="update-notification" v-if="updateAvailable">
<p>There is an update for Vikunja available!</p>
<p>{{ $t('update.available') }}</p>
<x-button @click="refreshApp()" :shadow="false">
Update Now
{{ $t('update.do') }}
</x-button>
</div>
</template>

View File

@ -19,7 +19,7 @@
:class="{'is-empty': empty}"
/>
<x-button @click="reset" class="is-small ml-2" :shadow="false" type="secondary">
Reset Color
{{ $t('input.resetColor') }}
</x-button>
</div>
</template>

View File

@ -18,7 +18,7 @@
</span>
<span class="text">
<span>
Today
{{ $t('input.datepicker.today') }}
</span>
<span class="weekday">
{{ getWeekdayFromStringInterval('today') }}
@ -31,7 +31,7 @@
</span>
<span class="text">
<span>
Tomorrow
{{ $t('input.datepicker.tomorrow') }}
</span>
<span class="weekday">
{{ getWeekdayFromStringInterval('tomorrow') }}
@ -44,7 +44,7 @@
</span>
<span class="text">
<span>
Next Monday
{{ $t('input.datepicker.nextMonday') }}
</span>
<span class="weekday">
{{ getWeekdayFromStringInterval('nextMonday') }}
@ -57,7 +57,7 @@
</span>
<span class="text">
<span>
This Weekend
{{ $t('input.datepicker.thisWeekend') }}
</span>
<span class="weekday">
{{ getWeekdayFromStringInterval('thisWeekend') }}
@ -70,7 +70,7 @@
</span>
<span class="text">
<span>
Later This Week
{{ $t('input.datepicker.laterThisWeek') }}
</span>
<span class="weekday">
{{ getWeekdayFromStringInterval('laterThisWeek') }}
@ -83,7 +83,7 @@
</span>
<span class="text">
<span>
Next Week
{{ $t('input.datepicker.nextWeek') }}
</span>
<span class="weekday">
{{ getWeekdayFromStringInterval('nextWeek') }}
@ -102,7 +102,7 @@
:shadow="false"
@click="close"
>
Confirm
{{ $t('misc.confirm') }}
</x-button>
</div>
</transition>
@ -118,7 +118,6 @@ import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
import {calculateNearestHours} from '@/helpers/time/calculateNearestHours'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {mapState} from 'vuex'
export default {
name: 'datepicker',
@ -142,7 +141,9 @@ export default {
},
chooseDateLabel: {
type: String,
default: 'Choose a date'
default() {
return this.$t('input.datepicker.chooseDate')
}
},
disabled: {
type: Boolean,
@ -165,19 +166,21 @@ export default {
this.updateData()
},
},
computed: mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: 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,
inline: true,
locale: {
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
},
}
},
},
methods: {
setDateValue(newVal) {
if(newVal === null) {

View File

@ -15,7 +15,7 @@
:shadow="false"
type="secondary"
>
Done
{{ $t('input.editor.done') }}
</x-button>
</div>
@ -129,112 +129,112 @@ export default {
{
name: 'heading-1',
action: EasyMDE.toggleHeading1,
title: 'Heading 1',
title: this.$t('input.editor.heading1'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-2',
action: EasyMDE.toggleHeading2,
title: 'Heading 2',
title: this.$t('input.editor.heading2'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-3',
action: EasyMDE.toggleHeading3,
title: 'Heading 3',
title: this.$t('input.editor.heading3'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-smaller',
action: EasyMDE.toggleHeadingSmaller,
title: 'Heading Smaller',
title: this.$t('input.editor.headingSmaller'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
{
name: 'heading-bigger',
action: EasyMDE.toggleHeadingBigger,
title: 'Heading Bigger',
title: this.$t('input.editor.headingBigger'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.2773 19.25L12.5773 4.34995C12.5773 4.34995 12.5773 4.24995 12.4773 4.24995C12.4773 4.24995 12.4773 4.14995 12.3773 4.14995C12.3773 4.14995 12.2773 4.14995 12.2773 4.04995L12.1773 3.94995H12.0773H11.9773C11.8773 3.94995 11.8773 3.94995 11.8773 3.94995H11.7773C11.6773 4.04995 11.6773 4.14995 11.5773 4.14995C11.5773 4.14995 11.5773 4.14995 11.4773 4.14995C11.4773 4.14995 11.4773 4.24995 11.3773 4.24995L11.2773 4.34995L5.67733 19.25C5.57733 19.55 5.67733 19.95 5.97733 20.05C6.07733 20.05 6.07733 20.05 6.17733 20.05C6.37733 20.05 6.67733 19.95 6.77733 19.65L7.87733 16.85H16.1773L17.2773 19.65C17.3773 19.85 17.5773 20.05 17.8773 20.05C17.9773 20.05 17.9773 20.05 18.0773 20.05C18.2773 19.85 18.4773 19.55 18.2773 19.25ZM8.27733 15.65L11.9773 6.24995L15.6773 15.65H8.27733Z"/></svg>',
},
'|',
{
name: 'bold',
action: EasyMDE.toggleBold,
title: 'Bold',
title: this.$t('input.editor.bold'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M3.5 3H6.5H15.25C18.15 3 20.5 5.36 20.5 8.25C20.5 9.8 19.81 11.19 18.73 12.15C20.37 13.04 21.5 14.76 21.5 16.75C21.5 19.64 19.15 22 16.25 22H6.5H3.5C2.95 22 2.5 21.55 2.5 21C2.5 20.45 2.95 20 3.5 20H5.5V5H3.5C2.95 5 2.5 4.55 2.5 4C2.5 3.45 2.95 3 3.5 3ZM7.5 20H16.25C18.04 20 19.5 18.54 19.5 16.75C19.5 14.96 18.04 13.5 16.25 13.5H7.5V20ZM7.5 11.5H15.25C17.04 11.5 18.5 10.04 18.5 8.25C18.5 6.46 17.04 5 15.25 5H7.5V11.5Z"/></svg>',
},
{
name: 'italic',
action: EasyMDE.toggleItalic,
title: 'Italic',
title: this.$t('input.editor.italic'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M14.0967 4.2H17.0001C17.3301 4.2 17.6001 3.93 17.6001 3.6C17.6001 3.27 17.3301 3 17.0001 3H10.2001C9.8701 3 9.6001 3.27 9.6001 3.6C9.6001 3.93 9.8701 4.2 10.2001 4.2H12.8748L9.90335 19.8H6.9999C6.6699 19.8 6.3999 20.07 6.3999 20.4C6.3999 20.73 6.6699 21 6.9999 21H13.7999C14.1299 21 14.3999 20.73 14.3999 20.4C14.3999 20.07 14.1299 19.8 13.7999 19.8H11.1253L14.0967 4.2Z"/></svg>',
},
{
name: 'strikethrough',
action: EasyMDE.toggleStrikethrough,
title: 'Strikethrough',
title: this.$t('input.editor.strikethrough'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.25 7.17005C18.25 7.50005 17.98 7.77005 17.65 7.77005C17.32 7.77005 17.05 7.50005 17.05 7.17005V5.96005C15.97 5.12005 14.17 4.56005 12.79 4.31005C11.1 4.00005 9.51 4.30005 8.41 5.12005C7.2 6.03005 6.67 7.67005 7.19 8.88005C7.56 9.73005 8.37 10.31 8.98 10.64C9.57215 10.9644 10.1961 11.2013 10.8465 11.3999H20.4C20.73 11.3999 21 11.6699 21 11.9999C21 12.3299 20.73 12.5999 20.4 12.5999H15.3012C16.6583 13.0929 17.5255 13.7765 17.95 14.69C18.73 16.36 17.74 18.33 16.36 19.41C15.05 20.4401 13.35 21 11.54 21H11.16C9.78 20.9401 8.34 20.5301 6.95 19.85V20.3601C6.95 20.6901 6.68 20.96 6.35 20.96C6.02 20.96 5.75 20.6901 5.75 20.3601V17.36C5.75 17.03 6.02 16.76 6.35 16.76C6.68 16.76 6.95 17.03 6.95 17.36V18.5C8.35 19.2801 9.81 19.74 11.21 19.8C12.86 19.89 14.46 19.39 15.62 18.48C16.6 17.71 17.37 16.3 16.86 15.21C16.55 14.54 15.8 14.0201 14.58 13.63C13.9711 13.4331 13.3222 13.2762 12.6906 13.1235C12.6168 13.1056 12.5432 13.0878 12.47 13.07C12.4313 13.0607 12.3925 13.0514 12.3537 13.0421C11.7861 12.9055 11.2108 12.767 10.6413 12.5999H3.6C3.27 12.5999 3 12.3299 3 11.9999C3 11.6699 3.27 11.3999 3.6 11.3999H7.90288C7.04984 10.8343 6.42752 10.1363 6.09 9.36005C5.34 7.63005 6.03 5.40005 7.69 4.16005C9.05 3.15005 10.99 2.77005 13 3.13005C13.64 3.25005 15.53 3.66005 17.05 4.53005V4.17005C17.05 3.84005 17.32 3.57005 17.65 3.57005C17.98 3.57005 18.25 3.84005 18.25 4.17005V7.17005Z"/></svg>',
},
{
name: 'code',
action: EasyMDE.toggleCodeBlock,
title: 'Code',
title: this.$t('input.editor.code'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M8.57 20.9601C8.64 20.9901 8.71 21.0001 8.78 21.0001C9.02 21.0001 9.24 20.8501 9.34 20.6101L15.79 3.81005C15.9 3.50005 15.75 3.15005 15.44 3.03005C15.14 2.92005 14.79 3.07005 14.67 3.38005L8.22 20.1801C8.11 20.4901 8.26 20.8401 8.57 20.9601ZM7.00007 18.0001C6.85007 18.0001 6.69007 17.9401 6.58007 17.8201L1.18007 12.4201C0.950068 12.1901 0.950068 11.8101 1.18007 11.5701L6.58007 6.17006C6.81007 5.94006 7.19007 5.94006 7.43007 6.17006C7.66007 6.40006 7.66007 6.78006 7.43007 7.02006L2.45007 12.0001L7.43007 16.9801C7.66007 17.2101 7.66007 17.5901 7.43007 17.8301C7.31007 17.9401 7.15007 18.0001 7.00007 18.0001ZM17 18.0001C16.85 18.0001 16.69 17.9401 16.58 17.8201C16.35 17.5901 16.35 17.2101 16.58 16.9701L21.55 12.0001L16.57 7.02006C16.34 6.79006 16.34 6.41006 16.57 6.17006C16.81 5.94006 17.19 5.94006 17.42 6.17006L22.82 11.5701C23.05 11.8001 23.05 12.1801 22.82 12.4201L17.42 17.8201C17.31 17.9401 17.15 18.0001 17 18.0001Z"/></svg>',
},
{
name: 'quote',
action: EasyMDE.toggleBlockquote,
title: 'Quote',
title: this.$t('input.editor.quote'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.373 5.16357H5.62695C4.79102 5.16357 4.11133 5.84326 4.11133 6.6792V16.2095C4.11133 17.0464 4.79102 17.7261 5.62695 17.7261H6.8877V21.1245C6.8877 21.3667 7.0332 21.5854 7.25684 21.6782C7.33203 21.7095 7.41016 21.7241 7.4873 21.7241C7.64258 21.7241 7.7959 21.6636 7.91113 21.5493L11.748 17.7261H19.373C20.209 17.7261 20.8887 17.0464 20.8887 16.2095V6.6792C20.8887 5.84326 20.209 5.16357 19.373 5.16357ZM19.6895 16.2095C19.6895 16.3843 19.5469 16.5269 19.373 16.5269H11.5C11.3408 16.5269 11.1895 16.5894 11.0762 16.7017L8.08691 19.6802V17.1265C8.08691 16.7954 7.81836 16.5269 7.4873 16.5269H5.62695C5.45312 16.5269 5.31055 16.3843 5.31055 16.2095V6.6792C5.31055 6.50537 5.45312 6.36279 5.62695 6.36279H19.373C19.5469 6.36279 19.6895 6.50537 19.6895 6.6792V16.2095ZM10.3431 8.45264C9.46326 8.45264 8.75 9.16589 8.75 10.0458C8.75 10.9257 9.46326 11.639 10.3431 11.639C10.4775 11.639 10.6058 11.6173 10.7305 11.5861V11.6195C10.7305 12.1322 10.3135 12.5492 9.75586 12.5492C9.4248 12.5492 9.17871 12.8177 9.17871 13.1488C9.17871 13.4799 9.46973 13.7484 9.80078 13.7484C10.9746 13.7484 11.9297 12.7933 11.9297 11.6195V10.1176L11.9294 10.1165L11.9292 10.1155C11.9297 10.1049 11.9312 10.0946 11.9326 10.0843L11.9326 10.0843C11.9345 10.0716 11.9363 10.059 11.9363 10.0458C11.9363 9.16589 11.223 8.45264 10.3431 8.45264ZM13.0637 10.0458C13.0637 9.16589 13.7771 8.45264 14.657 8.45264C15.5369 8.45264 16.2501 9.16589 16.2501 10.0458C16.2501 10.0584 16.2484 10.0706 16.2466 10.0828C16.2452 10.0929 16.2437 10.103 16.2433 10.1134C16.2433 10.1149 16.2441 10.1161 16.2441 10.1176V11.6195C16.2441 12.7933 15.2891 13.7484 14.1152 13.7484C13.7842 13.7484 13.4922 13.4799 13.4922 13.1488C13.4922 12.8177 13.7383 12.5492 14.0693 12.5492C14.6279 12.5492 15.0449 12.1322 15.0449 11.6195V11.5858C14.9202 11.6173 14.7915 11.639 14.657 11.639C13.7771 11.639 13.0637 10.9257 13.0637 10.0458Z"/></svg>',
},
{
name: 'unordered-list',
action: EasyMDE.toggleUnorderedList,
title: 'Unordered List',
title: this.$t('input.editor.unorderedList'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M6.5281 3.7002H3.5281C3.1981 3.7002 2.9281 3.9702 2.9281 4.3002V7.3002C2.9281 7.6302 3.1981 7.9002 3.5281 7.9002H6.5281C6.8581 7.9002 7.1281 7.6302 7.1281 7.3002V4.3002C7.1281 3.9702 6.8581 3.7002 6.5281 3.7002ZM5.9281 6.7002H4.1281V4.9002H5.9281V6.7002ZM3.5281 9.90015H6.5281C6.8581 9.90015 7.1281 10.1701 7.1281 10.5001V13.5001C7.1281 13.8301 6.8581 14.1001 6.5281 14.1001H3.5281C3.1981 14.1001 2.9281 13.8301 2.9281 13.5001V10.5001C2.9281 10.1701 3.1981 9.90015 3.5281 9.90015ZM4.1281 12.9001H5.9281V11.1001H4.1281V12.9001ZM3.5281 16.1001H6.5281C6.8581 16.1001 7.1281 16.3701 7.1281 16.7001V19.7001C7.1281 20.0301 6.8581 20.3001 6.5281 20.3001H3.5281C3.1981 20.3001 2.9281 20.0301 2.9281 19.7001V16.7001C2.9281 16.3701 3.1981 16.1001 3.5281 16.1001ZM4.1281 19.1001H5.9281V17.3001H4.1281V19.1001ZM9.72817 6.4002H20.7282C21.0582 6.4002 21.3282 6.1302 21.3282 5.8002C21.3282 5.4702 21.0582 5.2002 20.7282 5.2002H9.72817C9.39817 5.2002 9.12817 5.4702 9.12817 5.8002C9.12817 6.1302 9.39817 6.4002 9.72817 6.4002ZM9.72817 11.4001H20.7282C21.0582 11.4001 21.3282 11.6701 21.3282 12.0001C21.3282 12.3301 21.0582 12.6001 20.7282 12.6001H9.72817C9.39817 12.6001 9.12817 12.3301 9.12817 12.0001C9.12817 11.6701 9.39817 11.4001 9.72817 11.4001ZM9.72817 17.6001H20.7282C21.0582 17.6001 21.3282 17.8701 21.3282 18.2001C21.3282 18.5301 21.0582 18.8001 20.7282 18.8001H9.72817C9.39817 18.8001 9.12817 18.5301 9.12817 18.2001C9.12817 17.8701 9.39817 17.6001 9.72817 17.6001Z"/></svg>',
},
{
name: 'ordered-list',
action: EasyMDE.toggleOrderedList,
title: 'Ordered List',
title: this.$t('input.editor.orderedList'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M4.19494 8.29994H5.99494C6.26494 8.29994 6.49494 8.07995 6.49494 7.79994C6.49494 7.51995 6.27494 7.29994 5.99494 7.29994H5.59494V3.79994C5.59494 3.62994 5.50494 3.46994 5.36494 3.37994C5.22494 3.28994 5.04494 3.26994 4.89494 3.33994L3.89494 3.76994C3.64494 3.87994 3.52494 4.17994 3.63494 4.42994C3.74494 4.67994 4.03494 4.79994 4.29494 4.68994L4.59494 4.55994V7.29994H4.19494C3.91494 7.29994 3.69494 7.51995 3.69494 7.79994C3.69494 8.07995 3.91494 8.29994 4.19494 8.29994ZM20.195 6.39995H9.19497C8.86497 6.39995 8.59497 6.12995 8.59497 5.79995C8.59497 5.46995 8.86497 5.19995 9.19497 5.19995H20.195C20.525 5.19995 20.795 5.46995 20.795 5.79995C20.795 6.12995 20.525 6.39995 20.195 6.39995ZM3.78486 14.36H6.37486C6.65486 14.36 6.87486 14.14 6.87486 13.86C6.87486 13.58 6.65486 13.36 6.37486 13.36H4.88486C5.00486 13.23 5.12486 13.09 5.23486 12.95C5.26626 12.9151 5.29645 12.8802 5.32626 12.8458L5.32629 12.8457C5.38192 12.7814 5.43627 12.7186 5.49486 12.66C5.73486 12.4 5.98486 12.12 6.17486 11.79C6.47486 11.25 6.41486 10.63 6.01486 10.17C5.57486 9.66 4.86486 9.5 4.24486 9.74C3.74486 9.95 3.39486 10.35 3.22486 10.91C3.14486 11.18 3.29486 11.46 3.56486 11.54C3.82486 11.61 4.10486 11.46 4.18486 11.2C4.29486 10.85 4.48486 10.73 4.62486 10.67C4.88486 10.57 5.13486 10.68 5.26486 10.82C5.38486 10.96 5.40486 11.12 5.30486 11.29C5.17595 11.5202 4.99618 11.7165 4.80458 11.9257L4.75486 11.98C4.67298 12.0801 4.58283 12.1801 4.49946 12.2727L4.49945 12.2727L4.47486 12.3C4.12486 12.72 3.76486 13.13 3.40486 13.53C3.27486 13.68 3.23486 13.9 3.32486 14.07C3.41486 14.24 3.58486 14.36 3.78486 14.36ZM3.68486 20.3699C4.04486 20.5899 4.46486 20.6999 4.87486 20.6999C5.13486 20.6999 5.38486 20.6499 5.61486 20.5499C6.31486 20.2799 6.73486 19.5599 6.60486 18.8799C6.53486 18.5499 6.35486 18.2899 6.12486 18.0899C6.32486 17.8999 6.45486 17.6499 6.50486 17.3799C6.57486 17.0099 6.49486 16.6299 6.27486 16.3099C5.85486 15.6899 5.07486 15.5199 4.10486 15.8299C3.83486 15.9199 3.69486 16.1999 3.77486 16.4599C3.86486 16.7299 4.14486 16.8699 4.40486 16.7899C4.70486 16.6999 5.24486 16.5799 5.45486 16.8899C5.51486 16.9899 5.54486 17.0999 5.52486 17.1999C5.51486 17.2699 5.47486 17.3599 5.36486 17.4299C5.26486 17.4999 5.12486 17.5399 4.95486 17.5799L4.77486 17.6299C4.54486 17.6999 4.40486 17.9099 4.41486 18.1499C4.42486 18.3899 4.61486 18.5799 4.84486 18.6099C5.20486 18.6599 5.58486 18.8299 5.63486 19.0799C5.67486 19.2999 5.46486 19.5499 5.25486 19.6299C4.94486 19.7599 4.52486 19.7099 4.21486 19.5199C3.97486 19.3699 3.67486 19.4399 3.52486 19.6799C3.37486 19.9199 3.44486 20.2299 3.68486 20.3699ZM20.195 18.7999H9.19497C8.86497 18.7999 8.59497 18.5299 8.59497 18.1999C8.59497 17.8699 8.86497 17.5999 9.19497 17.5999H20.195C20.525 17.5999 20.795 17.8699 20.795 18.1999C20.795 18.5299 20.525 18.7999 20.195 18.7999ZM9.19497 12.5999H20.195C20.525 12.5999 20.795 12.3299 20.795 11.9999C20.795 11.6699 20.525 11.3999 20.195 11.3999H9.19497C8.86497 11.3999 8.59497 11.6699 8.59497 11.9999C8.59497 12.3299 8.86497 12.5999 9.19497 12.5999Z"/></svg>',
},
'|',
{
name: 'clean-block',
action: EasyMDE.cleanBlock,
title: 'Clean Block',
title: this.$t('input.editor.cleanBlock'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M9.25989 6.18384H20.4513C20.7823 6.18384 21.0509 6.45239 21.0509 6.78345V17.9749C21.0509 18.3059 20.7823 18.5745 20.4513 18.5745H9.25989C9.0929 18.5745 8.93469 18.5061 8.82043 18.384L3.6095 12.7883C3.39563 12.5579 3.39563 12.2004 3.6095 11.97L8.82043 6.37427C8.93469 6.2522 9.0929 6.18384 9.25989 6.18384ZM9.52063 17.3752H19.8517V7.38306H9.52063L4.86926 12.3792L9.52063 17.3752ZM12.7755 15.0686C12.6222 15.0686 12.4679 15.01 12.3517 14.8928C12.1173 14.6584 12.1173 14.2786 12.3517 14.0452L14.0503 12.3469L12.3517 10.6487C12.1173 10.4153 12.1173 10.0354 12.3517 9.80103C12.5841 9.56665 12.965 9.56665 13.1993 9.80103L14.8981 11.4994L16.5968 9.80103C16.8312 9.56665 17.212 9.56665 17.4445 9.80103C17.6788 10.0354 17.6788 10.4153 17.4445 10.6487L15.7458 12.3469L17.4445 14.0452C17.6788 14.2786 17.6788 14.6584 17.4445 14.8928C17.3282 15.01 17.174 15.0686 17.0206 15.0686C16.8673 15.0686 16.714 15.01 16.5968 14.8928L14.8981 13.1945L13.1993 14.8928C13.0822 15.01 12.9288 15.0686 12.7755 15.0686Z"/></svg>',
},
{
name: 'link',
action: EasyMDE.drawLink,
title: 'Link',
title: this.$t('input.editor.link'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M11.4399 15.3452C11.4999 15.3652 11.5699 15.3752 11.6299 15.3752C11.8799 15.3752 12.1199 15.2152 12.1999 14.9652C12.2999 14.6452 12.1299 14.3052 11.8199 14.2052C11.3499 14.0452 10.9299 13.7852 10.5699 13.4152C10.1999 13.0552 9.9399 12.6452 9.7799 12.1552C9.6599 11.8252 9.5999 11.4652 9.5999 11.0952C9.5999 10.2152 9.9399 9.38518 10.5699 8.75518L15.1599 4.15518C16.4499 2.87518 18.5399 2.87518 19.8299 4.15518C20.4499 4.78518 20.7899 5.61518 20.7899 6.49518C20.7899 7.37518 20.4499 8.20518 19.8299 8.82518L16.7399 11.9052C16.5099 12.1452 16.5099 12.5252 16.7399 12.7552C16.9799 12.9852 17.3599 12.9852 17.5899 12.7552L20.6799 9.67518C21.5299 8.83518 21.9999 7.69518 21.9999 6.49518C21.9999 5.29518 21.5299 4.16518 20.6899 3.30518C18.9299 1.55518 16.0799 1.55518 14.3199 3.30518L9.7299 7.90518C8.8699 8.75518 8.3999 9.88518 8.3999 11.0952C8.3999 11.6152 8.4899 12.1152 8.6499 12.5552C8.8599 13.1952 9.2399 13.7952 9.7199 14.2652C10.1999 14.7552 10.7999 15.1352 11.4399 15.3452ZM3.32 20.6851C4.2 21.5551 5.35 21.9951 6.5 21.9951C7.65 21.9951 8.81 21.5551 9.69 20.7051L14.28 16.1051C15.14 15.2551 15.61 14.1251 15.61 12.9151C15.61 12.4551 15.54 11.9951 15.4 11.5551C15.17 10.8651 14.8 10.2551 14.28 9.73509C13.76 9.21509 13.15 8.84509 12.46 8.61509C12.14 8.51509 11.8 8.68509 11.7 8.99509C11.6 9.30509 11.77 9.64509 12.1 9.75509C12.61 9.91509 13.06 10.1951 13.44 10.5751C13.82 10.9551 14.09 11.4051 14.26 11.9151C14.36 12.2351 14.41 12.5651 14.41 12.9051C14.41 13.7951 14.06 14.6251 13.43 15.2451L8.84 19.8451C7.55 21.1251 5.46 21.1251 4.17 19.8451C3.55 19.2151 3.21 18.3951 3.21 17.5051C3.21 16.6151 3.55 15.7851 4.17 15.1651L7.35 11.9851C7.58 11.7451 7.59 11.3651 7.35 11.1351C7.11 10.9051 6.73 10.9051 6.5 11.1351L3.32 14.3151C2.47 15.1551 2 16.2851 2 17.4951C2 18.7051 2.47 19.8351 3.32 20.6851Z"/></svg>',
},
{
name: 'image',
action: EasyMDE.drawImage,
title: 'Image',
title: this.$t('input.editor.image'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M4 4C2.89543 4 2 4.89543 2 6V16V17.5152V18C2 19.1046 2.89543 20 4 20H20C21.0528 20 21.9156 19.1866 21.9942 18.1539L22 18.1632V18V16V6C22 4.89543 21.1046 4 20 4H4ZM3.2 17.7V16.5642L6.78192 13.7254C6.8616 13.6622 6.97597 13.6689 7.04776 13.7409L10.3126 17.0146C10.7026 17.4056 11.3357 17.4065 11.7268 17.0165C11.7606 16.9827 11.792 16.9465 11.8207 16.9083L16.736 10.352C16.8023 10.2636 16.9277 10.2457 17.016 10.312C17.0355 10.3265 17.0521 10.3445 17.0651 10.365L20.8 16.2669V17.7C20.8 18.3075 20.3075 18.8 19.7 18.8H4.3C3.69249 18.8 3.2 18.3075 3.2 17.7ZM17.3865 8.61836L20.8 14.08V6.3C20.8 5.69249 20.3075 5.2 19.7 5.2H4.3C3.69249 5.2 3.2 5.69249 3.2 6.3V15.04L6.65054 12.2796C6.84949 12.1204 7.13629 12.1363 7.31645 12.3164L10.8369 15.8369C10.915 15.915 11.0417 15.915 11.1198 15.8369C11.1265 15.8302 16.5625 8.58336 16.5625 8.58336C16.7282 8.36245 17.0416 8.31768 17.2625 8.48336C17.3118 8.52034 17.3538 8.56611 17.3865 8.61836ZM8 8.5C8 9.32843 7.32843 10 6.5 10C5.67157 10 5 9.32843 5 8.5C5 7.67157 5.67157 7 6.5 7C7.32843 7 8 7.67157 8 8.5Z"/></svg>',
},
{
name: 'table',
action: EasyMDE.drawTable,
title: 'Table',
title: this.$t('input.editor.table'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M6.18524 3.08496H19.4152C20.6752 3.08496 21.7152 4.11496 21.7152 5.38496V18.615C21.7152 19.885 20.6852 20.915 19.4152 20.915H6.18524C4.91524 20.915 3.88525 19.885 3.88525 18.615V5.38496C3.88525 4.11496 4.91524 3.08496 6.18524 3.08496ZM19.4052 19.705C20.0152 19.705 20.5052 19.215 20.5052 18.605H20.5153V5.38496C20.5153 4.77496 20.0252 4.28496 19.4152 4.28496H6.18524C5.57524 4.28496 5.08521 4.77496 5.08521 5.38496V18.605C5.08521 19.215 5.57524 19.705 6.18524 19.705H19.4052ZM17.4453 9.15503H8.15527C7.82527 9.15503 7.5553 9.42503 7.5553 9.75503C7.5553 10.085 7.82527 10.355 8.15527 10.355H17.4453C17.7753 10.355 18.0453 10.085 18.0453 9.75503C18.0453 9.42503 17.7753 9.15503 17.4453 9.15503ZM17.4453 13.635H8.15527C7.82527 13.635 7.5553 13.905 7.5553 14.235C7.5553 14.565 7.82527 14.835 8.15527 14.835H17.4453C17.7753 14.835 18.0453 14.565 18.0453 14.235C18.0453 13.905 17.7753 13.635 17.4453 13.635Z"/></svg>',
},
{
name: 'horizontal-rule',
action: EasyMDE.drawHorizontalRule,
title: 'Horizontal Rule',
title: this.$t('input.editor.horizontalRule'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M21 13H3C2.45 13 2 12.55 2 12C2 11.45 2.45 11 3 11H21C21.55 11 22 11.45 22 12C22 12.55 21.55 13 21 13Z"/></svg>',
},
'|',
{
name: 'side-by-side',
action: EasyMDE.toggleSideBySide,
title: 'Side By Side',
title: this.$t('input.editor.sideBySide'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M18.4787 14.58C18.3587 14.69 18.2987 14.85 18.2987 15C18.2987 15.15 18.3587 15.31 18.4787 15.42C18.7187 15.65 19.0987 15.65 19.3287 15.42L22.3287 12.42C22.5587 12.18 22.5587 11.8 22.3287 11.57L19.3287 8.56996C19.0887 8.33996 18.7087 8.33996 18.4787 8.56996C18.2487 8.80996 18.2487 9.18996 18.4787 9.41996L20.451 11.3999L14.4487 11.3999L14.4487 4.6C14.4487 4.27 14.1787 4 13.8487 4C13.5187 4 13.2487 4.27 13.2487 4.6L13.2487 19.4C13.2487 19.73 13.5187 20 13.8487 20C14.1787 20 14.4487 19.73 14.4487 19.4L14.4487 12.5999L20.4511 12.5999L18.4787 14.58ZM9.54878 19.4L9.54878 12.5999L3.5486 12.5999L5.52867 14.58C5.75867 14.81 5.75867 15.19 5.52867 15.43C5.29867 15.66 4.91867 15.66 4.67867 15.43L1.67867 12.43C1.63274 12.384 1.5956 12.3323 1.56725 12.2774C1.53058 12.2077 1.50724 12.1299 1.50068 12.0477C1.49934 12.0317 1.49867 12.0158 1.49867 12C1.49867 11.9841 1.49933 11.9682 1.50067 11.9522C1.51454 11.778 1.60365 11.6242 1.73526 11.5234L4.67867 8.57997C4.90867 8.34997 5.28867 8.34997 5.52867 8.57997C5.75867 8.80997 5.75867 9.18997 5.52867 9.42997L3.55107 11.3999L9.54878 11.3999L9.54878 4.6C9.54878 4.27 9.81878 4 10.1488 4C10.4788 4 10.7488 4.27 10.7488 4.6L10.7488 11.9999L10.7488 19.4C10.7488 19.73 10.4788 20 10.1488 20C9.81878 20 9.54878 19.73 9.54878 19.4Z"/></svg>',
},
{
@ -242,7 +242,7 @@ export default {
action: () => {
window.open('https://www.markdownguide.org/basic-syntax/', '_blank')
},
title: 'Guide',
title: this.$t('input.editor.guide'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.4999 2.3999H6.4999C5.0699 2.3999 3.8999 3.5699 3.8999 4.9999V18.9999C3.8999 20.4299 5.0699 21.5999 6.4999 21.5999H19.4999C19.8299 21.5999 20.0999 21.3299 20.0999 20.9999V16.9999V2.9999C20.0999 2.6699 19.8299 2.3999 19.4999 2.3999ZM5.0999 4.9999V16.8118C5.50468 16.5513 5.98546 16.3999 6.4999 16.3999H18.8999V3.5999H6.4999C5.7299 3.5999 5.0999 4.2299 5.0999 4.9999ZM6.4999 17.5999H18.8999V20.3999H6.4999C5.7299 20.3999 5.0999 19.7699 5.0999 18.9999C5.0999 18.2299 5.7299 17.5999 6.4999 17.5999ZM8.4999 8.5999H15.4999C15.8299 8.5999 16.0999 8.3299 16.0999 7.9999C16.0999 7.6699 15.8299 7.3999 15.4999 7.3999H8.4999C8.1699 7.3999 7.8999 7.6699 7.8999 7.9999C7.8999 8.3299 8.1699 8.5999 8.4999 8.5999ZM15.4999 11.3999H8.4999C8.1699 11.3999 7.8999 11.6699 7.8999 11.9999C7.8999 12.3299 8.1699 12.5999 8.4999 12.5999H15.4999C15.8299 12.5999 16.0999 12.3299 16.0999 11.9999C16.0999 11.6699 15.8299 11.3999 15.4999 11.3999Z"/></svg>',
},
],

View File

@ -149,15 +149,15 @@ export default {
createPlaceholder: {
type: String,
default() {
return 'Create new'
},
return this.$t('input.multiselect.createPlaceholder')
}
},
// The text shown next to an option.
selectPlaceholder: {
type: String,
default() {
return 'Click or press enter to select'
},
return this.$t('input.multiselect.selectPlaceholder')
}
},
// If true, allows for selecting multiple items. v-model will be an array with all selected values in that case.
multiple: {

View File

@ -5,13 +5,13 @@
:to="{ name: `${listRoutePrefix}.settings.edit`, params: { listId: list.id } }"
icon="pen"
>
Edit
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.delete`, params: { listId: list.id } }"
icon="trash-alt"
>
Delete
{{ $t('misc.delete') }}
</dropdown-item>
</template>
<template v-else-if="list.isArchived">
@ -19,7 +19,7 @@
:to="{ name: `${listRoutePrefix}.settings.archive`, params: { listId: list.id } }"
icon="archive"
>
Un-Archive
{{ $t('menu.unarchive') }}
</dropdown-item>
</template>
<template v-else>
@ -27,32 +27,32 @@
:to="{ name: `${listRoutePrefix}.settings.edit`, params: { listId: list.id } }"
icon="pen"
>
Edit
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.background`, params: { listId: list.id } }"
v-if="backgroundsEnabled"
icon="image"
>
Set background
{{ $t('menu.setBackground') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.share`, params: { listId: list.id } }"
icon="share-alt"
>
Share
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.duplicate`, params: { listId: list.id } }"
icon="paste"
>
Duplicate
{{ $t('menu.duplicate') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.archive`, params: { listId: list.id } }"
icon="archive"
>
Archive
{{ $t('menu.archive') }}
</dropdown-item>
<task-subscription
class="dropdown-item has-no-shadow"
@ -67,7 +67,7 @@
icon="trash-alt"
class="has-text-danger"
>
Delete
{{ $t('menu.delete') }}
</dropdown-item>
</template>
</dropdown>

View File

@ -1,28 +1,30 @@
<template>
<card class="filters has-overflow">
<fancycheckbox v-model="params.filter_include_nulls">
Include Tasks which don't have a value set
{{ $t('filters.attributes.includeNulls') }}
</fancycheckbox>
<fancycheckbox
v-model="filters.requireAllFilters"
@change="setFilterConcat()"
>
Require all filters to be true for a task to show up
{{ $t('filters.attributes.requireAll') }}
</fancycheckbox>
<div class="field">
<label class="label">Show Done Tasks</label>
<label class="label">
{{ $t('filters.attributes.showDoneTasks') }}
</label>
<div class="control">
<fancycheckbox @change="setDoneFilter" v-model="filters.done">
Show Done Tasks
{{ $t('filters.attributes.showDoneTasks') }}
</fancycheckbox>
</div>
</div>
<div class="field">
<label class="label">Search</label>
<label class="label">{{ $t('misc.search') }}</label>
<div class="control">
<input
class="input"
placeholder="Search"
:placeholder="$t('misc.search')"
v-model="params.s"
@blur="change()"
@keyup.enter="change()"
@ -30,7 +32,7 @@
</div>
</div>
<div class="field">
<label class="label">Priority</label>
<label class="label">{{ $t('task.attributes.priority') }}</label>
<div class="control single-value-control">
<priority-select
:disabled="!filters.usePriority"
@ -41,12 +43,12 @@
v-model="filters.usePriority"
@change="setPriority"
>
Enable Filter By Priority
{{ $t('filters.attributes.enablePriority') }}
</fancycheckbox>
</div>
</div>
<div class="field">
<label class="label">Percent Done</label>
<label class="label">{{ $t('task.attributes.percentDone') }}</label>
<div class="control single-value-control">
<percent-done-select
v-model.number="filters.percentDone"
@ -57,65 +59,65 @@
v-model="filters.usePercentDone"
@change="setPercentDoneFilter"
>
Enable Filter By Percent Done
{{ $t('filters.attributes.enablePercentDone') }}
</fancycheckbox>
</div>
</div>
<div class="field">
<label class="label">Due Date</label>
<label class="label">{{ $t('task.attributes.dueDate') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setDueDateFilter"
class="input"
placeholder="Due Date Range"
:placeholder="$t('filters.attributes.dueDateRange')"
v-model="filters.dueDate"
/>
</div>
</div>
<div class="field">
<label class="label">Start Date</label>
<label class="label">{{ $t('task.attributes.startDate') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setStartDateFilter"
class="input"
placeholder="Start Date Range"
:placeholder="$t('filters.attributes.startDateRange')"
v-model="filters.startDate"
/>
</div>
</div>
<div class="field">
<label class="label">End Date</label>
<label class="label">{{ $t('task.attributes.endDate') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setEndDateFilter"
class="input"
placeholder="End Date Range"
:placeholder="$t('filters.attributes.endDateRange')"
v-model="filters.endDate"
/>
</div>
</div>
<div class="field">
<label class="label">Reminders</label>
<label class="label">{{ $t('task.attributes.reminders') }}</label>
<div class="control">
<flat-pickr
:config="flatPickerConfig"
@on-close="setReminderFilter"
class="input"
placeholder="Reminder Date Range"
:placeholder="$t('filters.attributes.reminderRange')"
v-model="filters.reminders"
/>
</div>
</div>
<div class="field">
<label class="label">Assignees</label>
<label class="label">{{ $t('task.attributes.assignees') }}</label>
<div class="control">
<multiselect
:loading="usersService.loading"
placeholder="Type to search for a user..."
:placeholder="$t('team.edit.search')"
@search="query => find('users', query)"
:search-results="foundusers"
@select="() => add('users', 'assignees')"
@ -128,10 +130,10 @@
</div>
<div class="field">
<label class="label">Labels</label>
<label class="label">{{ $t('task.attributes.label') }}</label>
<div class="control">
<multiselect
placeholder="Type to search for a label..."
:placeholder="$t('label.search')"
@search="findLabels"
:search-results="foundLabels"
@select="label => addLabel(label)"
@ -153,11 +155,11 @@
<template v-if="$route.name === 'filters.create' || $route.name === 'list.edit'">
<div class="field">
<label class="label">Lists</label>
<label class="label">{{ $t('list.lists') }}</label>
<div class="control">
<multiselect
:loading="listsService.loading"
placeholder="Type to search for a list..."
:placeholder="$t('list.search')"
@search="query => find('lists', query)"
:search-results="foundlists"
@select="() => add('lists', 'list_id')"
@ -169,11 +171,11 @@
</div>
</div>
<div class="field">
<label class="label">Namespaces</label>
<label class="label">{{ $t('namespace.namespaces') }}</label>
<div class="control">
<multiselect
:loading="namespaceService.loading"
placeholder="Type to search for a namespace..."
:placeholder="$t('namespace.search')"
@search="query => find('namespace', query)"
:search-results="foundnamespace"
@select="() => add('namespace', 'namespace')"
@ -203,7 +205,6 @@ import Multiselect from '@/components/input/multiselect'
import UserService from '@/services/user'
import ListService from '@/services/list'
import NamespaceService from '@/services/namespace'
import {mapState} from 'vuex'
export default {
name: 'filters',
@ -290,19 +291,19 @@ export default {
return first.id === second.id
})
},
...mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
flatPickerConfig() {
return {
altFormat: this.$t('date.altFormatLong'),
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
mode: 'range',
locale: {
firstDayOfWeek: state.auth.settings.weekStart,
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
},
}),
}),
}
},
},
methods: {
change() {

View File

@ -1,15 +1,15 @@
<template>
<div class="content">
<h1>Import your data from {{ name }} to Vikunja</h1>
<p>Vikunja will import all lists, tasks, notes, reminders and files you have access to.</p>
<h1>{{ $t('migrate.titleService', { name: name }) }}</h1>
<p>{{ $t('migrate.descriptionDo') }}</p>
<template v-if="isMigrating === false && message === '' && lastMigrationDate === null">
<p>To authorize Vikunja to access your {{ name }} Account, click the button below.</p>
<p>{{ $t('migrate.authorize', {name: name}) }}</p>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading"
:href="authUrl"
>
Get Started
{{ $t('migrate.getStarted') }}
</x-button>
</template>
<div
@ -29,17 +29,16 @@
</div>
<img alt="Vikunja" src="/images/logo.svg">
</div>
<p>Importing in progress, hang tight...</p>
<p>{{ $t('migrate.inProgress') }}</p>
</div>
<div v-else-if="lastMigrationDate">
<p>
It looks like you've already imported your stuff from {{ name }} at {{ formatDate(lastMigrationDate) }}.<br/>
Importing again is possible, but might create duplicates.
Are you sure?
{{ $t('migrate.alreadyMigrated1', { name: name, date: formatDate(lastMigrationDate) }) }}<br/>
{{ $t('migrate.alreadyMigrated2') }}
</p>
<div class="buttons">
<x-button @click="migrate">I am sure, please start migrating now!</x-button>
<x-button :to="{name: 'home'}" type="tertary" class="has-text-danger">Cancel</x-button>
<x-button @click="migrate">{{ $t('migrate.confirm') }}</x-button>
<x-button :to="{name: 'home'}" type="tertary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button>
</div>
</div>
<div v-else>
@ -48,7 +47,7 @@
{{ message }}
</div>
</div>
<x-button :to="{name: 'home'}">Refresh</x-button>
<x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button>
</div>
</div>
</template>

View File

@ -1,13 +1,13 @@
<template>
<div class="api-config">
<div v-if="configureApi">
<label class="label" for="api-url">Vikunja URL</label>
<label class="label" for="api-url">{{ $t('apiConfig.url') }}</label>
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input"
id="api-url"
placeholder="eg. https://localhost:3456"
:placeholder="$t('apiConfig.urlPlaceholder')"
required
type="url"
v-focus
@ -17,16 +17,17 @@
</div>
<div class="control">
<x-button @click="setApiUrl" :disabled="apiUrl === ''">
Change
{{ $t('apiConfig.change') }}
</x-button>
</div>
</div>
</div>
<div class="api-url-info" v-else>
Sign in to your Vikunja account on
<span v-tooltip="apiUrl"> {{ apiDomain() }} </span>
<i18n path="apiConfig.signInOn">
<span class="url" v-tooltip="apiUrl"> {{ apiDomain() }} </span>
</i18n>
<br />
<a @click="() => (configureApi = true)">change</a>
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
</div>
<div
@ -178,14 +179,14 @@ export default {
.catch(() => {
// Still not found, url is still invalid
this.successMsg = ''
this.errorMsg = `Could not find or use Vikunja installation at "${this.apiDomain()}".`
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain()})
window.API_URL = oldUrl
})
.then((r) => {
if (typeof r !== 'undefined') {
// Set it + save it to local storage to save us the hoops
this.errorMsg = ''
this.successMsg = `Using Vikunja installation at "${this.apiDomain()}".`
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain()})
localStorage.setItem('API_URL', window.API_URL)
this.configureApi = false
this.apiUrl = window.API_URL

View File

@ -26,7 +26,7 @@
type="secondary"
@click.prevent.stop="$router.back()"
>
Cancel
{{ $t('misc.cancel') }}
</x-button>
<x-button
type="primary"
@ -52,7 +52,9 @@ export default {
},
primaryLabel: {
type: String,
default: 'Create',
default() {
return this.$t('misc.create')
},
},
primaryIcon: {
type: String,

View File

@ -1,7 +1,9 @@
<template>
<div class="notification is-danger">
Loading failed, please <a @click="() => location.reload()">try again</a>.
If the error persists, please <a href="https://vikunja.io/contact/">contact us</a>.
<i18n path="loadingError.failed">
<a @click="() => location.reload()">{{ $t('loadingError.tryAgain') }}</a>
<a href="https://vikunja.io/contact/">{{ $t('loadingError.contact') }}</a>
</i18n>
</div>
</template>

View File

@ -2,56 +2,56 @@
<div class="modal-mask keyboard-shortcuts-modal">
<div @click.self="close()" class="modal-container">
<div class="modal-content">
<card class="has-background-white has-no-shadow" title="Available Keyboard Shortcuts">
<card class="has-background-white has-no-shadow" :title="$t('keyboardShortcuts.title')">
<div class="message is-primary">
<div class="message-body">
These shortcuts work on all pages.
{{ $t('keyboardShortcuts.allPages') }}
</div>
</div>
<p>
<strong>Toggle The Menu</strong>
<strong>{{ $t('keyboardShortcuts.toggleMenu') }}</strong>
<shortcut :keys="['ctrl', 'e']"/>
</p>
<p>
<strong>Open the search/quick action bar</strong>
<strong>{{ $t('keyboardShortcuts.quickSearch') }}</strong>
<shortcut :keys="['ctrl', 'k']"/>
</p>
<h3>Kanban</h3>
<h3>{{ $t('list.kanban.title') }}</h3>
<div class="message is-primary" v-if="$route.name === 'list.kanban'">
<div class="message-body">
These shortcuts work only on the current page.
{{ $t('keyboardShortcuts.currentPageOnly') }}
</div>
</div>
<p>
<strong>Mark a task as done</strong>
<strong>{{ $t('keyboardShortcuts.task.done') }}</strong>
<shortcut :keys="['ctrl', 'click']"/>
</p>
<h3>Task Page</h3>
<h3>{{ $t('keyboardShortcuts.task.title') }}</h3>
<div
class="message is-primary"
v-if="$route.name === 'task.detail' || $route.name === 'task.list.detail' || $route.name === 'task.gantt.detail' || $route.name === 'task.kanban.detail' || $route.name === 'task.detail'">
<div class="message-body">
These shortcuts work only on the current page.
{{ $t('keyboardShortcuts.currentPageOnly') }}
</div>
</div>
<p>
<strong>Assign this task to a user</strong>
<strong>{{ $t('keyboardShortcuts.task.assign') }}</strong>
<shortcut :keys="['a']"/>
</p>
<p>
<strong>Add labels to this task</strong>
<strong>{{ $t('keyboardShortcuts.task.labels') }}</strong>
<shortcut :keys="['l']"/>
</p>
<p>
<strong>Change the due date of this task</strong>
<strong>{{ $t('keyboardShortcuts.task.dueDate') }}</strong>
<shortcut :keys="['d']"/>
</p>
<p>
<strong>Add an attachment to this task</strong>
<strong>{{ $t('keyboardShortcuts.task.attachment') }}</strong>
<shortcut :keys="['f']"/>
</p>
<p>
<strong>Modify related tasks of this task</strong>
<strong>{{ $t('keyboardShortcuts.task.related') }}</strong>
<shortcut :keys="['r']"/>
</p>
</card>

View File

@ -1,8 +1,8 @@
<template>
<div class="legal-links">
<a :href="imprintUrl" target="_blank" v-if="imprintUrl">Imprint</a>
<a :href="imprintUrl" target="_blank" v-if="imprintUrl">{{ $t('navigation.imprint') }}</a>
<span v-if="imprintUrl && privacyPolicyUrl"> | </span>
<a :href="privacyPolicyUrl" target="_blank" v-if="privacyPolicyUrl">Privacy policy</a>
<a :href="privacyPolicyUrl" target="_blank" v-if="privacyPolicyUrl">{{ $t('navigation.privacy') }}</a>
</div>
</template>

View File

@ -54,16 +54,19 @@ export default {
},
computed: {
tooltipText() {
if(this.disabled) {
return `You can't unsubscribe here because you are subscribed to this ${this.entity} through its ${this.subscription.entity}.`
if (this.disabled) {
return this.$t('task.subscription.subscribedThroughParent', {
entity: this.entity,
parent: this.subscription.entity
})
}
return this.subscription !== null ?
`You are currently subscribed to this ${this.entity} and will receive notifications for changes.` :
`You are not subscribed to this ${this.entity} and won't receive notifications for changes.`
this.$t('task.subscription.subscribed', {entity: this.entity}) :
this.$t('task.subscription.notSubscribed', {entity: this.entity})
},
buttonText() {
return this.subscription !== null ? 'Unsubscribe' : 'Subscribe'
return this.subscription !== null ? this.$t('task.subscription.unsubscribe') : this.$t('task.subscription.subscribe')
},
icon() {
return this.subscription !== null ? ['far', 'bell-slash'] : 'bell'
@ -78,7 +81,7 @@ export default {
},
methods: {
changeSubscription() {
if(this.disabled) {
if (this.disabled) {
return
}
@ -96,7 +99,7 @@ export default {
this.subscriptionService.create(subscription)
.then(() => {
this.$emit('change', subscription)
this.success({message: `You are now subscribed to this ${this.entity}`})
this.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
})
.catch(e => {
this.error(e)
@ -110,7 +113,7 @@ export default {
this.subscriptionService.delete(subscription)
.then(() => {
this.$emit('change', null)
this.success({message: `You are now unsubscribed to this ${this.entity}`})
this.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
})
.catch(e => {
this.error(e)

View File

@ -16,14 +16,14 @@
type="tertary"
class="has-text-danger"
>
Cancel
{{ $t('misc.cancel') }}
</x-button>
<x-button
@click="$emit('submit')"
type="primary"
:shadow="false"
>
Do it!
{{ $t('misc.doit') }}
</x-button>
</div>
</slot>

View File

@ -1,7 +1,7 @@
<template>
<multiselect
:loading="namespaceService.loading"
placeholder="Search for a namespace..."
:placeholder="$t('namespace.search')"
@search="findNamespaces"
:search-results="namespaces"
@select="select"

View File

@ -5,7 +5,7 @@
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
icon="archive"
>
Un-Archive
{{ $t('menu.unarchive') }}
</dropdown-item>
</template>
<template v-else>
@ -13,25 +13,25 @@
:to="{ name: 'namespace.settings.edit', params: { id: namespace.id } }"
icon="pen"
>
Edit
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'namespace.settings.share', params: { id: namespace.id } }"
icon="share-alt"
>
Share
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.create', params: { id: namespace.id } }"
icon="plus"
>
New list
{{ $t('menu.newList') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'namespace.settings.archive', params: { id: namespace.id } }"
icon="archive"
>
Archive
{{ $t('menu.archive') }}
</dropdown-item>
<task-subscription
class="dropdown-item has-no-shadow"
@ -46,7 +46,7 @@
icon="trash-alt"
class="has-text-danger"
>
Delete
{{ $t('menu.delete') }}
</dropdown-item>
</template>
</dropdown>

View File

@ -37,9 +37,9 @@
</span>
</div>
<p class="nothing" v-if="notifications.length === 0">
You don't have any notifications. Have a nice day!<br/>
{{ $t('notification.none') }}<br/>
<span class="explainer">
Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen.
{{ $t('notification.explainer') }}
</span>
</p>
</div>

View File

@ -123,22 +123,22 @@ export default {
return [
{
type: TYPE_CMD,
title: 'Commands',
title: this.$t('quickActions.commands'),
items: cmds,
},
{
type: TYPE_TASK,
title: 'Tasks',
title: this.$t('quickActions.tasks'),
items: this.foundTasks,
},
{
type: TYPE_LIST,
title: 'Lists',
title: this.$t('quickActions.lists'),
items: lists,
},
{
type: TYPE_TEAM,
title: 'Teams',
title: this.$t('quickActions.teams'),
items: this.foundTeams,
},
].filter(i => i.items.length > 0)
@ -156,17 +156,17 @@ export default {
if (this.selectedCmd !== null) {
switch (this.selectedCmd.action) {
case CMD_NEW_TASK:
return 'Enter the title of the new task...'
return this.$t('quickActions.newTask')
case CMD_NEW_LIST:
return 'Enter the title of the new list...'
return this.$t('quickActions.newList')
case CMD_NEW_NAMESPACE:
return 'Enter the title of the new namespace...'
return this.$t('quickActions.newNamespace')
case CMD_NEW_TEAM:
return 'Enter the name of the new team...'
return this.$t('quickActions.newTeam')
}
}
return 'Type a command or search...'
return this.$t('quickActions.placeholder')
},
hintText() {
let namespace
@ -174,14 +174,14 @@ export default {
if (this.selectedCmd !== null && this.currentList !== null) {
switch (this.selectedCmd.action) {
case CMD_NEW_TASK:
return `Create a task in the current list (${this.currentList.title})`
return this.$t('quickActions.createTask', {title: this.currentList.title})
case CMD_NEW_LIST:
namespace = this.$store.getters['namespaces/getNamespaceById'](this.currentList.namespaceId)
return `Create a list in the current namespace (${namespace.title})`
return this.$t('quickActions.createList', {title: namespace.title})
}
}
return 'You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.'
return this.$t('quickActions.hint')
},
currentList() {
return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST]
@ -191,20 +191,20 @@ export default {
if (this.currentList !== null) {
cmds.push({
title: 'New task',
title: this.$t('quickActions.cmds.newTask'),
action: CMD_NEW_TASK,
})
cmds.push({
title: 'New list',
title: this.$t('quickActions.cmds.newList'),
action: CMD_NEW_LIST,
})
}
cmds.push({
title: 'New namespace',
title: this.$t('quickActions.cmds.newNamespace'),
action: CMD_NEW_NAMESPACE,
})
cmds.push({
title: 'New Team',
title: this.$t('quickActions.cmds.newTeam'),
action: CMD_NEW_TEAM,
})
@ -361,7 +361,7 @@ export default {
})
this.taskService.create(newTask)
.then(r => {
this.success({message: 'The task was successfully created.'})
this.success({message: this.$t('task.createSuccess')})
this.$router.push({name: 'task.detail', params: {id: r.id}})
this.closeQuickActions()
})
@ -380,7 +380,7 @@ export default {
})
this.listService.create(newList)
.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}})
this.closeQuickActions()
})
@ -393,7 +393,7 @@ export default {
this.namespaceService.create(newNamespace)
.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.closeQuickActions()
})
.catch((e) => {
@ -408,7 +408,7 @@ export default {
name: 'teams.edit',
params: {id: r.id},
})
this.success({message: 'The team was successfully created.'})
this.success({message: this.$t('team.create.success')})
this.closeQuickActions()
})
.catch((e) => {

View File

@ -1,72 +1,76 @@
<template>
<div>
<p class="has-text-weight-bold">
Share Links
{{ $t('list.share.links.title') }}
<span
class="is-size-7"
v-tooltip="'Share Links allow you to easily share a list with other users who don\'t have an account on Vikunja.'">
What is a share link?
v-tooltip="$t('list.share.links.explanation')">
{{ $t('list.share.links.what') }}
</span>
</p>
<div class="sharables-list">
<x-button
v-if="!(linkShares.length === 0 || showNewForm)"
@click="showNewForm = true"
icon="plus"
class="mb-4">
Create a new link share
{{ $t('list.share.links.create') }}
</x-button>
<div class="p-4" v-if="linkShares.length === 0 || showNewForm">
<div class="field">
<label class="label" for="linkShareRight">
Right
{{ $t('list.share.right.title') }}
</label>
<div class="control">
<div class="select">
<select v-model="selectedRight" id="linkShareRight">
<option :value="rights.READ">Read only</option>
<option :value="rights.READ_WRITE">
Read & write
<option :value="rights.READ">
{{ $t('list.share.right.read') }}
</option>
<option :value="rights.READ_WRITE">
{{ $t('list.share.right.readWrite') }}
</option>
<option :value="rights.ADMIN">
{{ $t('list.share.right.admin') }}
</option>
<option :value="rights.ADMIN">Admin</option>
</select>
</div>
</div>
</div>
<div class="field">
<label class="label" for="linkShareName">
Name (optional)
{{ $t('list.share.links.name') }}
</label>
<div class="control">
<input
id="linkShareName"
class="input"
placeholder="e.g. Lorem Ipsum"
v-tooltip="'All actions done by this link share will show up with the name.'"
:placeholder="$t('list.share.links.namePlaceholder')"
v-tooltip="$t('list.share.links.nameExplanation')"
v-model="name"
/>
</div>
</div>
<div class="field">
<label class="label" for="linkSharePassword">
Password (optional)
{{ $t('list.share.links.password') }}
</label>
<div class="control">
<input
id="linkSharePassword"
type="password"
class="input"
placeholder="e.g. ••••••••••••"
v-tooltip="'When authenticating, the user will be required to enter this password.'"
:placeholder="$t('user.auth.passwortPlaceholder')"
v-tooltip="$t('list.share.links.passwordExplanation')"
v-model="password"
/>
</div>
</div>
<x-button @click="add" icon="plus">Share</x-button>
<x-button @click="add" icon="plus">
{{ $t('list.share.share') }}
</x-button>
</div>
<table
@ -75,11 +79,11 @@
>
<thead>
<tr>
<th>Link</th>
<th>Name</th>
<th>Shared&nbsp;by</th>
<th>Right</th>
<th>Delete</th>
<th>{{ $t('list.share.attributes.link') }}</th>
<th>{{ $t('list.share.attributes.name') }}</th>
<th>{{ $t('list.share.attributes.sharedBy') }}</th>
<th>{{ $t('list.share.attributes.right') }}</th>
<th>{{ $t('list.share.attributes.delete') }}</th>
</tr>
</thead>
<tbody>
@ -98,7 +102,7 @@
<x-button
@click="copy(getShareLink(s.hash))"
:shadow="false"
v-tooltip="'Copy to clipboard'"
v-tooltip="$t('misc.copy')"
>
<span class="icon">
<icon icon="paste"/>
@ -111,7 +115,7 @@
<template v-if="s.name !== ''">
{{ s.name }}
</template>
<i v-else>No name set</i>
<i v-else>{{ $t('list.share.links.noName') }}</i>
</td>
<td>
{{ s.sharedBy.getDisplayName() }}
@ -121,19 +125,19 @@
<span class="icon is-small">
<icon icon="lock"/>
</span>&nbsp;
Admin
{{ $t('list.share.right.admin') }}
</template>
<template v-else-if="s.right === rights.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>&nbsp;
Write
{{ $t('list.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>&nbsp;
Read-only
{{ $t('list.share.right.read') }}
</template>
</td>
<td class="actions">
@ -159,12 +163,9 @@
@submit="remove()"
v-if="showDeleteModal"
>
<span slot="header">Remove a link share</span>
<span slot="header">{{ $t('list.share.links.remove') }}</span>
<p slot="text">
Are you sure you want to remove this link share?<br/>
It will no longer be possible to access this list with this link
share.<br/>
<b>This CANNOT BE UNDONE!</b>
{{ $t('list.share.links.removeText') }}
</p>
</modal>
</transition>
@ -248,7 +249,7 @@ export default {
this.name = ''
this.password = ''
this.showNewForm = false
this.success({message: 'The link share was successfully created'})
this.success({message: this.$t('list.share.links.createSuccess')})
this.load()
})
.catch((e) => {
@ -263,7 +264,7 @@ export default {
this.linkShareService
.delete(linkshare)
.then(() => {
this.success({message: 'The link share was successfully deleted'})
this.success({message: this.$t('list.share.links.deleteSuccess')})
this.load()
})
.catch((e) => {

View File

@ -1,6 +1,8 @@
<template>
<div>
<p class="has-text-weight-bold">Shared with these {{ shareType }}s</p>
<p class="has-text-weight-bold">
{{ $t('list.share.userTeam.shared', {type: shareTypeNames}) }}
</p>
<div v-if="userIsAdmin">
<div class="field has-addons">
<p
@ -9,7 +11,7 @@
>
<multiselect
:loading="searchService.loading"
placeholder="Type to search..."
:placeholder="$t('misc.searchPlaceholder')"
@search="find"
:search-results="found"
:label="searchLabel"
@ -17,7 +19,7 @@
/>
</p>
<p class="control">
<x-button @click="add()"> Share</x-button>
<x-button @click="add()">{{ $t('list.share.share') }}</x-button>
</p>
</div>
</div>
@ -29,7 +31,7 @@
<td>{{ s.getDisplayName() }}</td>
<td>
<template v-if="s.id === userInfo.id">
<b class="is-success">You</b>
<b class="is-success">{{ $t('list.share.userTeam.you') }}</b>
</template>
</td>
</template>
@ -50,19 +52,19 @@
<span class="icon is-small">
<icon icon="lock"/>
</span>
Admin
{{ $t('list.share.right.admin') }}
</template>
<template v-else-if="s.right === rights.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>
Write
{{ $t('list.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>
Read-only
{{ $t('list.share.right.read') }}
</template>
</td>
<td class="actions" v-if="userIsAdmin">
@ -76,19 +78,19 @@
:selected="s.right === rights.READ"
:value="rights.READ"
>
Read only
{{ $t('list.share.right.read') }}
</option>
<option
:selected="s.right === rights.READ_WRITE"
:value="rights.READ_WRITE"
>
Read & write
{{ $t('list.share.right.readWrite') }}
</option>
<option
:selected="s.right === rights.ADMIN"
:value="rights.ADMIN"
>
Admin
{{ $t('list.share.right.admin') }}
</option>
</select>
</div>
@ -108,7 +110,7 @@
</table>
<nothing v-else>
Not shared with any {{ shareType }} yet.
{{ $t('list.share.userTeam.notShared', {type: shareTypeNames}) }}
</nothing>
<transition name="modal">
@ -117,13 +119,11 @@
@submit="deleteSharable()"
v-if="showDeleteModal"
>
<span slot="header"
>Remove a {{ shareType }} from the {{ typeString }}</span
>
<span slot="header">
{{ $t('list.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName}) }}
</span>
<p slot="text">
Are you sure you want to remove this {{ shareType }} from the
{{ typeString }}?<br/>
<b>This CANNOT BE UNDONE!</b>
{{ $t('list.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}
</p>
</modal>
</transition>
@ -131,8 +131,6 @@
</template>
<script>
import {mapState} from 'vuex'
import UserNamespaceService from '../../services/userNamespace'
import UserNamespaceModel from '../../models/userNamespace'
import UserListModel from '../../models/userList'
@ -192,9 +190,44 @@ export default {
Nothing,
Multiselect,
},
computed: mapState({
userInfo: (state) => state.auth.info,
}),
computed: {
userInfo() {
return this.$store.state.auth.info
},
shareTypeNames() {
if (this.shareType === 'user') {
return this.$tc('list.share.userTeam.typeUser', 2)
}
if (this.shareType === 'team') {
return this.$tc('list.share.userTeam.typeTeam', 2)
}
return ''
},
shareTypeName() {
if (this.shareType === 'user') {
return this.$tc('list.share.userTeam.typeUser', 1)
}
if (this.shareType === 'team') {
return this.$tc('list.share.userTeam.typeTeam', 1)
}
return ''
},
sharableName() {
if (this.type === 'list') {
return this.$t('list.list.title')
}
if (this.shareType === 'namespace') {
return this.$t('namespace.namespace')
}
return ''
},
},
created() {
if (this.shareType === 'user') {
this.searchService = new UserService()
@ -271,7 +304,7 @@ export default {
this.sharables.splice(i, 1)
}
}
this.success({message: `The ${this.shareType} was successfully deleted from the ${this.typeString}.`})
this.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
})
.catch((e) => {
this.error(e)
@ -295,12 +328,7 @@ export default {
this.stuffService
.create(this.stuffModel)
.then(() => {
this.success({
message:
'The ' +
this.shareType +
' was successfully added.',
})
this.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
this.load()
})
.catch((e) => {
@ -337,12 +365,7 @@ export default {
this.$set(this.sharables[i], 'right', r.right)
}
}
this.success({
message:
'The ' +
this.shareType +
' right was successfully updated.',
})
this.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
})
.catch((e) => {
this.error(e)

View File

@ -1,7 +1,7 @@
<template>
<form @submit.prevent="editTaskSubmit()">
<div class="field">
<label class="label" for="tasktext">Title</label>
<label class="label" for="tasktext">{{ $t('task.attributes.title') }}</label>
<div class="control">
<input
:class="{ disabled: taskService.loading }"
@ -9,7 +9,6 @@
@change="editTaskSubmit()"
class="input"
id="tasktext"
placeholder="The task text is here..."
type="text"
v-focus
v-model="taskEditTask.title"
@ -17,26 +16,26 @@
</div>
</div>
<div class="field">
<label class="label" for="taskdescription">Description</label>
<label class="label" for="taskdescription">{{ $t('task.attributes.description') }}</label>
<div class="control">
<editor
:preview-is-default="false"
id="taskdescription"
placeholder="The tasks description goes here..."
:placeholder="$t('task.description.placeholder')"
v-if="editorActive"
v-model="taskEditTask.description"
/>
</div>
</div>
<strong>Reminders</strong>
<strong>{{ $t('task.attributes.reminders') }}</strong>
<reminders
@change="editTaskSubmit()"
v-model="taskEditTask.reminderDates"
/>
<div class="field">
<label class="label">Labels</label>
<label class="label">{{ $t('task.attributes.labels') }}</label>
<div class="control">
<edit-labels
:task-id="taskEditTask.id"
@ -46,7 +45,7 @@
</div>
<div class="field">
<label class="label">Color</label>
<label class="label">{{ $t('task.attributes.color') }}</label>
<div class="control">
<color-picker v-model="taskEditTask.hexColor" />
</div>
@ -57,14 +56,14 @@
class="is-fullwidth"
@click="editTaskSubmit()"
>
Save
{{ $t('misc.save') }}
</x-button>
<router-link
class="mt-2 has-text-centered is-block"
:to="{name: 'task.detail', params: {id: taskEditTask.id}}"
>
Open task detail view
{{ $t('task.openDetail') }}
</router-link>
</form>
</template>
@ -150,7 +149,7 @@ export default {
.then((r) => {
this.$set(this, 'taskEditTask', r)
this.initTaskFields()
this.success({message: 'The task has been saved successfully.'})
this.success({message: this.$t('task.detail.updateSuccess')})
})
.catch((e) => {
this.error(e)

View File

@ -7,7 +7,7 @@
type="secondary"
icon="filter"
>
Filters
{{ $t('filters.title') }}
</x-button>
</div>
<filter-popup
@ -108,8 +108,9 @@
'has-super-high-priority':
t.priority === priorities.DO_NOW,
}"
>{{ t.title }}</span
>
{{ t.title }}
</span>
<priority-label :priority="t.priority"/>
<!-- using the key here forces vue to use the updated version model and not the response returned by the api -->
<a @click="editTask(theTasks[k])" class="edit-toggle">
@ -148,7 +149,7 @@
@resizestop="resizeTask"
axis="x"
class="task nodate"
v-tooltip="'This task has no dates set.'"
v-tooltip="$t('list.gantt.noDates')"
>
<span>{{ t.title }}</span>
</VueDragResize>
@ -172,14 +173,14 @@
/>
</transition>
<x-button @click="showCreateNewTask" :shadow="false" icon="plus">
Add a new task
{{ $t('list.list.newTaskCta') }}
</x-button>
</form>
<transition name="fade">
<card
v-if="isTaskEdit"
class="taskedit"
title="Edit Task"
:title="$t('list.list.editTask')"
@close="() => {isTaskEdit = false;taskToEdit = null}"
:has-close="true"
>

View File

@ -4,7 +4,7 @@
<span class="icon is-grey">
<icon icon="paperclip"/>
</span>
Attachments
{{ $t('task.attachment.title') }}
</h3>
<input
@ -35,18 +35,16 @@
<div class="filename">{{ a.file.name }}</div>
<div class="info">
<p class="collapses">
<span>
created
<span v-tooltip="formatDate(a.created)">{{
formatDateSince(a.created)
}}</span>
by
<i18n path="task.attachment.createdBy">
<span v-tooltip="formatDate(a.created)">
{{ formatDateSince(a.created) }}
</span>
<user
:avatar-size="24"
:user="a.createdBy"
:is-inline="true"
/>
</span>
</i18n>
<span>
{{ a.file.getHumanSize() }}
</span>
@ -59,14 +57,14 @@
@click.prevent.stop="downloadAttachment(a)"
v-tooltip="'Download this attachment'"
>
Download
{{ $t('task.attachment.download') }}
</a>
<a
@click.prevent.stop="() => {attachmentToDelete = a; showDeleteModal = true}"
v-if="editEnabled"
v-tooltip="'Delete this attachment'"
>
Delete
{{ $t('misc.delete') }}
</a>
</p>
</div>
@ -82,7 +80,7 @@
type="secondary"
:shadow="false"
>
Upload attachment
{{ $t('task.attachment.upload') }}
</x-button>
<!-- Dropzone -->
@ -95,7 +93,7 @@
<div class="icon">
<icon icon="cloud-upload-alt"/>
</div>
<div class="hint">Drop files here to upload</div>
<div class="hint">{{ $t('task.attachment.drop') }}</div>
</div>
</div>
@ -106,11 +104,10 @@
v-if="showDeleteModal"
@submit="deleteAttachment()"
>
<span slot="header">Delete attachment</span>
<span slot="header">{{ $t('task.attachment.delete') }}</span>
<p slot="text">
Are you sure you want to delete the attachment
{{ attachmentToDelete.file.name }}?<br/>
<b>This CANNOT BE UNDONE!</b>
{{ $t('task.attachment.deleteText1', {filename: attachmentUpload.file.name}) }}<br/>
<strong>{{ $t('task.attachment.deleteText2') }}</strong>
</p>
</modal>
</transition>

View File

@ -4,17 +4,15 @@
<span class="icon is-grey">
<icon :icon="['far', 'comments']"/>
</span>
Comments
{{ $t('task.comment.title') }}
</h3>
<div class="comments">
<span
class="is-inline-flex is-align-items-center"
v-if="
taskCommentService.loading && saving === null && !creating
"
v-if="taskCommentService.loading && saving === null && !creating"
>
<span class="loader is-inline-block mr-2"></span>
Loading comments...
{{ $t('task.comment.loading') }}
</span>
<div :key="c.id" class="media comment" v-for="c in comments">
<figure class="media-left is-hidden-mobile">
@ -35,8 +33,7 @@
height="20"
width="20"
/>
<strong>{{ c.author.getDisplayName() }}</strong
>&nbsp;
<strong>{{ c.author.getDisplayName() }}</strong>&nbsp;
<span v-tooltip="formatDate(c.created)" class="has-text-grey">
{{ formatDateSince(c.created) }}
</span>
@ -44,7 +41,7 @@
v-if="+new Date(c.created) !== +new Date(c.updated)"
v-tooltip="formatDate(c.updated)"
>
· edited {{ formatDateSince(c.updated) }}
· {{ $t('task.comment.edited', {date: formatDateSince(c.updated)}) }}
</span>
<transition name="fade">
<span
@ -54,10 +51,8 @@
saving === c.id
"
>
<span
class="loader is-inline-block mr-2"
></span>
Saving...
<span class="loader is-inline-block mr-2"></span>
{{ $t('misc.saving') }}
</span>
<span
class="has-text-success"
@ -66,7 +61,7 @@
saved === c.id
"
>
Saved!
{{ $t('misc.saved') }}
</span>
</transition>
</div>
@ -104,10 +99,8 @@
class="is-inline-flex"
v-if="taskCommentService.loading && creating"
>
<span
class="loader is-inline-block mr-2"
></span>
Creating comment...
<span class="loader is-inline-block mr-2"></span>
{{ $t('task.comment.creating') }}
</span>
</transition>
<div class="field">
@ -120,20 +113,18 @@
:has-preview="false"
:upload-callback="attachmentUpload"
:upload-enabled="true"
placeholder="Add your comment..."
:placeholder="$t('task.comment.placeholder')"
v-if="editorActive"
v-model="newComment.comment"
/>
</div>
<div class="field">
<x-button
:loading="
taskCommentService.loading && !isCommentEdit
"
:loading="taskCommentService.loading && !isCommentEdit"
:disabled="newComment.comment === ''"
@click="addComment()"
>
Comment
{{ $t('task.comment.comment') }}
</x-button>
</div>
</div>
@ -147,10 +138,10 @@
@submit="deleteComment()"
v-if="showDeleteModal"
>
<span slot="header">Delete this comment</span>
<span slot="header">{{ $t('task.comment.delete') }}</span>
<p slot="text">
Are you sure you want to delete this comment? <br/>This
<b>CANNOT BE UNDONE!</b>
{{ $t('task.comment.deleteText1') }}<br/>
<strong>{{ $t('task.comment.deleteText2') }}</strong>
</p>
</modal>
</transition>
@ -258,7 +249,7 @@ export default {
.then((r) => {
this.comments.push(r)
this.newComment.comment = ''
this.success({message: 'The comment was added successfully.'})
this.success({message: this.$t('task.comment.addedSuccess')})
})
.catch((e) => {
this.error(e)
@ -327,7 +318,7 @@ export default {
this.$set(this.actions, c.id, [
{
action: () => this.toggleDelete(c.id),
title: 'Remove',
title: this.$t('misc.delete'),
},
])
})

View File

@ -3,28 +3,28 @@
:class="{ 'is-loading': taskService.loading }"
class="defer-task loading-container"
>
<label class="label">Defer due date</label>
<label class="label">{{ $t('task.deferDueDate.title') }}</label>
<div class="defer-days">
<x-button
@click.prevent.stop="() => deferDays(1)"
:shadow="false"
type="secondary"
>
1 day
{{ $t('task.deferDueDate.1day') }}
</x-button>
<x-button
@click.prevent.stop="() => deferDays(3)"
:shadow="false"
type="secondary"
>
3 days
{{ $t('task.deferDueDate.3days') }}
</x-button>
<x-button
@click.prevent.stop="() => deferDays(7)"
:shadow="false"
type="secondary"
>
1 week
{{ $t('task.deferDueDate.1week') }}
</x-button>
</div>
<flat-pickr
@ -40,7 +40,6 @@
<script>
import TaskService from '../../../services/task'
import flatPickr from 'vue-flatpickr-component'
import {mapState} from 'vuex'
export default {
name: 'defer-task',
@ -94,19 +93,21 @@ export default {
this.lastValue = this.dueDate
},
},
computed: mapState({
flatPickerConfig: state => ({
altFormat: 'j M Y H:i',
altInput: true,
dateFormat: 'Y-m-d H:i',
enableTime: true,
time_24hr: true,
inline: 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,
inline: true,
locale: {
firstDayOfWeek: this.$store.state.auth.settings.weekStart,
},
}
},
},
methods: {
deferDays(days) {
this.dueDate = new Date(this.dueDate)

View File

@ -4,15 +4,15 @@
<span class="icon is-grey">
<icon icon="align-left"/>
</span>
Description
{{ $t('task.attributes.description') }}
<transition name="fade">
<span class="is-small is-inline-flex" v-if="loading && saving">
<span class="loader is-inline-block mr-2"></span>
Saving...
{{ $t('misc.saving') }}
</span>
<span class="is-small has-text-success" v-if="!loading && saved">
<icon icon="check"/>
Saved!
{{ $t('misc.saved') }}
</span>
</transition>
</h3>
@ -21,8 +21,8 @@
:upload-callback="attachmentUpload"
:upload-enabled="true"
@change="save"
placeholder="Click here to enter a description..."
empty-text="No description available yet."
:placeholder="$t('task.description.placeholder')"
:empty-text="$t('task.description.empty')"
v-model="task.description"/>
</div>
</template>

View File

@ -5,14 +5,14 @@
>
<multiselect
:loading="listUserService.loading"
placeholder="Type to assign a user..."
:placeholder="$t('task.assignee.placeholder')"
:disabled="disabled"
:multiple="true"
@search="findUser"
:search-results="foundUsers"
@select="addAssignee"
label="username"
select-placeholder="Assign this user"
:select-placeholder="$t('task.assignee.selectPlaceholder')"
v-model="assignees"
ref="multiselect"
>
@ -84,7 +84,7 @@ export default {
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
.then(() => {
this.$emit('input', this.assignees)
this.success({message: 'The user has been assigned successfully.'})
this.success({message: this.$t('task.assignee.assignSuccess')})
})
.catch(e => {
this.error(e)
@ -99,7 +99,7 @@ export default {
this.assignees.splice(a, 1)
}
}
this.success({message: 'The user has been unassinged successfully.'})
this.success({message: this.$t('task.assignee.assignSuccess')})
})
.catch(e => {
this.error(e)

View File

@ -1,7 +1,7 @@
<template>
<multiselect
:loading="loading"
placeholder="Type to add a new label..."
:placeholder="$t('task.label.placeholder')"
:multiple="true"
@search="findLabel"
:search-results="foundLabels"
@ -9,7 +9,7 @@
label="title"
:creatable="true"
@create="createAndAddLabel"
create-placeholder="Add this as new label"
:create-placeholder="$t('task.label.createPlaceholder')"
v-model="labels"
:search-delay="10"
>
@ -104,7 +104,7 @@ export default {
.then(() => {
this.$emit('input', this.labels)
if (showNotification) {
this.success({message: 'The label has been added successfully.'})
this.success({message: this.$t('task.label.addSuccess')})
}
})
.catch(e => {
@ -121,7 +121,7 @@ export default {
}
}
this.$emit('input', this.labels)
this.success({message: 'The label has been removed successfully.'})
this.success({message: this.$t('task.label.removeSuccess')})
})
.catch(e => {
this.error(e)
@ -133,7 +133,7 @@ export default {
.then(r => {
this.addLabel(r, false)
this.labels.push(r)
this.success({message: 'The label has been created successfully.'})
this.success({message: this.$t('task.label.removeSuccess')})
})
.catch(e => {
this.error(e)

View File

@ -14,11 +14,11 @@
<transition name="fade">
<span class="is-inline-flex is-align-items-center" v-if="loading && saving">
<span class="loader is-inline-block mr-2"></span>
Saving...
{{ $t('misc.saving') }}
</span>
<span class="has-text-success is-inline-flex is-align-content-center" v-if="!loading && saved">
<icon icon="check" class="mr-2"/>
Saved!
{{ $t('misc.saved') }}
</span>
</transition>
</div>

View File

@ -3,13 +3,13 @@
class="control is-expanded"
v-focus
:loading="listSerivce.loading"
placeholder="Type to search for a list..."
:placeholder="$t('list.search')"
@search="findLists"
:search-results="foundLists"
@select="select"
label="title"
v-model="list"
select-placeholder="Click or press enter to select this list"
:select-placeholder="$t('list.searchSelect')"
>
<template v-slot:searchResult="props">
<span class="list-namespace-title">{{ namespace(props.option.namespaceId) }} ></span>
@ -65,7 +65,7 @@ export default {
if (namespace !== null) {
return namespace.title
}
return 'Shared Lists'
return this.$t('list.shared')
},
},
}

View File

@ -7,12 +7,12 @@
<icon icon="exclamation"/>
</span>
<span>
<template v-if="priority === priorities.UNSET">Unset</template>
<template v-if="priority === priorities.LOW">Low</template>
<template v-if="priority === priorities.MEDIUM">Medium</template>
<template v-if="priority === priorities.HIGH">High</template>
<template v-if="priority === priorities.URGENT">Urgent</template>
<template v-if="priority === priorities.DO_NOW">DO NOW</template>
<template v-if="priority === priorities.UNSET">{{ $t('task.priority.unset') }}</template>
<template v-if="priority === priorities.LOW">{{ $t('task.priority.low') }}</template>
<template v-if="priority === priorities.MEDIUM">{{ $t('task.priority.medium') }}</template>
<template v-if="priority === priorities.HIGH">{{ $t('task.priority.high') }}</template>
<template v-if="priority === priorities.URGENT">{{ $t('task.priority.urgent') }}</template>
<template v-if="priority === priorities.DO_NOW">{{ $t('task.priority.doNow') }}</template>
</span>
<span class="icon" v-if="priority === priorities.DO_NOW">
<icon icon="exclamation"/>

View File

@ -1,12 +1,12 @@
<template>
<div class="select">
<select :disabled="disabled" @change="updateData" v-model="priority">
<option :value="priorities.UNSET">Unset</option>
<option :value="priorities.LOW">Low</option>
<option :value="priorities.MEDIUM">Medium</option>
<option :value="priorities.HIGH">High</option>
<option :value="priorities.URGENT">Urgent</option>
<option :value="priorities.DO_NOW">DO NOW</option>
<option :value="priorities.UNSET">{{ $t('task.priority.unset') }}</option>
<option :value="priorities.LOW">{{ $t('task.priority.low') }}</option>
<option :value="priorities.MEDIUM">{{ $t('task.priority.medium') }}</option>
<option :value="priorities.HIGH">{{ $t('task.priority.high') }}</option>
<option :value="priorities.URGENT">{{ $t('task.priority.urgent') }}</option>
<option :value="priorities.DO_NOW">{{ $t('task.priority.doNow') }}</option>
</select>
</div>
</template>

View File

@ -5,7 +5,7 @@
@click="showNewRelationForm = !showNewRelationForm"
class="is-pulled-right add-task-relation-button"
:class="{'is-active': showNewRelationForm}"
v-tooltip="'Add a New Task Relation'"
v-tooltip="$t('task.relation.add')"
type="secondary"
icon="plus"
:shadow="false"
@ -13,27 +13,27 @@
<transition-group name="fade">
<template v-if="editEnabled && showCreate">
<label class="label" key="label">
New Task Relation
{{ $t('task.relation.new') }}
<transition name="fade">
<span class="is-inline-flex" v-if="taskRelationService.loading">
<span class="loader is-inline-block mr-2"></span>
Saving...
{{ $t('misc.saving') }}
</span>
<span class="has-text-success" v-if="!taskRelationService.loading && saved">
Saved!
{{ $t('misc.saved') }}
</span>
</transition>
</label>
<div class="field" key="field-search">
<multiselect
placeholder="Type search for a new task to add as related..."
:placeholder="$t('task.relation.searchPlaceholder')"
@search="findTasks"
:loading="taskService.loading"
:search-results="foundTasks"
label="title"
v-model="newTaskRelationTask"
:creatable="true"
create-placeholder="Add this as new related task"
:create-placeholder="$t('task.relation.createPlaceholder')"
@create="createAndRelateTask"
>
<template v-slot:searchResult="props">
@ -41,7 +41,7 @@
<span
class="different-list"
v-if="props.option.listId !== listId"
v-tooltip="'This task belongs to a different list.'">
v-tooltip="$t('task.relation.differentList')">
{{ $store.getters['lists/getListById'](props.option.listId) === null ? '' : $store.getters['lists/getListById'](props.option.listId).title }} >
</span>
{{ props.option.title }}
@ -64,7 +64,7 @@
</div>
</div>
<div class="control">
<x-button @click="addTaskRelation()">Add Task Relation</x-button>
<x-button @click="addTaskRelation()">{{ $t('task.relation.add') }}</x-button>
</div>
</div>
</template>
@ -80,7 +80,7 @@
<span
class="different-list"
v-if="t.listId !== listId"
v-tooltip="'This task belongs to a different list.'">
v-tooltip="$t('task.relation.differentList')">
{{
$store.getters['lists/getListById'](t.listId) === null ? '' : $store.getters['lists/getListById'](t.listId).title
}} >
@ -99,7 +99,7 @@
</template>
</div>
<p class="none" v-if="showNoRelationsNotice && Object.keys(relatedTasks).length === 0">
No task relations yet.
{{ $t('task.relation.noneYet') }}
</p>
<!-- Delete modal -->
@ -108,9 +108,11 @@
@close="showDeleteModal = false"
@submit="removeTaskRelation()"
v-if="showDeleteModal">
<span slot="header">Delete Task Relation</span>
<p slot="text">Are you sure you want to delete this task relation?<br/>
<b>This CANNOT BE UNDONE!</b></p>
<span slot="header">{{ $t('task.relation.delete') }}</span>
<p slot="text">
{{ $t('task.relation.deleteText1') }}<br/>
<strong>{{ $t('task.relation.deleteText2') }}</strong>
</p>
</modal>
</transition>
</div>

View File

@ -19,7 +19,7 @@
<datepicker
v-model="newReminder"
@close-on-change="() => addReminderDate()"
choose-date-label="Add a new reminder..."
:choose-date-label="$t('task.addReminder')"
/>
</div>
</div>

View File

@ -1,27 +1,27 @@
<template>
<div class="control repeat-after-input">
<div class="buttons has-addons is-centered mt-2">
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'days')">Every Day</x-button>
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'weeks')">Every Week</x-button>
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'months')">Every Month</x-button>
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'days')">{{ $t('task.repeat.everyDay') }}</x-button>
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'weeks')">{{ $t('task.repeat.everyWeek') }}</x-button>
<x-button type="secondary" class="is-small" @click="() => setRepeatAfter(1, 'months')">{{ $t('task.repeat.everyMonth') }}</x-button>
</div>
<div class="is-flex is-align-items-center mb-2">
<label for="repeatMode" class="is-fullwidth">
Repeat mode:
{{ $t('task.repeat.mode') }}:
</label>
<div class="control">
<div class="select">
<select @change="updateData" v-model="task.repeatMode" id="repeatMode">
<option :value="repeatModes.REPEAT_MODE_DEFAULT">Default</option>
<option :value="repeatModes.REPEAT_MODE_MONTH">Monthly</option>
<option :value="repeatModes.REPEAT_MODE_FROM_CURRENT_DATE">From Current Date</option>
<option :value="repeatModes.REPEAT_MODE_DEFAULT">{{ $t('misc.default') }}</option>
<option :value="repeatModes.REPEAT_MODE_MONTH">{{ $t('task.repeat.monthly') }}</option>
<option :value="repeatModes.REPEAT_MODE_FROM_CURRENT_DATE">{{ $t('task.repeat.fromCurrentDate') }}</option>
</select>
</div>
</div>
</div>
<div class="is-flex" v-if="task.repeatMode !== repeatModes.REPEAT_MODE_MONTH">
<p class="pr-4">
Each
{{ $t('task.repeat.each') }}
</p>
<div class="field has-addons is-fullwidth">
<div class="control">
@ -29,7 +29,7 @@
:disabled="disabled"
@change="updateData"
class="input"
placeholder="Specify an amount..."
:placeholder="$t('task.repeat.specifyAmount')"
v-model="repeatAfter.amount"
type="number"
/>
@ -37,11 +37,11 @@
<div class="control">
<div class="select">
<select :disabled="disabled" @change="updateData" v-model="repeatAfter.type">
<option value="hours">Hours</option>
<option value="days">Days</option>
<option value="weeks">Weeks</option>
<option value="months">Months</option>
<option value="years">Years</option>
<option value="hours">{{ $t('task.repeat.hours') }}</option>
<option value="days">{{ $t('task.repeat.days') }}</option>
<option value="weeks">{{ $t('task.repeat.weeks') }}</option>
<option value="months">{{ $t('task.repeat.months') }}</option>
<option value="years">{{ $t('task.repeat.years') }}</option>
</select>
</div>
</div>

View File

@ -16,7 +16,7 @@
:to="{ name: 'list.list', params: { listId: task.listId } }"
class="task-list"
v-if="showList && $store.getters['lists/getListById'](task.listId) !== null"
v-tooltip="`This task belongs to list '${$store.getters['lists/getListById'](task.listId).title}'`">
v-tooltip="$t('task.detail.belongsToList', {list: $store.getters['lists/getListById'](task.listId).title})">
{{ $store.getters['lists/getListById'](task.listId).title }}
</router-link>
@ -45,7 +45,7 @@
v-if="+new Date(task.dueDate) > 0"
v-tooltip="formatDate(task.dueDate)"
>
- Due {{ formatDateSince(task.dueDate) }}
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
</i>
<transition name="fade">
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
@ -70,7 +70,7 @@
:to="{ name: 'list.list', params: { listId: task.listId } }"
class="task-list"
v-if="!showList && currentList.id !== task.listId && $store.getters['lists/getListById'](task.listId) !== null"
v-tooltip="`This task belongs to list '${$store.getters['lists/getListById'](task.listId).title}'`">
v-tooltip="$t('task.detail.belongsToList', {list: $store.getters['lists/getListById'](task.listId).title})">
{{ $store.getters['lists/getListById'](task.listId).title }}
</router-link>
<a
@ -175,7 +175,11 @@ export default {
}
this.task = t
this.$emit('task-updated', t)
this.success({message: 'The task was successfully ' + (this.task.done ? '' : 'un-') + 'marked as done.'}, [{
this.success({
message: this.task.done ?
this.$t('task.doneSuccess') :
this.$t('task.undoneSuccess')
}, [{
title: 'Undo',
callback: () => {
this.task.done = !this.task.done

View File

@ -1,21 +1,21 @@
<template>
<card title="Avatar">
<card :title="$t('user.settings.avatar.title')">
<div class="control mb-4">
<label class="radio">
<input name="avatarProvider" type="radio" v-model="avatarProvider" value="default"/>
Default
{{ $t('misc.default') }}
</label>
<label class="radio">
<input name="avatarProvider" type="radio" v-model="avatarProvider" value="initials"/>
Initials
{{ $t('user.settings.avatar.initials') }}
</label>
<label class="radio">
<input name="avatarProvider" type="radio" v-model="avatarProvider" value="gravatar"/>
Gravatar
{{ $t('user.settings.avatar.gravatar') }}
</label>
<label class="radio">
<input name="avatarProvider" type="radio" v-model="avatarProvider" value="upload"/>
Upload
{{ $t('user.settings.avatar.upload') }}
</label>
</div>
@ -31,7 +31,7 @@
:loading="avatarService.loading || loading"
@click="$refs.avatarUploadInput.click()"
v-if="!isCropAvatar">
Upload Avatar
{{ $t('user.settings.avatar.uploadAvatar') }}
</x-button>
<template v-else>
<cropper
@ -45,7 +45,7 @@
:loading="avatarService.loading || loading"
@click="uploadAvatar"
>
Upload Avatar
{{ $t('user.settings.avatar.uploadAvatar') }}
</x-button>
</template>
</template>
@ -56,7 +56,7 @@
@click="updateAvatarStatus()"
class="is-fullwidth"
>
Save
{{ $t('misc.save') }}
</x-button>
</div>
</card>
@ -99,7 +99,7 @@ export default {
const avatarStatus = new AvatarModel({avatarProvider: this.avatarProvider})
this.avatarService.update(avatarStatus)
.then(() => {
this.success({message: 'Avatar status was updated successfully!'})
this.success({message: this.$t('user.settings.avatar.statusUpdateSuccess')})
this.$store.commit('auth/reloadAvatar')
})
.catch(e => this.error(e))
@ -112,7 +112,7 @@ export default {
canvas.toBlob(blob => {
this.avatarService.create(blob)
.then(() => {
this.success({message: 'The avatar has been set successfully!'})
this.success({message: this.$t('user.settings.avatar.setSuccess')})
this.$store.commit('auth/reloadAvatar')
})
.catch(e => this.error(e))

View File

@ -0,0 +1,40 @@
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {format, formatDistance} from 'date-fns'
import { enGB, de } from 'date-fns/locale'
const locales = {enGB, de}
const dateIsValid = date => {
if (date === null) {
return false
}
return date instanceof Date && !isNaN(date)
}
export const formatDate = (date, f, locale) => {
if (!dateIsValid(date)) {
return ''
}
date = createDateFromString(date)
return date ? format(date, f, {locale: locales[locale]}) : ''
}
export const formatDateSince = (date, $t) => {
if (!dateIsValid(date)) {
return ''
}
date = createDateFromString(date)
const currentDate = new Date()
const distance = formatDistance(date, currentDate)
if (date > currentDate) {
return $t('date.in', {date: distance})
}
return $t('date.ago', {date: distance})
}

309
src/i18n/lang/de.json Normal file
View File

@ -0,0 +1,309 @@
{
"404": {
"title": "Nicht gefunden",
"text": "Die angeforderte Seite existiert nicht."
},
"filters": {
"create": {
"action": "Neuen gespeicherten Filter erstellen",
"description": "Ein gespeicherter Filter ist eine virtuelle Liste, die bei jedem Zugriff aus einem Satz von Filtern errechnet wird. Einmal erstellt, erscheint er in einem speziellen Namensraum.",
"title": "Einen gespeicherten Filter erstellen"
},
"attributes": {
"descriptionPlaceholder": "Die Beschreibung steht hier …",
"description": "Beschreibung",
"titlePlaceholder": "Der gespeicherte Filtertitel steht hier …",
"title": "Titel"
},
"delete": {
"header": "Diesen gespeicherten Filter löschen",
"success": "Der Filter wurde erfolgreich gelöscht."
},
"edit": {
"success": "Der Filter wurde erfolgreich gespeichert.",
"title": "Diesen gespeicherten Filter bearbeiten"
},
"title": "Filter"
},
"sharing": {
"authenticating": "Authentifizierung …",
"invalidPassword": "Das Passwort ist ungültig.",
"error": "Es ist ein Fehler aufgetreten."
},
"label": {
"attributes": {
"color": "Farbe",
"description": "Beschreibung",
"title": "Titel"
}
},
"misc": {
"search": "Suchen",
"copy": "In Zwischenablage kopieren",
"disable": "Deaktivieren",
"confirm": "Bestätigen",
"delete": "Löschen",
"save": "Speichern",
"loading": "Wird geladen …",
"previous": "Vorherige",
"next": "Weiter"
},
"task": {
"delete": "Diese Aufgabe löschen",
"new": "Eine neue Aufgabe erstellen",
"task": "Aufgabe",
"show": {
"titleCurrent": "Aktuelle Aufgaben",
"noTasks": "Nichts zu tun Einen schönen Tag noch!",
"today": "Heute",
"nextWeek": "Nächste Woche"
},
"detail": {
"created": "Erstellt {0} von {1}",
"undone": "Als unerledigt markieren",
"done": "Fertig!",
"move": "Aufgabe in eine andere Liste verschieben",
"delete": {
"header": "Diese Aufgabe löschen"
},
"deleteSuccess": "Die Aufgabe wurde erfolgreich gelöscht.",
"updateSuccess": "Die Aufgabe wurde erfolgreich gespeichert.",
"doneAt": "Erledigt {0}",
"updated": "Aktualisiert {0}",
"actions": {
"priority": "Priorität einstellen",
"reminders": "Erinnerungen einstellen",
"relatedTasks": "Aufgabenbeziehungen hinzufügen",
"attachments": "Anhänge hinzufügen",
"delete": "Aufgabe löschen",
"color": "Taskfarbe einstellen",
"moveList": "Aufgabe verschieben"
}
},
"attributes": {
"color": "Farbe",
"done": "Fertig",
"createdBy": "Erstellt von",
"created": "Erstellt",
"endDate": "Enddatum",
"dueDate": "Fälligkeitsdatum",
"title": "Titel",
"startDate": "Anfangsdatum",
"relatedTasks": "Verwandte Aufgaben",
"priority": "Priorität",
"percentDone": "% erledigt",
"repeat": "Wiederholen",
"reminders": "Erinnerungen",
"updated": "Aktualisiert"
}
},
"team": {
"edit": {
"delete": {
"header": "Team löschen",
"success": "Das Team wurde erfolgreich gelöscht."
},
"madeAdmin": "Das Teammitglied wurde erfolgreich zum Admin gemacht.",
"madeMember": "Das Teammitglied wurde erfolgreich zum Mitglied gemacht.",
"userAddedSuccess": "Das Teammitglied wurde erfolgreich hinzugefügt.",
"success": "Das Team wurde erfolgreich aktualisiert.",
"addUser": "Zum Team hinzufügen",
"members": "Teammitglieder",
"title": "Team „{team}“ bearbeiten",
"deleteUser": {
"header": "Benutzer aus dem Team entfernen"
}
},
"create": {
"success": "Das Team wurde erfolgreich erstellt.",
"title": "Ein neues Team erstellen"
},
"title": "Teams",
"attributes": {
"description": "Beschreibung",
"descriptionPlaceholder": "Die Beschreibung des Teams steht hier …",
"member": "Mitglied",
"admin": "Admin",
"name": "Teamname",
"namePlaceholder": "Der Name des Teams steht hier …"
}
},
"namespace": {
"create": {
"explanation": "Ein Namensraum ist eine Sammlung von Listen, die man teilen und verwenden kann, um seine Listen zu organisieren. Tatsächlich gehört jede Liste zu einem Namensraum.",
"success": "Der Namensraum wurde erfolgreich angelegt.",
"tooltip": "Was ist ein Namensraum?",
"title": "Einen neuen Namensraum erstellen"
},
"attributes": {
"isArchived": "Dieser Namensraum wird archiviert",
"archived": "Ist archiviert",
"color": "Farbe",
"descriptionPlaceholder": "Die Beschreibung des Namensraums steht hier …",
"description": "Beschreibung",
"titlePlaceholder": "Der Titel des Namensraums steht hier …",
"title": "Namensraumtitel"
},
"share": {
"title": "„{namespace}“ teilen"
},
"edit": {
"success": "Der Namensraum wurde erfolgreich aktualisiert.",
"title": "„{namespace}“ bearbeiten"
},
"delete": {
"success": "Der Namensraum wurde erfolgreich gelöscht.",
"text2": "Dies umfasst alle Listen und Aufgaben und kann NICHT rückgängig gemacht werden!",
"title": "„{namespace}“ löschen"
},
"archive": {
"description": "Wenn ein Namensraum archiviert ist, kann man keine neuen Listen erstellen oder ihn bearbeiten.",
"success": "Der Namensraum wurde erfolgreich archiviert.",
"titleUnarchive": "Archivierung von „{namespace}“ aufheben",
"titleArchive": "„{namespace}“ archivieren"
},
"noLists": "Dieser Namensraum enthält keine Listen.",
"title": "Namensräume & Listen",
"unarchive": "Archivierung aufheben",
"archived": "Archiviert",
"showArchived": "Archivierte anzeigen"
},
"list": {
"kanban": {
"bucketTitleSavedSuccess": "Der Eimertitel wurde erfolgreich gespeichert.",
"deleteBucketSuccess": "Der Eimer wurde erfolgreich gelöscht.",
"deleteBucketText2": "Dies löscht keine Aufgaben, sondern verschiebt sie in den Standard-Eimer.",
"deleteHeaderBucket": "Den Eimer löschen",
"addBucket": "Einen neuen Eimer erstellen",
"addAnotherTask": "Weitere Aufgabe hinzufügen",
"addTask": "Eine Aufgabe hinzufügen",
"doneBucket": "Erledigte-Dinge-Eimer",
"noLimit": "Nicht eingestellt"
},
"table": {
"columns": "Spalten",
"title": "Tabelle"
},
"gantt": {
"to": "An",
"from": "Von",
"day": "Tag",
"month": "Monat",
"default": "Standard",
"size": "Größe",
"showTasksWithoutDates": "Aufgaben anzeigen, für die keine Termine festgelegt sind",
"title": "Gantt"
},
"list": {
"empty": "Diese Liste ist derzeit leer.",
"addPlaceholder": "Eine neue Aufgabe hinzufügen …",
"add": "Hinzufügen",
"title": "Liste"
},
"share": {
"title": "„{Liste}“ teilen",
"header": "Diese Liste teilen"
},
"edit": {
"success": "Die Liste wurde erfolgreich aktualisiert.",
"color": "Farbe",
"descriptionPlaceholder": "Die Listenbeschreibung geht hier …",
"description": "Beschreibung",
"identifierPlaceholder": "Der Listenbezeichner geht hier …",
"identifier": "Listebezeichner",
"identifierTooltip": "Der Listenbezeichner kann zur eindeutigen Identifizierung einer Aufgabe über Listen hinweg verwendet werden. Man kann ihn auf leer setzen, um ihn zu deaktivieren.",
"titlePlaceholder": "Der Titel der Liste steht hier …",
"title": "„{list}“ bearbeiten",
"header": "Diese Liste bearbeiten"
},
"duplicate": {
"success": "Die Liste wurde erfolgreich dupliziert.",
"label": "Duplizieren",
"title": "Diese Liste duplizieren"
},
"delete": {
"success": "Die Liste wurde erfolgreich gelöscht.",
"text2": "Dies umfasst alle Aufgaben und kann NICHT rückgängig gemacht werden!",
"header": "Diese Liste löschen",
"title": "„{list}“ löschen"
},
"background": {
"removeSuccess": "Der Hintergrund ist erfolgreich entfernt worden!",
"success": "Der Hintergrund ist erfolgreich eingestellt worden!",
"loadMore": "Mehr Fotos laden",
"poweredByUnsplash": "Angetrieben von Unsplash",
"searchPlaceholder": "Nach einem Hintergrund suchen …",
"remove": "Hintergrund entfernen",
"title": "Listenhintergrund festlegen"
},
"archive": {
"success": "Die Liste wurde erfolgreich archiviert.",
"unarchive": "Archivierung dieser Liste aufheben",
"archive": "Diese Liste archivieren",
"title": "„{Liste}“ archivieren"
},
"create": {
"createdSuccess": "Die Liste wurde erfolgreich erstellt.",
"titlePlaceholder": "Der Titel der Liste steht hier …",
"header": "Eine neue Liste erstellen"
},
"color": "Farbe"
},
"user": {
"settings": {
"caldav": {
"title": "Caldav"
},
"totp": {
"disableSuccess": "Die Zwei-Faktor-Authentifizierung wurde erfolgreich deaktiviert.",
"passcode": "Passcode",
"enroll": "Einschreiben",
"title": "Zwei-Faktor-Authentifizierung"
},
"general": {
"weekStartMonday": "Montag",
"weekStartSunday": "Sonntag",
"weekStart": "Woche beginnt am",
"playSoundWhenDone": "Einen Ton abspielen, wenn Aufgaben als erledigt markiert werden",
"discoverableByEmail": "Andere Benutzer mich finden lassen, wenn sie nach meiner vollständigen E-Mail suchen",
"discoverableByName": "Andere Benutzer mich finden lassen, wenn sie nach meinem Namen suchen",
"overdueReminders": "Mir jeden Morgen Erinnerungen für überfällige unerledigte Aufgaben per E-Mail senden",
"emailReminders": "Mir Erinnerungen für Aufgaben per E-Mail senden",
"savedSuccess": "Die Einstellungen wurden erfolgreich aktualisiert.",
"newName": "Der neue Name",
"name": "Name",
"title": "Allgemeine Einstellungen"
},
"updateEmailNew": "Neue E-Mail-Adresse",
"passwordUpdateSuccess": "Das Passwort wurde erfolgreich aktualisiert.",
"passwordsDontMatch": "Das neue Passwort und seine Bestätigung stimmen nicht überein.",
"currentPassword": "Aktuelles Passwort",
"newPasswordConfirm": "Neue Passwortbestätigung",
"newPassword": "Neues Passwort",
"title": "Einstellungen"
},
"auth": {
"openIdStateError": "Zustand stimmt nicht überein, weigert sich fortzufahren!",
"authenticating": "Authentifizierung …",
"loginWith": "Mit {provider} anmelden",
"register": "Registrieren",
"login": "Anmelden",
"totpPlaceholder": "z.B. 123456",
"totpTitle": "Zwei-Faktor-Authentifizierungscode",
"passwordsDontMatch": "Passwörter stimmen nicht überein",
"passwordPlaceholder": "z.B. •••••••••••",
"password": "Passwort",
"emailPlaceholder": "z.B. frederic@vikunja.io",
"email": "E-Mail-Adresse",
"usernamePlaceholder": "z.B. frederick",
"usernameEmail": "Benutzername oder E-Mail-Adresse",
"username": "Benutzername"
}
},
"home": {
"list": {
"new": "Eine neue Liste erstellen"
},
"welcome": "Hallo {username}"
}
}

795
src/i18n/lang/en.json Normal file
View File

@ -0,0 +1,795 @@
{
"home": {
"welcome": "Hi {username}",
"list": {
"newText": "You can create a new list for your new tasks:",
"new": "Create a new list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
"title": "Not found",
"text": "The page you requested does not exist."
},
"user": {
"auth": {
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"emailPlaceholder": "e.g. frederic@vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"resetPassword": "Reset your password",
"resetPasswordAction": "Send me a password reset link",
"restPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
"passwordsDontMatch": "Passwords don't match",
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"logout": "Logout"
},
"settings": {
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current Password",
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
"title": "General Settings",
"name": "Name",
"newName": "The new Name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"overdueReminders": "Send me reminders for overdue undone tasks via email each morning",
"discoverableByName": "Let other users find me when they search for my name",
"discoverableByEmail": "Let other users find me when they search for my full email",
"playSoundWhenDone": "Play a sound when marking tasks as done",
"weekStart": "Week starts on",
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language"
},
"totp": {
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"finishSetupPart2": "After that, enter a code from your app below.",
"scanQR": "Alternatively you can scan this QR code:",
"passcode": "Passcode",
"passcodePlaceholder": "A code generated by your totp application",
"setupSuccess": "You've sucessfully set up two factor authentication!",
"enterPassword": "Please Enter Your Password",
"disable": "Disable two factor authentication",
"confirmSuccess": "You've successfully confirmed your totp setup and can use it from now on!",
"disableSuccess": "Two factor authentication was sucessfully disabled."
},
"caldav": {
"title": "Caldav",
"howTo": "You can connect Vikunja to caldav clients to view and manage all tasks from different clients. Enter this url into your client:",
"more": "More information about caldav in Vikunja"
},
"avatar": {
"title": "Avatar",
"initials": "Initials",
"gravatar": "Gravatar",
"upload": "Upload",
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
}
}
},
"list": {
"archived": "This list is archived. It is not possible to create new or edit tasks or it.",
"title": "List Title",
"color": "Color",
"lists": "Lists",
"search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists",
"create": {
"header": "Create a new list",
"titlePlaceholder": "The list's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The list was successfully created."
},
"archive": {
"title": "Archive \"{list}\"",
"archive": "Archive this list",
"unarchive": "Un-Archive this list",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this list or create new tasks until you un-archive it.",
"success": "The list was successfully archived."
},
"background": {
"title": "Set list background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Delete \"{list}\"",
"header": "Delete this list",
"text1": "Are you sure you want to delete this list and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The list was successfully deleted."
},
"duplicate": {
"title": "Duplicate this list",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated list:",
"success": "The list was successfully duplicated."
},
"edit": {
"header": "Edit This List",
"title": "Edit \"{list}\"",
"titlePlaceholder": "The list title goes here…",
"identifierTooltip": "The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.",
"identifier": "List Identifier",
"identifierPlaceholder": "The list identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The lists description goes here…",
"color": "Color",
"success": "The list was successfully updated."
},
"share": {
"header": "Share this list",
"title": "Share \"{list}\"",
"share": "Share",
"links": {
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a list with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted"
},
"userTeam": {
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Right",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Link",
"name": "Name",
"sharedBy": "Shared by",
"right": "Right",
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"addTitleRequired": "Please specify a title.",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"from": "From",
"to": "To",
"noDates": "This task has no dates set."
},
"table": {
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Limit: {limit}",
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "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.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully."
}
},
"namespace": {
"title": "Namespaces & Lists",
"namespace": "Namespace",
"showArchived": "Show Archived",
"noneAvailable": "You don't have any namespaces right now.",
"unarchive": "Un-Archive",
"archived": "Archived",
"noLists": "This namespace does not contain any lists.",
"createList": "Create a new list in this namespace.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"create": {
"title": "Create a new namespace",
"titleRequired": "Please specify a title.",
"explanation": "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.",
"tooltip": "What's a namespace?",
"success": "The namespace was successfully created."
},
"archive": {
"titleArchive": "Archive \"{namespace}\"",
"titleUnarchive": "Un-Archive \"{namespace}\"",
"archiveText": "You won't be able to edit this namespace or create new lists until you un-archive it. This will also archive all lists in this namespace.",
"unarchiveText": "You will be able to create new lists or edit it.",
"success": "The namespace was successfully archived.",
"description": "If a namespace is archived, you cannot create new lists or edit it."
},
"delete": {
"title": "Delete \"{namespace}\"",
"text1": "Are you sure you want to delete this namespace and all of its contents?",
"text2": "This includes all lists and tasks and CANNOT BE UNDONE!",
"success": "The namespace was successfully deleted."
},
"edit": {
"title": "Edit \"{namespace}\"",
"success": "The namespace was successfully updated."
},
"share": {
"title": "Share \"{namespace}\""
},
"attributes": {
"title": "Namespace Title",
"titlePlaceholder": "The namespace title goes here…",
"description": "Description",
"descriptionPlaceholder": "The namespaces description goes here…",
"color": "Color",
"archived": "Is Archived",
"isArchived": "This namespace is archived"
}
},
"filters": {
"title": "Filters",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
"description": "Description",
"descriptionPlaceholder": "The description goes here…",
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",
"startDateRange": "Start Date Range",
"endDateRange": "End Date Range",
"reminderRange": "Reminder Date Range"
},
"create": {
"title": "Create A Saved Filter",
"description": "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.",
"action": "Create new saved filter"
},
"delete": {
"header": "Delete this saved filter",
"text": "Are you sure you want to delete this saved filter?",
"success": "The filter was deleted successfully."
},
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
}
},
"migrate": {
"title": "Migrate from other services to Vikunja",
"titleService": "Import your data from {name} into Vikunja",
"import": "Import your data into Vikunja",
"description": "Click on the logo of one of the third-party services below to get started.",
"descriptionDo": "Vikunja will import all lists, tasks, notes, reminders and files you have access to.",
"authorize": "To authorize Vikunja to access your {name} Account, click the button below.",
"getStarted": "Get Started",
"inProgress": "Importing in progress…",
"alreadyMigrated1": "It looks like you've already imported your stuff from {name} at {date}.",
"alreadyMigrated2": "Importing again is possible, but might create duplicates. Are you sure?",
"confirm": "I am sure, please start migrating now!"
},
"label": {
"title": "Labels",
"manage": "Manage labels",
"description": "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.",
"newCTA": "You currently do not have any labels.",
"search": "Type to search for a label…",
"create": {
"header": "New label",
"title": "Create a new label",
"titleRequired": "Please specify a title.",
"success": "The label was successfully created."
},
"edit": {
"header": "Edit Label",
"forbidden": "You are not allowed to edit this label because you dont own it.",
"success": "The label was successfully updated."
},
"deleteSuccess": "The label was successfully deleted.",
"attributes": {
"title": "Title",
"titlePlaceholder": "The label title goes here…",
"description": "Description",
"descriptionPlaceholder": "Label description",
"color": "Color"
}
},
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared list requires a password. Please enter it below:",
"error": "An error occured.",
"invalidPassword": "The password is invalid."
},
"navigation": {
"overview": "Overview",
"upcoming": "Upcoming",
"settings": "Settings",
"imprint": "Imprint",
"privacy": "Privacy Policy"
},
"misc": {
"loading": "Loading…",
"save": "Save",
"delete": "Delete",
"confirm": "Confirm",
"cancel": "Cancel",
"refresh": "Refresh",
"disable": "Disable",
"copy": "Copy to clipboard",
"search": "Search",
"searchPlaceholder": "Type to search…",
"previous": "Previous",
"next": "Next",
"poweredBy": "Powered by Vikunja",
"info": "Info",
"create": "Create",
"doit": "Do it!",
"saving": "Saving…",
"saved": "Saved!",
"default": "Default"
},
"input": {
"resetColor": "Reset Color",
"datepicker": {
"today": "Today",
"tomorrow": "Tomorrow",
"nextMonday": "Next Monday",
"thisWeekend": "This Weekend",
"laterThisWeek": "Later This Week",
"nextWeek": "Next Week",
"chooseDate": "Choose a date"
},
"editor": {
"done": "Done",
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"headingSmaller": "Heading Smaller",
"headingBigger": "Heading Bigger",
"bold": "Bold",
"italic": "Italic",
"strikethrough": "Strikethrough",
"code": "Code",
"quote": "Quote",
"unorderedList": "Unordered List",
"orderedList": "Ordered List",
"cleanBlock": "Clean Block",
"link": "Link",
"image": "Image",
"table": "Table",
"horizontalRule": "Horizontal Rule",
"sideBySide": "Side By Side",
"guide": "Guide"
},
"multiselect": {
"createPlaceholder": "Create new",
"selectPlaceholder": "Click or press enter to select"
}
},
"task": {
"task": "Task",
"new": "Create a new task",
"delete": "Delete this task",
"createSuccess": "The task was successfully created.",
"addReminder": "Add a new reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
"noDates": "Show tasks without dates",
"current": "Current tasks",
"from": "Tasks from",
"until": "until",
"today": "Today",
"nextWeek": "Next Week",
"nextMonth": "Next Month",
"noTasks": "Nothing to do - Have a nice day!"
},
"detail": {
"chooseDueDate": "Click here to set a due date",
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToList": "This task belongs to list '{list}'",
"due": "Due {at}",
"delete": {
"header": "Delete this task",
"text1": "Are you sure you want to remove this task?",
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
},
"actions": {
"assign": "Assign this task to a user",
"label": "Add labels",
"priority": "Set Priority",
"dueDate": "Set Due Date",
"startDate": "Set a Start Date",
"endDate": "Set an End Date",
"reminders": "Set Reminders",
"repeatAfter": "Set a repeating interval",
"percentDone": "Set Percent Done",
"attachments": "Add attachments",
"relatedTasks": "Add task relations",
"moveList": "Move task",
"color": "Set task color",
"delete": "Delete task"
}
},
"attributes": {
"assignees": "Assignees",
"color": "Color",
"created": "Created",
"createdBy": "Created By",
"description": "Description",
"done": "Done",
"dueDate": "Due Date",
"endDate": "End Date",
"labels": "Labels",
"percentDone": "% Done",
"priority": "Priority",
"relatedTasks": "Related Tasks",
"reminders": "Reminders",
"repeat": "Repeat",
"startDate": "Start Date",
"title": "Title",
"updated": "Updated"
},
"subscription": {
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
},
"attachment": {
"title": "Attachments",
"createdBy": "created {0} by {1}",
"download": "Download",
"upload": "Upload attachment",
"drop": "Drop files here to upload",
"delete": "Delete attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?",
"deleteText2": "This cannot be undone!"
},
"comment": {
"title": "Comments",
"loading": "Loading comments…",
"edited": "edited {date}",
"creating": "Creating comment…",
"placeholder": "Add your comment…",
"comment": "Comment",
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteText2": "This cannot be undone!",
"addedSuccess": "The comment was added successfully."
},
"deferDueDate": {
"title": "Defer due date",
"1day": "1 day",
"3days": "3 days",
"1week": "1 week"
},
"description": {
"placeholder": "Click here to enter a description…",
"empty": "No description available yet."
},
"assignee": {
"placeholder": "Type to assign a user…",
"selectPlaceholder": "Assign this user",
"assignSuccess": "The user has been assigned successfully.",
"unassignSuccess": "The user has been unassigned successfully."
},
"label": {
"placeholder": "Type to add a new label…",
"createPlaceholder": "Add this as new label",
"addSuccess": "The label has been added successfully.",
"createSuccess": "The label has been created successfully.",
"removeSuccess": "The label has been removed successfully."
},
"priority": {
"unset": "Unset",
"low": "Low",
"medium": "Medium",
"high": "high",
"urgent": "Urgent",
"doNow": "DO NOW"
},
"relation": {
"add": "Add a New Task Relation",
"new": "New Task Relation",
"searchPlaceholder": "Type search for a new task to add as related…",
"createPlaceholder": "Add this as new related task",
"differentList": "This task belongs to a different list.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
"deleteText1": "Are you sure you want to delete this task relation?",
"deleteText2": "This cannot be undone!"
},
"repeat": {
"everyDay": "Every Day",
"everyWeek": "Every Week",
"everyMonth": "Every Month",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
"days": "Days",
"weeks": "Weeks",
"months": "Months",
"years": "Years"
}
},
"team": {
"title": "Teams",
"noTeams": "You are currently not part of any teams.",
"create": {
"title": "Create a new team",
"success": "The team was successfully created."
},
"edit": {
"title": "Edit Team \"{team}\"",
"members": "Team Members",
"search": "Type to search a user…",
"addUser": "Add to team",
"makeMember": "Make Member",
"makeAdmin": "Make Admin",
"success": "The team was successfully updated.",
"userAddedSuccess": "The team member was successfully added.",
"madeMember": "The team member was successfully made member.",
"madeAdmin": "The team member was successfully made admin.",
"delete": {
"header": "Delete the team",
"text1": "Are you sure you want to delete this team and all of its members?",
"text2": "All team members will loose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "The team was successfully deleted."
},
"deleteUser": {
"header": "Remove a user from the team",
"text1": "Are you sure you want to remove this user from the team?",
"text2": "They will loose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team."
}
},
"attributes": {
"name": "Team Name",
"namePlaceholder": "The team's name goes here…",
"nameRequired": "Please specify a name.",
"description": "Description",
"descriptionPlaceholder": "The teams description goes here…",
"admin": "Admin",
"member": "Member"
}
},
"keyboardShortcuts": {
"title": "Keyboard Shortcuts",
"allPages": "These shortcuts work on all pages.",
"currentPageOnly": "These shortcuts work only on the current page.",
"toggleMenu": "Toggle The Menu",
"quickSearch": "Open the search/quick action bar",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"assign": "Assign this task to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
"attachment": "Add an attachment to this task",
"related": "Modify related tasks of this task"
}
},
"update": {
"available": "There is an update for Vikunja available!",
"do": "Update Now"
},
"menu": {
"edit": "Edit",
"archive": "Archive",
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"share": "Share",
"newList": "New list"
},
"apiConfig": {
"url": "Vikunja URL",
"urlPlaceholder": "eg. https://localhost:3456",
"change": "change",
"signInOn": "Sign in to your Vikunja account on {0}",
"error": "Could not find or use Vikunja installation at \"{domain}\".",
"success": "Using Vikunja installation at \"{domain}\"."
},
"loadingError": {
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
"tryAgain": "try again",
"contact": "contact us"
},
"notification": {
"none": "You don't have any notifications. Have a nice day!",
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use # to only seach for tasks, * to only search for lists and @ to only search for teams.",
"tasks": "Tasks",
"lists": "Lists",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"newTask": "Enter the title of the new task…",
"newNamespace": "Enter the title of the new namespace…",
"newTeam": "Enter the name of the new team…",
"createTask": "Create a task in the current list ({title})",
"createList": "Create a list in the current namespace ({title})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newNamespace": "New namespace",
"newTeam": "New team"
}
},
"date": {
"locale": "en",
"in": "in {date}",
"ago": "{date} ago",
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"error": {
"error": "Error",
"success": "Success",
"0001": "You're not allowed to do that.",
"1001": "A user with this username already exists.",
"1002": "A user with this email address already exists.",
"1004": "No username and password specified.",
"1005": "The user does not exist.",
"1006": "Could not get the user id.",
"1008": "No password reset token provided.",
"1009": "Invalid password reset token.",
"1010": "Invalid email confirm token.",
"1011": "Wrong username or password.",
"1012": "Email address of the user not confirmed.",
"1013": "New password is empty.",
"1014": "Old password is empty.",
"1015": "Totp is already enabled for this user.",
"1016": "Totp is not enabled for this user.",
"1017": "The totp passcode is invalid.",
"1018": "The user avatar type setting is invalid.",
"2001": "ID cannot be empty or 0.",
"2002": "Some of the request data was invalid.",
"3001": "The list does not exist.",
"3004": "You need to have read permissions on that list to perform that action.",
"3005": "The list title cannot be empty.",
"3006": "The list share does not exist.",
"3007": "A list with this identifier already exists.",
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
"4001": "The list task text cannot be empty.",
"4002": "The list task does not exist.",
"4003": "All bulk editing tasks must belong to the same list.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
"4006": "You can't set a parent task as the task itself.",
"4007": "You can't create a task relation with an invalid kind of relation.",
"4008": "You can't create a task relation which already exists.",
"4009": "The task relation does not exist.",
"4010": "Cannot relate a task with itself.",
"4011": "The task attachment does not exist.",
"4012": "The task attachment is too large.",
"4013": "The task sort param is invalid.",
"4014": "The task sort order is invalid.",
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4019": "Invalid task filter value.",
"5001": "The namspace does not exist.",
"5003": "You do not have access to the specified namespace.",
"5006": "The namespace name cannot be empty.",
"5009": "You need to have namespace read access to perform that action.",
"5010": "This team does not have access to that namespace.",
"5011": "This user has already access to that namespace.",
"5012": "The namespace is archived and can therefore only be accessed read only.",
"6001": "The team name cannot be emtpy.",
"6002": "The team does not exist.",
"6004": "The team already has access to that namespace or list.",
"6005": "The user is already a member of that team.",
"6006": "Cannot delete the last team member.",
"6007": "The team does not have access to the list to perform that action.",
"7002": "The user already has access to that list.",
"7003": "You do not have access to that list.",
"8001": "This label already exists on that task.",
"8002": "The label does not exist.",
"8003": "You do not have access to this label.",
"9001": "The right is invalid.",
"10001": "The bucket does not exist.",
"10002": "The bucket does not belong to that list.",
"10003": "You cannot remove the last bucket on a list.",
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
"10005": "There can be only one done bucket per list.",
"11001": "The saved filter does not exist.",
"11002": "Saved filters are not available for link shares.",
"12001": "The subscription entity type is invalid.",
"12002": "You are already subscribed to the entity itself or a parent entity.",
"13001": "This link share requires a password for authentication, but none was provided.",
"13002": "The provided link share password was invalid."
}
}

1
src/i18n/lang/es.json Normal file
View File

@ -0,0 +1 @@
{}

1
src/i18n/lang/fr.json Normal file
View File

@ -0,0 +1 @@
{}

1
src/i18n/lang/ro.json Normal file
View File

@ -0,0 +1 @@
{}

1
src/i18n/lang/ru.json Normal file
View File

@ -0,0 +1 @@
{}

74
src/i18n/setup.js Normal file
View File

@ -0,0 +1,74 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
export const i18n = new VueI18n({
locale: 'en', // set locale
fallbackLocale: 'en',
messages: {
en: require('./lang/en.json'),
},
})
export const availableLanguages = {
en: 'English',
de: 'Deutsch',
}
const loadedLanguages = ['en'] // our default language that is preloaded
const setI18nLanguage = lang => {
i18n.locale = lang
document.querySelector('html').setAttribute('lang', lang)
return lang
}
export const loadLanguageAsync = lang => {
// If the same language
if (i18n.locale === lang) {
return Promise.resolve(setI18nLanguage(lang))
}
// If the language was already loaded
if (loadedLanguages.includes(lang)) {
return Promise.resolve(setI18nLanguage(lang))
}
// If the language hasn't been loaded yet
return import(/* webpackChunkName: "lang-[request]" */ `@/i18n/lang/${lang}.json`).then(
messages => {
i18n.setLocaleMessage(lang, messages.default)
loadedLanguages.push(lang)
return setI18nLanguage(lang)
}
)
}
export const getCurrentLanguage = () => {
const savedLanguage = localStorage.getItem('language')
if(savedLanguage !== null) {
return savedLanguage
}
let browserLanguage = navigator.language || navigator.userLanguage
if (browserLanguage.startsWith('en-')) {
browserLanguage = 'en'
}
if (typeof availableLanguages[browserLanguage] !== 'undefined') {
return browserLanguage
}
return 'en'
}
export const saveLanguage = lang => {
localStorage.setItem('language', lang)
setLanguage()
}
export const setLanguage = () => {
loadLanguageAsync(getCurrentLanguage())
}

View File

@ -2,7 +2,7 @@ import Vue from 'vue'
import App from './App.vue'
import router from './router'
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {formatDate, formatDateSince} from '@/helpers/time/formatDate'
import {VERSION} from './version.json'
// Register the modal
@ -65,7 +65,16 @@ import {
faImage,
faBell,
} from '@fortawesome/free-solid-svg-icons'
import {faCalendarAlt, faClock, faComments, faSave, faStar, faTimesCircle, faSun, faBellSlash} from '@fortawesome/free-regular-svg-icons'
import {
faCalendarAlt,
faClock,
faComments,
faSave,
faStar,
faTimesCircle,
faSun,
faBellSlash
} from '@fortawesome/free-regular-svg-icons'
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
// PWA
import './registerServiceWorker'
@ -74,11 +83,12 @@ import './registerServiceWorker'
import vueShortkey from 'vue-shortkey'
// Mixins
import message from './message'
import {format, formatDistance} from 'date-fns'
import {colorIsDark} from './helpers/color/colorIsDark'
import {setTitle} from './helpers/setTitle'
// Vuex
import {store} from './store'
// i18n
import {i18n} from './i18n/setup'
console.info(`Vikunja frontend version ${VERSION}`)
@ -160,66 +170,40 @@ library.add(faBellSlash)
Vue.component('icon', FontAwesomeIcon)
Vue.use(vueShortkey, { prevent: ['input', 'textarea', '.input'] })
Vue.use(vueShortkey, {prevent: ['input', 'textarea', '.input']})
import focus from '@/directives/focus'
Vue.directive('focus', focus)
import tooltip from '@/directives/tooltip'
Vue.directive('tooltip', tooltip)
const dateIsValid = date => {
if (date === null) {
return false
}
return date instanceof Date && !isNaN(date)
}
const formatDate = (date, f) => {
if (!dateIsValid(date)) {
return ''
}
date = createDateFromString(date)
return date ? format(date, f) : ''
}
import Button from '@/components/input/button'
Vue.component('x-button', Button)
import Card from '@/components/misc/card'
Vue.component('card', Card)
Vue.mixin({
methods: {
formatDateSince: date => {
if (!dateIsValid(date)) {
return ''
}
date = createDateFromString(date)
const currentDate = new Date()
let formatted = ''
if (date > currentDate) {
formatted += 'in '
}
formatted += formatDistance(date, currentDate)
if (date < currentDate) {
formatted += ' ago'
}
return formatted
formatDateSince(date) {
return formatDateSince(date, (p, params) => this.$t(p, params))
},
formatDate(date) {
return formatDate(date, 'PPPPpppp', this.$t('date.locale'))
},
formatDateShort(date) {
return formatDate(date, 'PPpp', this.$t('date.locale'))
},
formatDate: date => formatDate(date, 'PPPPpppp'),
formatDateShort: date => formatDate(date, 'PPpp'),
error(e, actions = []) {
return message.error(e, this, actions)
return message.error(e, this, p => this.$t(p), actions)
},
success(s, actions = []) {
return message.success(s, this, actions)
return message.success(s, this, p => this.$t(p), actions)
},
colorIsDark: colorIsDark,
setTitle: setTitle,
@ -229,5 +213,6 @@ Vue.mixin({
new Vue({
router,
store,
i18n,
render: h => h(App),
}).$mount('#app')

View File

@ -1,29 +1,44 @@
const getText = t => {
export const getErrorText = (r, $t) => {
if (t.response && t.response.data && t.response.data.message) {
return [
t.message,
t.response.data.message
]
if (r.response && r.response.data) {
if(r.response.data.code) {
const path = `error.${r.response.data.code}`
const message = $t(path)
// If message and path are equal no translation exists for that error code
if (path !== message) {
return [
r.message,
message,
]
}
}
if (r.response.data.message) {
return [
r.message,
r.response.data.message,
]
}
}
return [t.message]
return [r.message]
}
export default {
error(e, context, actions = []) {
error(e, context, $t, actions = []) {
context.$notify({
type: 'error',
title: 'Error',
text: getText(e),
title: $t('error.error'),
text: getErrorText(e, $t),
actions: actions,
})
},
success(e, context, actions = []) {
success(e, context, $t, actions = []) {
context.$notify({
type: 'success',
title: 'Success',
text: getText(e),
title: $t('error.success'),
text: getErrorText(e, $t),
data: {
actions: actions,
},

View File

@ -85,17 +85,11 @@ export default {
if (e.response) {
if (e.response.data.code === 1017 && !credentials.totpPasscode) {
ctx.commit('needsTotpPasscode', true)
return Promise.reject()
return Promise.reject(e)
}
let errorMsg = typeof e.response.data.message !== 'undefined' ? e.response.data.message : null
if (e.response.status === 401) {
errorMsg = 'Wrong username or password.'
}
ctx.commit(ERROR_MESSAGE, errorMsg, {root: true})
}
return Promise.reject()
return Promise.reject(e)
})
.finally(() => {
ctx.commit(LOADING, false, {root: true})
@ -145,14 +139,7 @@ export default {
return Promise.resolve()
})
.catch(e => {
if (e.response) {
let errorMsg = typeof e.response.data.message !== 'undefined' ? e.response.data.message : null
if (e.response.status === 401) {
errorMsg = 'Wrong username or password.'
}
ctx.commit(ERROR_MESSAGE, errorMsg, {root: true})
}
return Promise.reject()
return Promise.reject(e)
})
.finally(() => {
ctx.commit(LOADING, false, {root: true})

View File

@ -6,7 +6,7 @@
font-size: .9rem;
text-align: right;
span {
span.url {
border-bottom: 1px dashed $primary;
}
}

View File

@ -3,8 +3,8 @@
.unread-indicator {
position: absolute;
top: 1rem;
right: .75rem;
top: .75rem;
right: 1.15rem;
width: .75rem;
height: .75rem;

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
}

Some files were not shown because too many files have changed in this diff Show More