feat(#22): Use translation keys in files

This commit is contained in:
2025-11-21 19:20:19 +01:00
parent 63023f4f9f
commit 81b1227e82
28 changed files with 497 additions and 195 deletions

View File

@@ -1,11 +1,11 @@
<template> <template>
<UModal :open="isOpen" title="Mitbestimmungsantrag löschen" @update:open="$emit('update:isOpen', $event)"> <UModal :open="isOpen" :title="$t('applicationForms.deleteTitle')" @update:open="$emit('update:isOpen', $event)">
<template #body> <template #body>
Möchten Sie wirklich den Mitbestimmungsantrag <strong>{{ applicationFormToDelete.name }}</strong> löschen? <span v-html="$t('applicationForms.deleteConfirm', { name: applicationFormToDelete.name })"></span>
</template> </template>
<template #footer> <template #footer>
<UButton label="Abbrechen" color="neutral" variant="outline" @click="$emit('update:isOpen', false)" /> <UButton :label="$t('common.cancel')" color="neutral" variant="outline" @click="$emit('update:isOpen', false)" />
<UButton label="Löschen" color="neutral" @click="$emit('delete', applicationFormToDelete.id)" /> <UButton :label="$t('common.delete')" color="neutral" @click="$emit('delete', applicationFormToDelete.id)" />
</template> </template>
</UModal> </UModal>
</template> </template>

View File

@@ -92,18 +92,19 @@ function getResolvedComponent(formElement: FormElementDto) {
} }
function getDropdownItems(formElementId: string, formElementPosition: number): DropdownMenuItem[] { function getDropdownItems(formElementId: string, formElementPosition: number): DropdownMenuItem[] {
const { t: $t } = useI18n()
const items = [] const items = []
if (route.path !== '/create') { if (route.path !== '/create') {
items.push({ items.push({
label: 'Comments', label: $t('applicationForms.formElements.comments'),
icon: 'i-lucide-message-square-more', icon: 'i-lucide-message-square-more',
onClick: () => toggleComments(formElementId) onClick: () => toggleComments(formElementId)
}) })
} }
items.push({ items.push({
label: 'Add input field below', label: $t('applicationForms.formElements.addInputBelow'),
icon: 'i-lucide-list-plus', icon: 'i-lucide-list-plus',
onClick: () => emit('add:input-form', formElementPosition) onClick: () => emit('add:input-form', formElementPosition)
}) })

View File

@@ -30,7 +30,7 @@
<div class="flex gap-2 justify-between"> <div class="flex gap-2 justify-between">
<UButton leading-icon="i-lucide-arrow-left" :disabled="!stepper?.hasPrev" @click="handleNavigate('backward')"> <UButton leading-icon="i-lucide-arrow-left" :disabled="!stepper?.hasPrev" @click="handleNavigate('backward')">
Prev {{ $t('applicationForms.navigation.previous') }}
</UButton> </UButton>
<UButton <UButton
@@ -40,15 +40,15 @@
size="lg" size="lg"
@click="handleNavigate('forward')" @click="handleNavigate('forward')"
> >
Next {{ $t('applicationForms.navigation.next') }}
</UButton> </UButton>
<div v-if="!stepper?.hasNext" class="flex flex-wrap items-center gap-1.5"> <div v-if="!stepper?.hasNext" class="flex flex-wrap items-center gap-1.5">
<UButton trailing-icon="i-lucide-save" :disabled="disabled" variant="outline" size="lg" @click="emit('save')"> <UButton trailing-icon="i-lucide-save" :disabled="disabled" variant="outline" size="lg" @click="emit('save')">
Save {{ $t('applicationForms.navigation.save') }}
</UButton> </UButton>
<UButton trailing-icon="i-lucide-send-horizontal" :disabled="disabled" size="lg" @click="emit('submit')"> <UButton trailing-icon="i-lucide-send-horizontal" :disabled="disabled" size="lg" @click="emit('submit')">
Submit {{ $t('applicationForms.navigation.submit') }}
</UButton> </UButton>
</div> </div>
</div> </div>

View File

@@ -1,13 +1,7 @@
<template> <template>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="relative flex items-center justify-center"> <div class="relative flex items-center justify-center">
<svg <svg class="shield-ring" :class="ringClass" width="48" height="48" viewBox="0 0 48 48">
class="shield-ring"
:class="ringClass"
width="48"
height="48"
viewBox="0 0 48 48"
>
<circle <circle
class="ring-background" class="ring-background"
cx="24" cx="24"
@@ -32,24 +26,13 @@
transform="rotate(-90 24 24)" transform="rotate(-90 24 24)"
/> />
</svg> </svg>
<Transition name="shield-pulse" mode="out-in"> <Transition name="shield-pulse" mode="out-in">
<UIcon <UIcon :key="status" :name="shieldIcon" class="absolute shield-icon" :class="iconClass" />
:key="status"
:name="shieldIcon"
class="absolute shield-icon"
:class="iconClass"
/>
</Transition> </Transition>
</div> </div>
<UBadge <UBadge :label="statusLabel" :color="badgeColor" size="md" variant="subtle" class="status-badge" />
:label="statusLabel"
:color="badgeColor"
size="md"
variant="subtle"
class="status-badge"
/>
</div> </div>
</template> </template>
@@ -75,16 +58,18 @@ const shieldIcon = computed(() => {
} }
}) })
const { t: $t } = useI18n()
const statusLabel = computed(() => { const statusLabel = computed(() => {
switch (props.status) { switch (props.status) {
case ComplianceStatus.Critical: case ComplianceStatus.Critical:
return 'Kritisch' return $t('compliance.critical')
case ComplianceStatus.Warning: case ComplianceStatus.Warning:
return 'Warnung' return $t('compliance.warning')
case ComplianceStatus.NonCritical: case ComplianceStatus.NonCritical:
return 'Unkritisch' return $t('compliance.nonCritical')
default: default:
return 'Unkritisch' return $t('compliance.nonCritical')
} }
}) })
@@ -144,15 +129,18 @@ const progressOffset = computed(() => {
return circumference - (progressValue.value / 100) * circumference return circumference - (progressValue.value / 100) * circumference
}) })
watch(() => props.status, () => { watch(
const element = document.querySelector('.shield-ring') () => props.status,
if (element) { () => {
element.classList.add('status-change-pulse') const element = document.querySelector('.shield-ring')
setTimeout(() => { if (element) {
element.classList.remove('status-change-pulse') element.classList.add('status-change-pulse')
}, 600) setTimeout(() => {
element.classList.remove('status-change-pulse')
}, 600)
}
} }
}) )
</script> </script>
<style scoped> <style scoped>
@@ -210,4 +198,3 @@ watch(() => props.status, () => {
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
} }
</style> </style>

View File

@@ -1,9 +1,9 @@
<template> <template>
<USlideover v-model:open="isOpen" title="Benachrichtigungen"> <USlideover v-model:open="isOpen" :title="$t('notifications.title')">
<template #body> <template #body>
<div v-if="notifications.length === 0" class="text-center py-8 text-muted"> <div v-if="notifications.length === 0" class="text-center py-8 text-muted">
<UIcon name="i-heroicons-bell-slash" class="h-8 w-8 mx-auto mb-2" /> <UIcon name="i-heroicons-bell-slash" class="h-8 w-8 mx-auto mb-2" />
<p>Keine Benachrichtigungen</p> <p>{{ $t('notifications.empty') }}</p>
</div> </div>
<NuxtLink <NuxtLink

View File

@@ -1,19 +1,18 @@
<template> <template>
<UModal :open="open" title="Version wiederherstellen" @update:open="$emit('update:open', $event)"> <UModal :open="open" :title="$t('versions.restoreTitle')" @update:open="$emit('update:open', $event)">
<template #body> <template #body>
<div class="space-y-2"> <div class="space-y-2">
<p> <p>
Möchten Sie Version <strong>v{{ versionNumber }}</strong> wirklich wiederherstellen? {{ $t('versions.restoreConfirm', { number: versionNumber }) }}
</p> </p>
<p class="text-sm text-gray-600"> <p class="text-sm text-gray-600">
Dies erstellt eine neue Version mit dem Inhalt der ausgewählten Version. Die aktuelle Version und alle {{ $t('versions.restoreDescription') }}
Änderungen bleiben in der Historie erhalten.
</p> </p>
</div> </div>
</template> </template>
<template #footer> <template #footer>
<UButton label="Abbrechen" color="neutral" variant="outline" @click="$emit('update:open', false)" /> <UButton :label="$t('common.cancel')" color="neutral" variant="outline" @click="$emit('update:open', false)" />
<UButton label="Wiederherstellen" color="primary" :loading="loading" @click="$emit('confirm')" /> <UButton :label="$t('versions.restore')" color="primary" :loading="loading" @click="$emit('confirm')" />
</template> </template>
</UModal> </UModal>
</template> </template>

View File

@@ -23,9 +23,11 @@
</UChatMessages> </UChatMessages>
</template> </template>
<UTextarea v-model="commentTextAreaValue" class="w-full" /> <UTextarea v-model="commentTextAreaValue" class="w-full" />
<UButton v-if="!isEditingComment" class="my-3 lg:my-4" @click="submitComment(formElementId)"> Submit </UButton> <UButton v-if="!isEditingComment" class="my-3 lg:my-4" @click="submitComment(formElementId)">
<UButton v-if="isEditingComment" class="my-3 lg:my-4" @click="updateEditComment"> Edit comment </UButton> {{ $t('comments.submit') }}
<UButton v-if="isEditingComment" class="my-3 lg:my-4" @click="cancelEditComment"> Cancel </UButton> </UButton>
<UButton v-if="isEditingComment" class="my-3 lg:my-4" @click="updateEditComment"> {{ $t('comments.edit') }} </UButton>
<UButton v-if="isEditingComment" class="my-3 lg:my-4" @click="cancelEditComment"> {{ $t('common.cancel') }} </UButton>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -50,11 +52,12 @@ const {
} = commentActions } = commentActions
function createChatMessageActions(comment: CommentDto) { function createChatMessageActions(comment: CommentDto) {
const { t: $t } = useI18n()
const chatMessageActions = [] const chatMessageActions = []
if (isCommentByUser(comment)) { if (isCommentByUser(comment)) {
chatMessageActions.push({ chatMessageActions.push({
label: 'Edit', label: $t('comments.editAction'),
icon: 'i-lucide-pencil', icon: 'i-lucide-pencil',
onClick: () => editComment(comment) onClick: () => editComment(comment)
}) })

View File

@@ -71,6 +71,8 @@ const user = ref({
} }
}) })
const { t: $t } = useI18n()
const items = computed<DropdownMenuItem[][]>(() => [ const items = computed<DropdownMenuItem[][]>(() => [
[ [
{ {
@@ -81,27 +83,27 @@ const items = computed<DropdownMenuItem[][]>(() => [
], ],
[ [
{ {
label: 'Profile', label: $t('user.profile'),
icon: 'i-lucide-user' icon: 'i-lucide-user'
}, },
{ {
label: 'Administration', label: $t('user.administration'),
icon: 'i-lucide-shield', icon: 'i-lucide-shield',
to: '/administration' to: '/administration'
}, },
{ {
label: 'Settings', label: $t('user.settings'),
icon: 'i-lucide-settings', icon: 'i-lucide-settings',
to: '/settings' to: '/settings'
} }
], ],
[ [
{ {
label: 'Theme', label: $t('user.theme'),
icon: 'i-lucide-palette', icon: 'i-lucide-palette',
children: [ children: [
{ {
label: 'Primary', label: $t('user.themePrimary'),
slot: 'chip', slot: 'chip',
chip: appConfig.ui.colors.primary, chip: appConfig.ui.colors.primary,
content: { content: {
@@ -121,7 +123,7 @@ const items = computed<DropdownMenuItem[][]>(() => [
})) }))
}, },
{ {
label: 'Neutral', label: $t('user.themeNeutral'),
slot: 'chip', slot: 'chip',
chip: appConfig.ui.colors.neutral, chip: appConfig.ui.colors.neutral,
content: { content: {
@@ -143,11 +145,11 @@ const items = computed<DropdownMenuItem[][]>(() => [
] ]
}, },
{ {
label: 'Appearance', label: $t('user.appearance'),
icon: 'i-lucide-sun-moon', icon: 'i-lucide-sun-moon',
children: [ children: [
{ {
label: 'Light', label: $t('user.light'),
icon: 'i-lucide-sun', icon: 'i-lucide-sun',
type: 'checkbox', type: 'checkbox',
checked: colorMode.value === 'light', checked: colorMode.value === 'light',
@@ -157,7 +159,7 @@ const items = computed<DropdownMenuItem[][]>(() => [
} }
}, },
{ {
label: 'Dark', label: $t('user.dark'),
icon: 'i-lucide-moon', icon: 'i-lucide-moon',
type: 'checkbox', type: 'checkbox',
checked: colorMode.value === 'dark', checked: colorMode.value === 'dark',
@@ -175,7 +177,7 @@ const items = computed<DropdownMenuItem[][]>(() => [
], ],
[ [
{ {
label: 'Log out', label: $t('user.logout'),
icon: 'i-lucide-log-out', icon: 'i-lucide-log-out',
async onSelect(e: Event) { async onSelect(e: Event) {
e.preventDefault() e.preventDefault()

View File

@@ -1,7 +1,7 @@
<template> <template>
<UModal :open="open" title="Vergleich mit Version" @update:open="$emit('update:open', $event)"> <UModal :open="open" :title="$t('versions.compare')" @update:open="$emit('update:open', $event)">
<template #header> <template #header>
<h3 class="text-lg font-semibold">Vergleich: Aktuelles Formular mit Version v{{ versionNumber }}</h3> <h3 class="text-lg font-semibold">{{ $t('versions.comparisonTitle', { number: versionNumber }) }}</h3>
</template> </template>
<template #body> <template #body>
@@ -9,13 +9,13 @@
<UIcon name="i-lucide-loader-circle" class="animate-spin h-8 w-8" /> <UIcon name="i-lucide-loader-circle" class="animate-spin h-8 w-8" />
</div> </div>
<div v-else-if="error" class="text-red-500">Fehler beim Laden der Version: {{ error }}</div> <div v-else-if="error" class="text-red-500">{{ $t('versions.comparisonError') }}: {{ error }}</div>
<div v-else-if="diff && hasChanges" class="space-y-6"> <div v-else-if="diff && hasChanges" class="space-y-6">
<div v-if="diff.elementsAdded.length > 0" class="space-y-2"> <div v-if="diff.elementsAdded.length > 0" class="space-y-2">
<h4 class="font-semibold text-success flex items-center gap-2"> <h4 class="font-semibold text-success flex items-center gap-2">
<UIcon name="i-lucide-plus-circle" /> <UIcon name="i-lucide-plus-circle" />
Hinzugefügte Elemente ({{ diff.elementsAdded.length }}) {{ $t('versions.elementsAdded', { count: diff.elementsAdded.length }) }}
</h4> </h4>
<UCard v-for="(element, index) in diff.elementsAdded" :key="`added-${index}`" variant="subtle"> <UCard v-for="(element, index) in diff.elementsAdded" :key="`added-${index}`" variant="subtle">
<div class="flex items-start gap-2"> <div class="flex items-start gap-2">
@@ -31,14 +31,17 @@
<div v-if="diff.elementsRemoved.length > 0" class="space-y-2"> <div v-if="diff.elementsRemoved.length > 0" class="space-y-2">
<h4 class="font-semibold text-error flex items-center gap-2"> <h4 class="font-semibold text-error flex items-center gap-2">
<UIcon name="i-lucide-minus-circle" /> <UIcon name="i-lucide-minus-circle" />
Entfernte Elemente ({{ diff.elementsRemoved.length }}) {{ $t('versions.elementsRemoved', { count: diff.elementsRemoved.length }) }}
</h4> </h4>
<UCard v-for="(element, index) in diff.elementsRemoved" :key="`removed-${index}`" variant="subtle"> <UCard v-for="(element, index) in diff.elementsRemoved" :key="`removed-${index}`" variant="subtle">
<div class="flex items-start gap-2"> <div class="flex items-start gap-2">
<UBadge color="error" variant="subtle">-</UBadge> <UBadge color="error" variant="subtle">-</UBadge>
<div class="flex-1"> <div class="flex-1">
<div class="font-medium">{{ element.title || 'Ohne Titel' }}</div> <div class="font-medium">{{ element.title || $t('versions.elementWithoutTitle') }}</div>
<div class="text-sm text-gray-500">Typ: {{ element.type }} · Sektion: {{ element.sectionTitle }}</div> <div class="text-sm text-gray-500">
{{ $t('common.type') }}: {{ element.type }} · {{ $t('versions.elementIn') }}
{{ element.sectionTitle }}
</div>
</div> </div>
</div> </div>
</UCard> </UCard>
@@ -47,14 +50,14 @@
<div v-if="diff.elementsModified.length > 0" class="space-y-2"> <div v-if="diff.elementsModified.length > 0" class="space-y-2">
<h4 class="font-semibold text-warning flex items-center gap-2"> <h4 class="font-semibold text-warning flex items-center gap-2">
<UIcon name="i-lucide-pencil" /> <UIcon name="i-lucide-pencil" />
Geänderte Elemente ({{ diff.elementsModified.length }}) {{ $t('versions.elementsModified', { count: diff.elementsModified.length }) }}
</h4> </h4>
<UCard v-for="(element, index) in diff.elementsModified" :key="`modified-${index}`" variant="subtle"> <UCard v-for="(element, index) in diff.elementsModified" :key="`modified-${index}`" variant="subtle">
<div class="space-y-2"> <div class="space-y-2">
<div class="flex items-start gap-2"> <div class="flex items-start gap-2">
<UBadge color="warning" variant="subtle">~</UBadge> <UBadge color="warning" variant="subtle">~</UBadge>
<div class="flex-1"> <div class="flex-1">
<div class="font-medium">Element in {{ element.sectionTitle }}</div> <div class="font-medium">{{ $t('versions.elementIn') }} {{ element.sectionTitle }}</div>
<div <div
v-if=" v-if="
@@ -66,7 +69,7 @@
> >
<div v-if="element.optionsAdded.length > 0"> <div v-if="element.optionsAdded.length > 0">
<div class="text-sm font-medium text-success"> <div class="text-sm font-medium text-success">
Optionen hinzugefügt ({{ element.optionsAdded.length }}): {{ $t('versions.optionsAdded', { count: element.optionsAdded.length }) }}:
</div> </div>
<div <div
v-for="(option, optIdx) in element.optionsAdded" v-for="(option, optIdx) in element.optionsAdded"
@@ -80,7 +83,7 @@
<div v-if="element.optionsRemoved.length > 0"> <div v-if="element.optionsRemoved.length > 0">
<div class="text-sm font-medium text-error"> <div class="text-sm font-medium text-error">
Optionen entfernt ({{ element.optionsRemoved.length }}): {{ $t('versions.optionsRemoved', { count: element.optionsRemoved.length }) }}:
</div> </div>
<div <div
v-for="(option, optIdx) in element.optionsRemoved" v-for="(option, optIdx) in element.optionsRemoved"
@@ -94,7 +97,7 @@
<div v-if="element.optionsModified.length > 0"> <div v-if="element.optionsModified.length > 0">
<div class="text-sm font-medium text-warning"> <div class="text-sm font-medium text-warning">
Optionen geändert ({{ element.optionsModified.length }}): {{ $t('versions.optionsModified', { count: element.optionsModified.length }) }}:
</div> </div>
<div <div
v-for="(option, optIdx) in element.optionsModified" v-for="(option, optIdx) in element.optionsModified"
@@ -117,12 +120,12 @@
</div> </div>
</div> </div>
<div v-else class="text-center py-8 text-gray-500">Keine Unterschiede gefunden</div> <div v-else class="text-center py-8 text-gray-500">{{ $t('versions.noChanges') }}</div>
</template> </template>
<template #footer> <template #footer>
<div class="flex justify-end"> <div class="flex justify-end">
<UButton label="Schließen" color="neutral" variant="outline" @click="$emit('update:open', false)" /> <UButton :label="$t('common.close')" color="neutral" variant="outline" @click="$emit('update:open', false)" />
</div> </div>
</template> </template>
</UModal> </UModal>
@@ -145,6 +148,7 @@ defineEmits<{
}>() }>()
const { getVersion } = useApplicationFormVersion() const { getVersion } = useApplicationFormVersion()
const { t: $t } = useI18n()
const loading = ref(false) const loading = ref(false)
const error = ref<string | null>(null) const error = ref<string | null>(null)
@@ -176,7 +180,7 @@ async function loadVersionAndCompare() {
versionData.value = await getVersion(props.applicationFormId, props.versionNumber) versionData.value = await getVersion(props.applicationFormId, props.versionNumber)
diff.value = compareApplicationForms(props.currentForm, versionData.value.snapshot) diff.value = compareApplicationForms(props.currentForm, versionData.value.snapshot)
} catch (e: unknown) { } catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Unbekannter Fehler' error.value = e instanceof Error ? e.message : $t('versions.unknownError')
} finally { } finally {
loading.value = false loading.value = false
} }

View File

@@ -4,9 +4,9 @@
<UIcon name="i-lucide-loader-circle" class="animate-spin h-8 w-8" /> <UIcon name="i-lucide-loader-circle" class="animate-spin h-8 w-8" />
</div> </div>
<div v-else-if="error" class="text-red-500">Fehler beim Laden der Versionen: {{ error }}</div> <div v-else-if="error" class="text-red-500">{{ $t('versions.loadError') }}: {{ error }}</div>
<div v-else-if="versions.length === 0" class="text-center py-8 text-gray-500">Keine Versionen verfügbar</div> <div v-else-if="versions.length === 0" class="text-center py-8 text-gray-500">{{ $t('versions.empty') }}</div>
<div v-else class="space-y-3"> <div v-else class="space-y-3">
<UCard v-for="version in versions" :key="version.id" class="hover:shadow-md transition-shadow"> <UCard v-for="version in versions" :key="version.id" class="hover:shadow-md transition-shadow">
@@ -29,7 +29,7 @@
size="sm" size="sm"
color="neutral" color="neutral"
variant="outline" variant="outline"
label="Vergleichen" :label="$t('versions.compare')"
@click="openComparisonModal(version)" @click="openComparisonModal(version)"
/> />
<UButton <UButton
@@ -37,7 +37,7 @@
size="sm" size="sm"
color="neutral" color="neutral"
variant="outline" variant="outline"
label="Wiederherstellen" :label="$t('versions.restore')"
@click="openRestoreModal(version)" @click="openRestoreModal(version)"
/> />
</div> </div>
@@ -76,6 +76,7 @@ const emit = defineEmits<{
const { getVersions, restoreVersion } = useApplicationFormVersion() const { getVersions, restoreVersion } = useApplicationFormVersion()
const toast = useToast() const toast = useToast()
const { t: $t } = useI18n()
const versions = ref<ApplicationFormVersionListItemDto[]>([]) const versions = ref<ApplicationFormVersionListItemDto[]>([])
const loading = ref(false) const loading = ref(false)
@@ -91,10 +92,10 @@ async function loadVersions() {
try { try {
versions.value = await getVersions(props.applicationFormId) versions.value = await getVersions(props.applicationFormId)
} catch (e: unknown) { } catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Unbekannter Fehler' error.value = e instanceof Error ? e.message : $t('versions.unknownError')
toast.add({ toast.add({
title: 'Fehler', title: $t('common.error'),
description: 'Versionen konnten nicht geladen werden', description: $t('versions.loadErrorDescription'),
color: 'error' color: 'error'
}) })
} finally { } finally {
@@ -119,8 +120,8 @@ async function handleRestore() {
try { try {
await restoreVersion(props.applicationFormId, selectedVersion.value.versionNumber) await restoreVersion(props.applicationFormId, selectedVersion.value.versionNumber)
toast.add({ toast.add({
title: 'Erfolg', title: $t('common.success'),
description: `Version v${selectedVersion.value.versionNumber} wurde wiederhergestellt`, description: $t('versions.restored', { number: selectedVersion.value.versionNumber }),
color: 'success' color: 'success'
}) })
isRestoreModalOpen.value = false isRestoreModalOpen.value = false
@@ -128,8 +129,8 @@ async function handleRestore() {
emit('restored') emit('restored')
} catch { } catch {
toast.add({ toast.add({
title: 'Fehler', title: $t('common.error'),
description: 'Version konnte nicht wiederhergestellt werden', description: $t('versions.restoreError'),
color: 'error' color: 'error'
}) })
} finally { } finally {
@@ -154,18 +155,20 @@ function getStatusColor(status: ApplicationFormStatus) {
} }
} }
const { t: $t } = useI18n()
function getStatusLabel(status: ApplicationFormStatus): string { function getStatusLabel(status: ApplicationFormStatus): string {
switch (status) { switch (status) {
case 'DRAFT': case 'DRAFT':
return 'Entwurf' return $t('applicationForms.status.draft')
case 'SUBMITTED': case 'SUBMITTED':
return 'Eingereicht' return $t('applicationForms.status.submitted')
case 'APPROVED': case 'APPROVED':
return 'Genehmigt' return $t('applicationForms.status.approved')
case 'REJECTED': case 'REJECTED':
return 'Abgelehnt' return $t('applicationForms.status.rejected')
case 'SIGNED': case 'SIGNED':
return 'Signiert' return $t('applicationForms.status.signed')
default: default:
return status return status
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<USelect v-model="modelValue" placeholder="Select status" :items="items" /> <USelect v-model="modelValue" :placeholder="$t('applicationForms.formElements.selectPlaceholder')" :items="items" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,8 +1,8 @@
<template> <template>
<UFormField label="Titel"> <UFormField :label="$t('applicationForms.formElements.title')">
<UInput v-model="title" class="w-full" :disabled="props.disabled" /> <UInput v-model="title" class="w-full" :disabled="props.disabled" />
</UFormField> </UFormField>
<UFormField label="Text"> <UFormField :label="$t('applicationForms.formElements.text')">
<UTextarea v-model="body" class="w-full" autoresize :disabled="props.disabled" /> <UTextarea v-model="body" class="w-full" autoresize :disabled="props.disabled" />
</UFormField> </UFormField>
</template> </template>

View File

@@ -1,3 +1,3 @@
<template> <template>
<div>Element unimplemented:</div> <div>{{ $t('applicationForms.formElements.unimplemented') }}</div>
</template> </template>

View File

@@ -25,7 +25,12 @@ export function useFormElementManagement() {
if (applicationFormId) { if (applicationFormId) {
try { try {
return await applicationForm.addFormElementToSubSection(applicationFormId, subsectionId, inputFormElement, position + 1) return await applicationForm.addFormElementToSubSection(
applicationFormId,
subsectionId,
inputFormElement,
position + 1
)
} catch (error) { } catch (error) {
console.error('Failed to add form element:', error) console.error('Failed to add form element:', error)
throw error throw error

View File

@@ -149,4 +149,3 @@ export const usePermissions = () => {
canDeleteComment canDeleteComment
} }
} }

View File

@@ -11,9 +11,11 @@ defineProps<{
error: NuxtError error: NuxtError
}>() }>()
const { t: $t } = useI18n()
useSeoMeta({ useSeoMeta({
title: 'Page not found', title: $t('error.pageNotFound'),
description: 'We are sorry but this page could not be found.' description: $t('error.pageNotFoundDescription')
}) })
useHead({ useHead({

View File

@@ -36,10 +36,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { t: $t } = useI18n()
const links = [ const links = [
[ [
{ {
label: 'General', label: $t('common.general'),
icon: 'i-lucide-user', icon: 'i-lucide-user',
to: '/settings', to: '/settings',
exact: true exact: true
@@ -47,7 +49,7 @@ const links = [
], ],
[ [
{ {
label: 'General2', label: $t('common.general'),
icon: 'i-lucide-user', icon: 'i-lucide-user',
to: '/settings', to: '/settings',
exact: true exact: true

View File

@@ -5,4 +5,3 @@ export default defineNuxtRouteMiddleware((to) => {
return navigateTo('/', { replace: true }) return navigateTo('/', { replace: true })
} }
}) })

View File

@@ -1,7 +1,7 @@
<template> <template>
<UDashboardPanel id="administration"> <UDashboardPanel id="administration">
<template #header> <template #header>
<UDashboardNavbar title="Administration - JSON Template Editor" :ui="{ right: 'gap-3' }"> <UDashboardNavbar :title="$t('templates.editorTitle')" :ui="{ right: 'gap-3' }">
<template #leading> <template #leading>
<UDashboardSidebarCollapse /> <UDashboardSidebarCollapse />
</template> </template>
@@ -10,21 +10,21 @@
<UButton <UButton
v-if="hasUnsavedChanges" v-if="hasUnsavedChanges"
icon="i-lucide-rotate-ccw" icon="i-lucide-rotate-ccw"
label="Zurücksetzen" :label="$t('templates.reset')"
color="neutral" color="neutral"
variant="outline" variant="outline"
@click="resetEditor" @click="resetEditor"
/> />
<UButton <UButton
icon="i-lucide-file-plus" icon="i-lucide-file-plus"
label="Neue Vorlage" :label="$t('templates.newTemplate')"
color="neutral" color="neutral"
variant="outline" variant="outline"
@click="createNewTemplate" @click="createNewTemplate"
/> />
<UButton <UButton
icon="i-lucide-save" icon="i-lucide-save"
label="Speichern" :label="$t('common.save')"
:disabled="!hasUnsavedChanges || !isValidJson" :disabled="!hasUnsavedChanges || !isValidJson"
:loading="isSaving" :loading="isSaving"
@click="saveTemplate" @click="saveTemplate"
@@ -40,17 +40,17 @@
<div> <div>
<h3 class="font-semibold text-lg">{{ currentTemplate.name }}</h3> <h3 class="font-semibold text-lg">{{ currentTemplate.name }}</h3>
<p class="text-sm text-muted mt-1"> <p class="text-sm text-muted mt-1">
Zuletzt bearbeitet am {{ formatDate(new Date(currentTemplate.modifiedAt)) }} {{ $t('templates.lastModified') }} {{ formatDate(new Date(currentTemplate.modifiedAt)) }}
</p> </p>
</div> </div>
<UBadge v-if="hasUnsavedChanges" label="Ungespeicherte Änderungen" color="warning" variant="subtle" /> <UBadge v-if="hasUnsavedChanges" :label="$t('templates.unsavedChanges')" color="warning" variant="subtle" />
</div> </div>
</UCard> </UCard>
<UCard v-else class="mb-4"> <UCard v-else class="mb-4">
<div class="text-center py-4"> <div class="text-center py-4">
<UIcon name="i-lucide-info" class="size-8 text-muted mx-auto mb-2" /> <UIcon name="i-lucide-info" class="size-8 text-muted mx-auto mb-2" />
<p class="text-muted">Keine Vorlage gefunden. Erstellen Sie eine neue Vorlage.</p> <p class="text-muted">{{ $t('templates.noTemplateFound') }}</p>
</div> </div>
</UCard> </UCard>
@@ -70,8 +70,8 @@
v-if="!isValidJson" v-if="!isValidJson"
color="error" color="error"
icon="i-lucide-alert-circle" icon="i-lucide-alert-circle"
title="Ungültiges JSON" :title="$t('templates.invalidJson')"
description="Das JSON-Format ist ungültig. Bitte korrigieren Sie die Syntax." :description="$t('templates.invalidJsonDescription')"
/> />
</div> </div>
</template> </template>
@@ -92,6 +92,7 @@ const colorMode = useColorMode()
const { hasRole } = usePermissions() const { hasRole } = usePermissions()
const { getAllApplicationFormTemplates, updateApplicationFormTemplate, createApplicationFormTemplate } = const { getAllApplicationFormTemplates, updateApplicationFormTemplate, createApplicationFormTemplate } =
await useApplicationFormTemplate() await useApplicationFormTemplate()
const { t: $t } = useI18n()
if (!hasRole('CHIEF_EXECUTIVE_OFFICER') && !hasRole('IT_DEPARTMENT')) { if (!hasRole('CHIEF_EXECUTIVE_OFFICER') && !hasRole('IT_DEPARTMENT')) {
await navigateTo('/') await navigateTo('/')
@@ -194,8 +195,8 @@ function createNewTemplate() {
async function saveTemplate() { async function saveTemplate() {
if (!isValidJson.value) { if (!isValidJson.value) {
toast.add({ toast.add({
title: 'Fehler', title: $t('common.error'),
description: 'Das JSON-Format ist ungültig.', description: $t('templates.invalidJsonDescription'),
color: 'error' color: 'error'
}) })
return return
@@ -210,15 +211,15 @@ async function saveTemplate() {
if (currentTemplate.value?.id) { if (currentTemplate.value?.id) {
currentTemplate.value = await updateApplicationFormTemplate(currentTemplate.value.id, dataWithDates) currentTemplate.value = await updateApplicationFormTemplate(currentTemplate.value.id, dataWithDates)
toast.add({ toast.add({
title: 'Erfolg', title: $t('common.success'),
description: 'Vorlage erfolgreich aktualisiert.', description: $t('templates.updated'),
color: 'success' color: 'success'
}) })
} else { } else {
currentTemplate.value = await createApplicationFormTemplate(dataWithDates) currentTemplate.value = await createApplicationFormTemplate(dataWithDates)
toast.add({ toast.add({
title: 'Erfolg', title: $t('common.success'),
description: 'Vorlage erfolgreich erstellt.', description: $t('templates.created'),
color: 'success' color: 'success'
}) })
} }
@@ -230,8 +231,8 @@ async function saveTemplate() {
await refreshNuxtData('applicationFormTemplates') await refreshNuxtData('applicationFormTemplates')
} catch (error) { } catch (error) {
toast.add({ toast.add({
title: 'Fehler', title: $t('common.error'),
description: 'Fehler beim Speichern der Vorlage.', description: $t('templates.saveError'),
color: 'error' color: 'error'
}) })
console.error('Error saving template:', error) console.error('Error saving template:', error)
@@ -242,7 +243,7 @@ async function saveTemplate() {
onBeforeRouteLeave((to, from, next) => { onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) { if (hasUnsavedChanges.value) {
const answer = window.confirm('Sie haben ungespeicherte Änderungen. Möchten Sie die Seite wirklich verlassen?') const answer = window.confirm($t('templates.unsavedWarning'))
if (answer) { if (answer) {
next() next()
} else { } else {

View File

@@ -1,7 +1,7 @@
<template> <template>
<UDashboardPanel id="home"> <UDashboardPanel id="home">
<template #header> <template #header>
<UDashboardNavbar title="Home" :ui="{ right: 'gap-3' }"> <UDashboardNavbar :title="$t('common.home')" :ui="{ right: 'gap-3' }">
<template #leading> <template #leading>
<UDashboardSidebarCollapse /> <UDashboardSidebarCollapse />
</template> </template>
@@ -9,7 +9,7 @@
<template #right> <template #right>
<UButton <UButton
icon="i-lucide-circle-plus" icon="i-lucide-circle-plus"
label="Neuer Mitbestimmungsantrag" :label="$t('applicationForms.createNew')"
to="/create" to="/create"
:disabled="!canWriteApplicationForms" :disabled="!canWriteApplicationForms"
size="xl" size="xl"
@@ -59,6 +59,7 @@ import { useUserStore } from '~~/stores/useUserStore'
const route = useRoute() const route = useRoute()
const toast = useToast() const toast = useToast()
const { t: $t } = useI18n()
definePageMeta({ definePageMeta({
// Prevent whole page from re-rendering when navigating between sections to keep state // Prevent whole page from re-rendering when navigating between sections to keep state
@@ -113,7 +114,7 @@ async function onSave() {
const updated = await updateForm(applicationForm.value.id, applicationForm.value) const updated = await updateForm(applicationForm.value.id, applicationForm.value)
if (updated) { if (updated) {
updateApplicationForm(updated) updateApplicationForm(updated)
toast.add({ title: 'Success', description: 'Application form saved', color: 'success' }) toast.add({ title: $t('common.success'), description: $t('applicationForms.saved'), color: 'success' })
} }
} }
} }
@@ -122,7 +123,7 @@ async function onSubmit() {
if (applicationForm.value) { if (applicationForm.value) {
await submitApplicationForm(applicationForm.value.id) await submitApplicationForm(applicationForm.value.id)
await navigateTo('/') await navigateTo('/')
toast.add({ title: 'Success', description: 'Application form submitted', color: 'success' }) toast.add({ title: $t('common.success'), description: $t('applicationForms.submitted'), color: 'success' })
} }
} }

View File

@@ -2,7 +2,7 @@
<template> <template>
<UDashboardPanel id="versions"> <UDashboardPanel id="versions">
<template #header> <template #header>
<UDashboardNavbar :title="`Versionen: ${applicationForm?.name}`"> <UDashboardNavbar :title="$t('versions.pageTitle', { name: applicationForm?.name })">
<template #leading> <template #leading>
<UDashboardSidebarCollapse /> <UDashboardSidebarCollapse />
</template> </template>
@@ -15,7 +15,12 @@
<template #body> <template #body>
<div class="p-6"> <div class="p-6">
<VersionHistory v-if="applicationForm" :application-form-id="applicationForm.id" :current-form="applicationForm" @restored="handleRestored" /> <VersionHistory
v-if="applicationForm"
:application-form-id="applicationForm.id"
:current-form="applicationForm"
@restored="handleRestored"
/>
</div> </div>
</template> </template>
</UDashboardPanel> </UDashboardPanel>
@@ -25,6 +30,7 @@
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
const { t: $t } = useI18n()
definePageMeta({ definePageMeta({
key: (route) => `${route.params.id}-versions` key: (route) => `${route.params.id}-versions`
@@ -36,8 +42,8 @@ const { applicationForm, navigationLinks: links, refresh } = await useApplicatio
async function handleRestored() { async function handleRestored() {
await refresh() await refresh()
toast.add({ toast.add({
title: 'Version wiederhergestellt', title: $t('versions.restored'),
description: 'Das Formular wurde auf die ausgewählte Version zurückgesetzt.', description: $t('versions.restoredDescription'),
color: 'success' color: 'success'
}) })
router.push(`/application-forms/${applicationForm.value.id}/0`) router.push(`/application-forms/${applicationForm.value.id}/0`)

View File

@@ -1,7 +1,7 @@
<template> <template>
<UDashboardPanel id="home"> <UDashboardPanel id="home">
<template #header> <template #header>
<UDashboardNavbar title="Home" :ui="{ right: 'gap-3' }"> <UDashboardNavbar :title="$t('common.home')" :ui="{ right: 'gap-3' }">
<template #leading> <template #leading>
<UDashboardSidebarCollapse /> <UDashboardSidebarCollapse />
</template> </template>
@@ -18,9 +18,9 @@
<div class="flex flex-col gap-4 sm:gap-6 lg:gap-12 w-full lg:max-w-4xl mx-auto"> <div class="flex flex-col gap-4 sm:gap-6 lg:gap-12 w-full lg:max-w-4xl mx-auto">
<div v-if="!canWriteApplicationForms" class="text-center py-12"> <div v-if="!canWriteApplicationForms" class="text-center py-12">
<UIcon name="i-lucide-shield-x" class="w-16 h-16 mx-auto text-red-400 mb-4" /> <UIcon name="i-lucide-shield-x" class="w-16 h-16 mx-auto text-red-400 mb-4" />
<h2 class="text-2xl font-semibold text-gray-700 mb-2">Keine Berechtigung</h2> <h2 class="text-2xl font-semibold text-gray-700 mb-2">{{ $t('applicationForms.noPermission') }}</h2>
<p class="text-gray-500 mb-4">Sie haben keine Berechtigung zum Erstellen von Anträgen.</p> <p class="text-gray-500 mb-4">{{ $t('applicationForms.noPermissionDescription') }}</p>
<UButton to="/" class="mt-4"> Zurück zur Übersicht </UButton> <UButton to="/" class="mt-4"> {{ $t('applicationForms.backToOverview') }} </UButton>
</div> </div>
<div v-else-if="applicationFormTemplate"> <div v-else-if="applicationFormTemplate">
<FormStepperWithNavigation <FormStepperWithNavigation
@@ -29,7 +29,7 @@
@submit="onSubmit" @submit="onSubmit"
@add-input-form="handleAddInputForm" @add-input-form="handleAddInputForm"
> >
<UFormField label="Name" class="mb-4"> <UFormField :label="$t('common.name')" class="mb-4">
<UInput v-model="applicationFormTemplate.name" class="w-full" /> <UInput v-model="applicationFormTemplate.name" class="w-full" />
</UFormField> </UFormField>
</FormStepperWithNavigation> </FormStepperWithNavigation>
@@ -57,6 +57,7 @@ const { canWriteApplicationForms } = usePermissions()
const userStore = useUserStore() const userStore = useUserStore()
const { selectedOrganization } = storeToRefs(userStore) const { selectedOrganization } = storeToRefs(userStore)
const toast = useToast() const toast = useToast()
const { t: $t } = useI18n()
const { data, error } = await useAsyncData<PagedApplicationFormDto>( const { data, error } = await useAsyncData<PagedApplicationFormDto>(
'create-application-form', 'create-application-form',
@@ -98,7 +99,7 @@ watch(
async function onSave() { async function onSave() {
const applicationForm = await prepareAndCreateApplicationForm() const applicationForm = await prepareAndCreateApplicationForm()
if (applicationForm) { if (applicationForm) {
toast.add({ title: 'Success', description: 'Application form saved', color: 'success' }) toast.add({ title: $t('common.success'), description: $t('applicationForms.saved'), color: 'success' })
} }
} }
@@ -107,7 +108,7 @@ async function onSubmit() {
if (applicationForm) { if (applicationForm) {
await submitApplicationForm(applicationForm.id) await submitApplicationForm(applicationForm.id)
await navigateTo('/') await navigateTo('/')
toast.add({ title: 'Success', description: 'Application form submitted', color: 'success' }) toast.add({ title: $t('common.success'), description: $t('applicationForms.submitted'), color: 'success' })
} }
} }

View File

@@ -1,13 +1,13 @@
<template> <template>
<UDashboardPanel id="home"> <UDashboardPanel id="home">
<template #header> <template #header>
<UDashboardNavbar title="Home" :ui="{ right: 'gap-3' }"> <UDashboardNavbar :title="$t('common.home')" :ui="{ right: 'gap-3' }">
<template #leading> <template #leading>
<UDashboardSidebarCollapse /> <UDashboardSidebarCollapse />
</template> </template>
<template #right> <template #right>
Aktuelle Organisation {{ $t('organization.current') }}
<USelect <USelect
v-model="selectedOrganizationId" v-model="selectedOrganizationId"
:items="organizations" :items="organizations"
@@ -20,7 +20,7 @@
class="w-48" class="w-48"
/> />
<UTooltip text="Notifications" :shortcuts="['N']"> <UTooltip :text="$t('notifications.tooltip')" :shortcuts="['N']">
<UButton color="neutral" variant="ghost" square @click="isNotificationsSlideoverOpen = true"> <UButton color="neutral" variant="ghost" square @click="isNotificationsSlideoverOpen = true">
<UChip :show="unreadCount > 0" color="error" inset> <UChip :show="unreadCount > 0" color="error" inset>
<UIcon name="i-lucide-bell" class="size-5 shrink-0" /> <UIcon name="i-lucide-bell" class="size-5 shrink-0" />
@@ -31,7 +31,7 @@
<UButton <UButton
icon="i-lucide-circle-plus" icon="i-lucide-circle-plus"
label="Neuer Mitbestimmungsantrag" :label="$t('applicationForms.createNew')"
to="/create" to="/create"
:disabled="!canWriteApplicationForms" :disabled="!canWriteApplicationForms"
size="xl" size="xl"
@@ -86,18 +86,18 @@
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-2 text-sm">
<UIcon name="i-lucide-pencil" class="size-4 text-muted shrink-0" /> <UIcon name="i-lucide-pencil" class="size-4 text-muted shrink-0" />
<span class="text-muted"> <span class="text-muted">
Zuletzt bearbeitet von {{ $t('applicationForms.lastEditedBy') }}
<span class="font-medium text-highlighted">{{ applicationFormElem.lastModifiedBy.name }}</span> <span class="font-medium text-highlighted">{{ applicationFormElem.lastModifiedBy.name }}</span>
am {{ formatDate(applicationFormElem.modifiedAt) }} {{ $t('common.on') }} {{ formatDate(applicationFormElem.modifiedAt) }}
</span> </span>
</div> </div>
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-2 text-sm">
<UIcon name="i-lucide-user-plus" class="size-4 text-muted shrink-0" /> <UIcon name="i-lucide-user-plus" class="size-4 text-muted shrink-0" />
<span class="text-muted"> <span class="text-muted">
Erstellt von {{ $t('applicationForms.createdBy') }}
<span class="font-medium text-highlighted">{{ applicationFormElem.createdBy.name }}</span> <span class="font-medium text-highlighted">{{ applicationFormElem.createdBy.name }}</span>
am {{ formatDate(applicationFormElem.createdAt) }} {{ $t('common.on') }} {{ formatDate(applicationFormElem.createdAt) }}
</span> </span>
</div> </div>
</div> </div>
@@ -122,6 +122,7 @@ const { getAllApplicationForms, deleteApplicationFormById } = useApplicationForm
const route = useRoute() const route = useRoute()
const userStore = useUserStore() const userStore = useUserStore()
const { organizations, selectedOrganization } = storeToRefs(userStore) const { organizations, selectedOrganization } = storeToRefs(userStore)
const { t: $t } = useI18n()
// Inject notification state from layout // Inject notification state from layout
const { isNotificationsSlideoverOpen, unreadCount } = inject('notificationState', { const { isNotificationsSlideoverOpen, unreadCount } = inject('notificationState', {
@@ -178,7 +179,7 @@ const applicationForms = computed({
function getLinksForApplicationForm(applicationForm: ApplicationFormDto) { function getLinksForApplicationForm(applicationForm: ApplicationFormDto) {
return [ return [
{ {
label: 'Löschen', label: $t('common.delete'),
icon: 'i-lucide-trash', icon: 'i-lucide-trash',
to: `?delete&id=${applicationForm.id}`, to: `?delete&id=${applicationForm.id}`,
disabled: !canWriteApplicationForms.value disabled: !canWriteApplicationForms.value

View File

@@ -1,40 +1,37 @@
<template> <template>
<UCard variant="subtle"> <UCard variant="subtle">
<template #header> <template #header>
<div class="text-center">
<UIcon name="i-lucide-lock" class="mx-auto h-16 w-16 text-primary-500 mb-6" />
<h1 class="text-3xl font-bold text-gray-900 mb-2">
Welcome
</h1>
<p class="text-gray-600">
You will be redirected to Keycloak to authenticate
</p>
</div>
</template>
<div class="text-center"> <div class="text-center">
<UButton <UIcon name="i-lucide-lock" class="mx-auto h-16 w-16 text-primary-500 mb-6" />
color="primary" <h1 class="text-3xl font-bold text-gray-900 mb-2">
size="xl" {{ $t('auth.welcome') }}
icon="i-lucide-log-in" </h1>
@click="handleSignIn" <p class="text-gray-600">
> {{ $t('auth.redirectMessage') }}
Sign in with Keycloak </p>
</UButton>
</div> </div>
</template>
<template #footer> <div class="text-center">
<div class="text-center text-xs text-gray-500"> <UButton color="primary" size="xl" icon="i-lucide-log-in" @click="handleSignIn">
By signing in, you agree to our terms of service {{ $t('auth.signIn') }}
</div> </UButton>
</template> </div>
</UCard>
<template #footer>
<div class="text-center text-xs text-gray-500">
{{ $t('auth.termsAgreement') }}
</div>
</template>
</UCard>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
definePageMeta({ auth: false, layout: 'auth' }) definePageMeta({ auth: false, layout: 'auth' })
useSeoMeta({ title: 'Login' }) const { t: $t } = useI18n()
useSeoMeta({ title: $t('auth.login') })
function handleSignIn() { function handleSignIn() {
navigateTo('/auth/keycloak', { external: true }) navigateTo('/auth/keycloak', { external: true })

View File

@@ -74,7 +74,7 @@ function flattenFormElements(form: ApplicationFormDto): Array<{ element: FormEle
for (const section of form.formElementSections) { for (const section of form.formElementSections) {
for (const subsection of section.formElementSubSections) { for (const subsection of section.formElementSubSections) {
for (const element of subsection.formElements) { for (const element of subsection.formElements) {
elements.push({ element, sectionTitle: section.title }) elements.push({ element, sectionTitle: section.title })
} }
} }
} }
@@ -88,7 +88,7 @@ function flattenSnapshotElements(
for (const section of snapshot.sections) { for (const section of snapshot.sections) {
for (const subsection of section.subsections) { for (const subsection of section.subsections) {
for (const element of subsection.elements) { for (const element of subsection.elements) {
elements.push({ element, sectionTitle: section.title }) elements.push({ element, sectionTitle: section.title })
} }
} }
} }
@@ -110,7 +110,7 @@ function compareElements(
position: number position: number
): ElementModification | null { ): ElementModification | null {
const optionsDiff = compareOptions(current.options, version.options) const optionsDiff = compareOptions(current.options, version.options)
if (optionsDiff.added.length > 0 || optionsDiff.removed.length > 0 || optionsDiff.modified.length > 0) { if (optionsDiff.added.length > 0 || optionsDiff.removed.length > 0 || optionsDiff.modified.length > 0) {
return { return {
sectionTitle, sectionTitle,

View File

@@ -1,10 +1,148 @@
{ {
"roles": { "applicationForms": {
"admin": "Administrator", "title": "Mitbestimmungsanträge",
"employee": "Arbeitnehmer", "createNew": "Neuer Mitbestimmungsantrag",
"employer": "Arbeitgeber", "noFormsAvailable": "Keine Anträge vorhanden",
"worksCouncilMember": "Betriebsratsmitglied", "noPermission": "Keine Berechtigung",
"worksCouncilChair": "Betriebsratsvorsitzender" "noPermissionDescription": "Sie haben keine Berechtigung zum Erstellen von Anträgen.",
"backToOverview": "Zurück zur Übersicht",
"deleteConfirm": "Möchten Sie wirklich den Mitbestimmungsantrag <strong>{name}</strong> löschen?",
"deleteTitle": "Mitbestimmungsantrag löschen",
"lastEditedBy": "Zuletzt bearbeitet von",
"createdBy": "Erstellt von",
"saved": "Antrag erfolgreich gespeichert",
"submitted": "Antrag erfolgreich eingereicht",
"deleted": "Antrag erfolgreich gelöscht",
"formElements": {
"comments": "Kommentare",
"addInputBelow": "Eingabefeld hinzufügen",
"selectPlaceholder": "Status auswählen",
"title": "Titel",
"text": "Text",
"unimplemented": "Element nicht implementiert:"
},
"status": {
"draft": "Entwurf",
"submitted": "Eingereicht",
"approved": "Genehmigt",
"rejected": "Abgelehnt",
"signed": "Signiert"
},
"navigation": {
"previous": "Zurück",
"next": "Weiter",
"save": "Speichern",
"submit": "Einreichen"
}
},
"templates": {
"title": "Vorlagen",
"editorTitle": "Administration - JSON Template Editor",
"newTemplate": "Neue Vorlage",
"reset": "Zurücksetzen",
"unsavedChanges": "Ungespeicherte Änderungen",
"noTemplateFound": "Keine Vorlage gefunden. Erstellen Sie eine neue Vorlage.",
"invalidJson": "Ungültiges JSON",
"invalidJsonDescription": "Das JSON-Format ist ungültig. Bitte korrigieren Sie die Syntax.",
"lastModified": "Zuletzt bearbeitet am",
"created": "Vorlage erfolgreich erstellt",
"updated": "Vorlage erfolgreich aktualisiert",
"saveError": "Fehler beim Speichern der Vorlage",
"unsavedWarning": "Sie haben ungespeicherte Änderungen. Möchten Sie die Seite wirklich verlassen?"
},
"versions": {
"title": "Versionen",
"pageTitle": "Versionen: {name}",
"empty": "Keine Versionen verfügbar",
"loading": "Versionen werden geladen...",
"loadError": "Fehler beim Laden der Versionen",
"loadErrorDescription": "Versionen konnten nicht geladen werden",
"unknownError": "Unbekannter Fehler",
"compare": "Vergleichen",
"restore": "Wiederherstellen",
"restored": "Erfolg",
"restoredDescription": "Das Formular wurde auf die ausgewählte Version zurückgesetzt.",
"restoreError": "Version konnte nicht wiederhergestellt werden",
"restoreTitle": "Version wiederherstellen",
"restoreConfirm": "Möchten Sie Version v{number} wirklich wiederherstellen?",
"restoreDescription": "Dies erstellt eine neue Version mit dem Inhalt der ausgewählten Version. Die aktuelle Version und alle Änderungen bleiben in der Historie erhalten.",
"comparisonTitle": "Vergleich: Aktuelles Formular mit Version v{number}",
"comparisonError": "Fehler beim Laden der Version",
"elementsAdded": "Hinzugefügte Elemente ({count})",
"elementsRemoved": "Entfernte Elemente ({count})",
"elementsModified": "Geänderte Elemente ({count})",
"elementWithoutTitle": "Ohne Titel",
"elementIn": "Element in",
"optionsAdded": "Optionen hinzugefügt ({count})",
"optionsRemoved": "Optionen entfernt ({count})",
"optionsModified": "Optionen geändert ({count})",
"noChanges": "Keine Unterschiede gefunden"
},
"comments": {
"title": "Kommentare",
"empty": "Keine Kommentare vorhanden",
"submit": "Absenden",
"edit": "Kommentar bearbeiten",
"editAction": "Bearbeiten"
},
"compliance": {
"title": "Compliance-Status",
"critical": "Kritisch",
"warning": "Warnung",
"nonCritical": "Unkritisch"
},
"notifications": {
"title": "Benachrichtigungen",
"empty": "Keine Benachrichtigungen",
"unreadCount": "{count} ungelesen",
"tooltip": "Benachrichtigungen"
},
"administration": {
"title": "Administration",
"accessDenied": "Zugriff verweigert"
},
"user": {
"profile": "Profil",
"administration": "Administration",
"settings": "Einstellungen",
"logout": "Abmelden",
"theme": "Design",
"themePrimary": "Primärfarbe",
"themeNeutral": "Neutralfarbe",
"appearance": "Erscheinungsbild",
"light": "Hell",
"dark": "Dunkel"
},
"organization": {
"current": "Aktuelle Organisation"
},
"auth": {
"welcome": "Willkommen",
"redirectMessage": "Sie werden zur Authentifizierung zu Keycloak weitergeleitet",
"signIn": "Mit Keycloak anmelden",
"termsAgreement": "Mit der Anmeldung stimmen Sie unseren Nutzungsbedingungen zu",
"login": "Anmelden"
},
"error": {
"pageNotFound": "Seite nicht gefunden",
"pageNotFoundDescription": "Es tut uns leid, aber diese Seite konnte nicht gefunden werden."
},
"common": {
"save": "Speichern",
"cancel": "Abbrechen",
"delete": "Löschen",
"edit": "Bearbeiten",
"close": "Schließen",
"confirm": "Bestätigen",
"back": "Zurück",
"loading": "Laden...",
"error": "Fehler",
"success": "Erfolg",
"name": "Name",
"home": "Home",
"type": "Typ",
"general": "Allgemein",
"on": "am"
}, },
"serverConnection": { "serverConnection": {
"title": "Verbindung zum Server unterbrochen", "title": "Verbindung zum Server unterbrochen",
@@ -13,5 +151,12 @@
"lastCheck": "Letzte Überprüfung", "lastCheck": "Letzte Überprüfung",
"retryInfo": "Automatischer Wiederholungsversuch alle 60 Sekunden", "retryInfo": "Automatischer Wiederholungsversuch alle 60 Sekunden",
"retryNow": "Jetzt erneut versuchen" "retryNow": "Jetzt erneut versuchen"
},
"roles": {
"admin": "Administrator",
"employee": "Arbeitnehmer",
"employer": "Arbeitgeber",
"worksCouncilMember": "Betriebsratsmitglied",
"worksCouncilChair": "Betriebsratsvorsitzender"
} }
} }

View File

@@ -1,10 +1,148 @@
{ {
"roles": { "applicationForms": {
"admin": "Administrator", "title": "Co-determination Applications",
"employee": "Employee", "createNew": "New Co-determination Application",
"employer": "Employer", "noFormsAvailable": "No applications available",
"worksCouncilMember": "Works Council Member", "noPermission": "No Permission",
"worksCouncilChair": "Works Council Chair" "noPermissionDescription": "You do not have permission to create applications.",
"backToOverview": "Back to Overview",
"deleteConfirm": "Do you really want to delete the co-determination application <strong>{name}</strong>?",
"deleteTitle": "Delete Co-determination Application",
"lastEditedBy": "Last edited by",
"createdBy": "Created by",
"saved": "Application form saved",
"submitted": "Application form submitted",
"deleted": "Application form deleted",
"formElements": {
"comments": "Comments",
"addInputBelow": "Add input field below",
"selectPlaceholder": "Select status",
"title": "Title",
"text": "Text",
"unimplemented": "Element unimplemented:"
},
"status": {
"draft": "Draft",
"submitted": "Submitted",
"approved": "Approved",
"rejected": "Rejected",
"signed": "Signed"
},
"navigation": {
"previous": "Previous",
"next": "Next",
"save": "Save",
"submit": "Submit"
}
},
"templates": {
"title": "Templates",
"editorTitle": "Administration - JSON Template Editor",
"newTemplate": "New Template",
"reset": "Reset",
"unsavedChanges": "Unsaved Changes",
"noTemplateFound": "No template found. Create a new template.",
"invalidJson": "Invalid JSON",
"invalidJsonDescription": "The JSON format is invalid. Please correct the syntax.",
"lastModified": "Last edited on",
"created": "Template successfully created",
"updated": "Template successfully updated",
"saveError": "Error saving template",
"unsavedWarning": "You have unsaved changes. Do you really want to leave this page?"
},
"versions": {
"title": "Versions",
"pageTitle": "Versions: {name}",
"empty": "No versions available",
"loading": "Loading versions...",
"loadError": "Error loading versions",
"loadErrorDescription": "Versions could not be loaded",
"unknownError": "Unknown error",
"compare": "Compare",
"restore": "Restore",
"restored": "Success",
"restoredDescription": "The form has been restored to the selected version.",
"restoreError": "Version could not be restored",
"restoreTitle": "Restore Version",
"restoreConfirm": "Do you really want to restore version v{number}?",
"restoreDescription": "This will create a new version with the content of the selected version. The current version and all changes will remain in the history.",
"comparisonTitle": "Comparison: Current Form with Version v{number}",
"comparisonError": "Error loading version",
"elementsAdded": "Added Elements ({count})",
"elementsRemoved": "Removed Elements ({count})",
"elementsModified": "Modified Elements ({count})",
"elementWithoutTitle": "Without Title",
"elementIn": "Element in",
"optionsAdded": "Options added ({count})",
"optionsRemoved": "Options removed ({count})",
"optionsModified": "Options modified ({count})",
"noChanges": "No differences found"
},
"comments": {
"title": "Comments",
"empty": "No comments available",
"submit": "Submit",
"edit": "Edit Comment",
"editAction": "Edit"
},
"compliance": {
"title": "Compliance Status",
"critical": "Critical",
"warning": "Warning",
"nonCritical": "Non-critical"
},
"notifications": {
"title": "Notifications",
"empty": "No notifications",
"unreadCount": "{count} unread",
"tooltip": "Notifications"
},
"administration": {
"title": "Administration",
"accessDenied": "Access denied"
},
"user": {
"profile": "Profile",
"administration": "Administration",
"settings": "Settings",
"logout": "Log out",
"theme": "Theme",
"themePrimary": "Primary Color",
"themeNeutral": "Neutral Color",
"appearance": "Appearance",
"light": "Light",
"dark": "Dark"
},
"organization": {
"current": "Current Organization"
},
"auth": {
"welcome": "Welcome",
"redirectMessage": "You will be redirected to Keycloak to authenticate",
"signIn": "Sign in with Keycloak",
"termsAgreement": "By signing in, you agree to our terms of service",
"login": "Login"
},
"error": {
"pageNotFound": "Page not found",
"pageNotFoundDescription": "We are sorry but this page could not be found."
},
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"close": "Close",
"confirm": "Confirm",
"back": "Back",
"loading": "Loading...",
"error": "Error",
"success": "Success",
"name": "Name",
"home": "Home",
"type": "Type",
"general": "General",
"on": "on"
}, },
"serverConnection": { "serverConnection": {
"title": "Server Connection Lost", "title": "Server Connection Lost",
@@ -13,5 +151,12 @@
"lastCheck": "Last check", "lastCheck": "Last check",
"retryInfo": "Automatic retry every 60 seconds", "retryInfo": "Automatic retry every 60 seconds",
"retryNow": "Try again now" "retryNow": "Try again now"
},
"roles": {
"admin": "Administrator",
"employee": "Employee",
"employer": "Employer",
"worksCouncilMember": "Works Council Member",
"worksCouncilChair": "Works Council Chair"
} }
} }

View File

@@ -28,4 +28,3 @@ export interface OptionModification {
value: string value: string
labelChanged: { from: string; to: string } labelChanged: { from: string; to: string }
} }