diff --git a/.drone.yml b/.drone.yml index 38ff2044..3f7c8926 100644 --- a/.drone.yml +++ b/.drone.yml @@ -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/' diff --git a/README.md b/README.md index 40fcb1c6..852c4b4d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/cypress/integration/list/list.spec.js b/cypress/integration/list/list.spec.js index 3c8c0d6f..843d8291 100644 --- a/cypress/integration/list/list.spec.js +++ b/cypress/integration/list/list.spec.js @@ -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() diff --git a/cypress/integration/list/namespaces.spec.js b/cypress/integration/list/namespaces.spec.js index 9d3125b5..e8c15443 100644 --- a/cypress/integration/list/namespaces.spec.js +++ b/cypress/integration/list/namespaces.spec.js @@ -24,7 +24,7 @@ describe('Namepaces', () => { cy.visit('/namespaces') cy.get('a.button') - .contains('Create namespace') + .contains('Create a new namespace') .click() cy.url() diff --git a/cypress/integration/sharing/team.spec.js b/cypress/integration/sharing/team.spec.js index 09455635..7c48a59a 100644 --- a/cypress/integration/sharing/team.spec.js +++ b/cypress/integration/sharing/team.spec.js @@ -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') diff --git a/cypress/integration/task/task.spec.js b/cypress/integration/task/task.spec.js index e0793637..ac948115 100644 --- a/cypress/integration/task/task.spec.js +++ b/cypress/integration/task/task.spec.js @@ -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') diff --git a/package.json b/package.json index 4de17161..4917fc5d 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/App.vue b/src/App.vue index cb017b6b..774d8161 100644 --- a/src/App.vue +++ b/src/App.vue @@ -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 diff --git a/src/components/home/contentLinkShare.vue b/src/components/home/contentLinkShare.vue index 0cf68556..e3602c56 100644 --- a/src/components/home/contentLinkShare.vue +++ b/src/components/home/contentLinkShare.vue @@ -10,12 +10,12 @@

- {{ currentList.title === '' ? 'Loading...' : currentList.title }} + {{ currentList.title === '' ? $t('misc.loading') : currentList.title }}

- Logout + {{ $t('user.auth.logout') }} @@ -23,7 +23,7 @@
- Powered by Vikunja + {{ $t('misc.poweredBy') }}
diff --git a/src/components/home/contentNoAuth.vue b/src/components/home/contentNoAuth.vue index ad8dc789..f8349fbd 100644 --- a/src/components/home/contentNoAuth.vue +++ b/src/components/home/contentNoAuth.vue @@ -4,7 +4,7 @@ Vikunja
-

Info

+

{{ $t('misc.info') }}

{{ motd }} diff --git a/src/components/home/navigation.vue b/src/components/home/navigation.vue index 05b8cd2a..65b04508 100644 --- a/src/components/home/navigation.vue +++ b/src/components/home/navigation.vue @@ -10,7 +10,7 @@ - Overview + {{ $t('navigation.overview') }}
  • @@ -18,7 +18,7 @@ - Upcoming + {{ $t('navigation.upcoming') }}
  • @@ -26,7 +26,7 @@ - Namespaces & Lists + {{ $t('namespace.title') }}
  • @@ -34,7 +34,7 @@ - Labels + {{ $t('label.title') }}
  • @@ -42,7 +42,7 @@ - Teams + {{ $t('team.title') }}
  • @@ -109,7 +109,9 @@
    - Powered by Vikunja + + {{ $t('misc.poweredBy') }} +
    diff --git a/src/components/home/topNavigation.vue b/src/components/home/topNavigation.vue index dba162ef..385f971a 100644 --- a/src/components/home/topNavigation.vue +++ b/src/components/home/topNavigation.vue @@ -25,14 +25,16 @@ > -
    -

    - {{ currentList.title === '' ? 'Loading...' : currentList.title }} -

    +
    +
    @@ -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() { diff --git a/src/components/home/update.vue b/src/components/home/update.vue index 63559e3c..2569c9c3 100644 --- a/src/components/home/update.vue +++ b/src/components/home/update.vue @@ -1,8 +1,8 @@ diff --git a/src/components/input/colorPicker.vue b/src/components/input/colorPicker.vue index 5b2395fd..868ad58e 100644 --- a/src/components/input/colorPicker.vue +++ b/src/components/input/colorPicker.vue @@ -19,7 +19,7 @@ :class="{'is-empty': empty}" /> - Reset Color + {{ $t('input.resetColor') }}
    diff --git a/src/components/input/datepicker.vue b/src/components/input/datepicker.vue index 2260d67d..029ccac1 100644 --- a/src/components/input/datepicker.vue +++ b/src/components/input/datepicker.vue @@ -18,7 +18,7 @@ - Today + {{ $t('input.datepicker.today') }} {{ getWeekdayFromStringInterval('today') }} @@ -31,7 +31,7 @@ - Tomorrow + {{ $t('input.datepicker.tomorrow') }} {{ getWeekdayFromStringInterval('tomorrow') }} @@ -44,7 +44,7 @@ - Next Monday + {{ $t('input.datepicker.nextMonday') }} {{ getWeekdayFromStringInterval('nextMonday') }} @@ -57,7 +57,7 @@ - This Weekend + {{ $t('input.datepicker.thisWeekend') }} {{ getWeekdayFromStringInterval('thisWeekend') }} @@ -70,7 +70,7 @@ - Later This Week + {{ $t('input.datepicker.laterThisWeek') }} {{ getWeekdayFromStringInterval('laterThisWeek') }} @@ -83,7 +83,7 @@ - Next Week + {{ $t('input.datepicker.nextWeek') }} {{ getWeekdayFromStringInterval('nextWeek') }} @@ -102,7 +102,7 @@ :shadow="false" @click="close" > - Confirm + {{ $t('misc.confirm') }} @@ -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) { diff --git a/src/components/input/editor.vue b/src/components/input/editor.vue index 0334bac6..06879990 100644 --- a/src/components/input/editor.vue +++ b/src/components/input/editor.vue @@ -15,7 +15,7 @@ :shadow="false" type="secondary" > - Done + {{ $t('input.editor.done') }} @@ -129,112 +129,112 @@ export default { { name: 'heading-1', action: EasyMDE.toggleHeading1, - title: 'Heading 1', + title: this.$t('input.editor.heading1'), icon: '', }, { name: 'heading-2', action: EasyMDE.toggleHeading2, - title: 'Heading 2', + title: this.$t('input.editor.heading2'), icon: '', }, { name: 'heading-3', action: EasyMDE.toggleHeading3, - title: 'Heading 3', + title: this.$t('input.editor.heading3'), icon: '', }, { name: 'heading-smaller', action: EasyMDE.toggleHeadingSmaller, - title: 'Heading Smaller', + title: this.$t('input.editor.headingSmaller'), icon: '', }, { name: 'heading-bigger', action: EasyMDE.toggleHeadingBigger, - title: 'Heading Bigger', + title: this.$t('input.editor.headingBigger'), icon: '', }, '|', { name: 'bold', action: EasyMDE.toggleBold, - title: 'Bold', + title: this.$t('input.editor.bold'), icon: '', }, { name: 'italic', action: EasyMDE.toggleItalic, - title: 'Italic', + title: this.$t('input.editor.italic'), icon: '', }, { name: 'strikethrough', action: EasyMDE.toggleStrikethrough, - title: 'Strikethrough', + title: this.$t('input.editor.strikethrough'), icon: '', }, { name: 'code', action: EasyMDE.toggleCodeBlock, - title: 'Code', + title: this.$t('input.editor.code'), icon: '', }, { name: 'quote', action: EasyMDE.toggleBlockquote, - title: 'Quote', + title: this.$t('input.editor.quote'), icon: '', }, { name: 'unordered-list', action: EasyMDE.toggleUnorderedList, - title: 'Unordered List', + title: this.$t('input.editor.unorderedList'), icon: '', }, { name: 'ordered-list', action: EasyMDE.toggleOrderedList, - title: 'Ordered List', + title: this.$t('input.editor.orderedList'), icon: '', }, '|', { name: 'clean-block', action: EasyMDE.cleanBlock, - title: 'Clean Block', + title: this.$t('input.editor.cleanBlock'), icon: '', }, { name: 'link', action: EasyMDE.drawLink, - title: 'Link', + title: this.$t('input.editor.link'), icon: '', }, { name: 'image', action: EasyMDE.drawImage, - title: 'Image', + title: this.$t('input.editor.image'), icon: '', }, { name: 'table', action: EasyMDE.drawTable, - title: 'Table', + title: this.$t('input.editor.table'), icon: '', }, { name: 'horizontal-rule', action: EasyMDE.drawHorizontalRule, - title: 'Horizontal Rule', + title: this.$t('input.editor.horizontalRule'), icon: '', }, '|', { name: 'side-by-side', action: EasyMDE.toggleSideBySide, - title: 'Side By Side', + title: this.$t('input.editor.sideBySide'), icon: '', }, { @@ -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: '', }, ], diff --git a/src/components/input/multiselect.vue b/src/components/input/multiselect.vue index f0db907c..34a81fad 100644 --- a/src/components/input/multiselect.vue +++ b/src/components/input/multiselect.vue @@ -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: { diff --git a/src/components/list/list-settings-dropdown.vue b/src/components/list/list-settings-dropdown.vue index 090defb1..e44bec3a 100644 --- a/src/components/list/list-settings-dropdown.vue +++ b/src/components/list/list-settings-dropdown.vue @@ -5,13 +5,13 @@ :to="{ name: `${listRoutePrefix}.settings.edit`, params: { listId: list.id } }" icon="pen" > - Edit + {{ $t('menu.edit') }} - Delete + {{ $t('misc.delete') }} diff --git a/src/components/list/partials/filters.vue b/src/components/list/partials/filters.vue index 448222d0..1bb58659 100644 --- a/src/components/list/partials/filters.vue +++ b/src/components/list/partials/filters.vue @@ -1,28 +1,30 @@ @@ -76,19 +78,19 @@ :selected="s.right === rights.READ" :value="rights.READ" > - Read only + {{ $t('list.share.right.read') }} @@ -108,7 +110,7 @@ - Not shared with any {{ shareType }} yet. + {{ $t('list.share.userTeam.notShared', {type: shareTypeNames}) }} @@ -117,13 +119,11 @@ @submit="deleteSharable()" v-if="showDeleteModal" > - Remove a {{ shareType }} from the {{ typeString }} + + {{ $t('list.share.userTeam.removeHeader', {type: shareTypeName, sharable: sharableName}) }} +

    - Are you sure you want to remove this {{ shareType }} from the - {{ typeString }}?
    - This CANNOT BE UNDONE! + {{ $t('list.share.userTeam.removeText', {type: shareTypeName, sharable: sharableName}) }}

    @@ -131,8 +131,6 @@