From 60f38a3117dd15da7456b875f16c81b7f2a6ef47 Mon Sep 17 00:00:00 2001 From: jon r <jon@allmende.io> Date: Thu, 3 Apr 2025 12:49:38 +0200 Subject: [PATCH] fix(front): translations --- front/src/LegacyLayout.vue | 105 ------------------ front/src/components/SetInstanceModal.vue | 4 + front/src/components/admin/SettingsGroup.vue | 3 +- front/src/components/channels/AlbumSelect.vue | 2 +- front/src/components/library/EditForm.vue | 6 +- front/src/components/library/FileUpload.vue | 4 +- front/src/locales/en_US.json | 21 +++- front/src/ui/components/Sidebar.vue | 13 --- front/src/ui/components/UploadGroupList.vue | 4 + front/src/ui/components/UploadList.vue | 4 + front/src/ui/components/UploadModal.vue | 4 +- front/src/ui/modals/Search.vue | 5 +- front/src/ui/modals/Upload.vue | 6 +- front/src/ui/pages/upload.vue | 4 + front/src/ui/pages/upload/all.vue | 4 + front/src/ui/pages/upload/index.vue | 4 + front/src/views/Search.vue | 3 +- front/src/views/content/libraries/Quota.vue | 2 +- front/src/views/content/upload/Home.vue | 4 + front/src/views/playlists/Detail.vue | 5 +- 20 files changed, 69 insertions(+), 138 deletions(-) delete mode 100644 front/src/LegacyLayout.vue diff --git a/front/src/LegacyLayout.vue b/front/src/LegacyLayout.vue deleted file mode 100644 index 4bf2925cc..000000000 --- a/front/src/LegacyLayout.vue +++ /dev/null @@ -1,105 +0,0 @@ -<script setup lang="ts"> -import { useIntervalFn, useStyleTag, useToggle, useWindowSize } from '@vueuse/core' -import { computed, defineAsyncComponent, nextTick, onMounted } from 'vue' - -import { useQueue } from '~/composables/audio/queue' -import { useStore } from '~/store' -import { useI18n } from 'vue-i18n' - -import onKeyboardShortcut from '~/composables/onKeyboardShortcut' - -const { t } = useI18n() - -const ChannelUploadModal = defineAsyncComponent(() => import('~/components/channels/UploadModal.vue')) -const PlaylistModal = defineAsyncComponent(() => import('~/components/playlists/PlaylistModal.vue')) -const FilterModal = defineAsyncComponent(() => import('~/components/moderation/FilterModal.vue')) -const ReportModal = defineAsyncComponent(() => import('~/components/moderation/ReportModal.vue')) -const ServiceMessages = defineAsyncComponent(() => import('~/components/ServiceMessages.vue')) -const ShortcutsModal = defineAsyncComponent(() => import('~/components/ShortcutsModal.vue')) -const AudioPlayer = defineAsyncComponent(() => import('~/components/audio/Player.vue')) -const Sidebar = defineAsyncComponent(() => import('~/components/Sidebar.vue')) -const Queue = defineAsyncComponent(() => import('~/components/Queue.vue')) - -const store = useStore() - -// Tracks -const { tracks } = useQueue() - -// Fake content -onMounted(async () => { - await nextTick() - document.getElementById('fake-content')?.classList.add('loaded') -}) - -// Styles -const customStylesheets = computed(() => { - return store.state.instance.frontSettings.additionalStylesheets ?? [] -}) - -useStyleTag(computed(() => store.state.instance.settings.ui.custom_css.value)) - -// Time ago -useIntervalFn(() => { - // used to redraw ago dates every minute - store.commit('ui/computeLastDate') -}, 1000 * 60) - -// Shortcuts -const [showShortcutsModal, toggleShortcutsModal] = useToggle(false) -onKeyboardShortcut('h', () => toggleShortcutsModal()) - -const { width } = useWindowSize() -</script> - -<template> - <div - :key="store.state.instance.instanceUrl" - :class="{ - 'has-bottom-player': tracks.length > 0, - 'queue-focused': store.state.ui.queueFocused - }" - > - <!-- here, we display custom stylesheets, if any --> - <link - v-for="url in customStylesheets" - :key="url" - rel="stylesheet" - property="stylesheet" - :href="url" - > - - <sidebar - :width="width" - @show:shortcuts-modal="toggleShortcutsModal" - /> - <service-messages /> - <transition name="queue"> - <queue v-show="store.state.ui.queueFocused" /> - </transition> - - <router-view v-slot="{ Component }"> - <template v-if="Component"> - <keep-alive :max="1"> - <Suspense> - <component :is="Component" /> - <template #fallback> - <!-- TODO (wvffle): Add loader --> - {{ t('App.loading') }} - </template> - </Suspense> - </keep-alive> - </template> - <template v-else> - <!-- Display a proper 404 page or error message --> - <h1>404 - Page Not Found</h1> - </template> - </router-view> - - <audio-player /> - <playlist-modal v-if="store.state.auth.authenticated" /> - <channel-upload-modal v-if="store.state.auth.authenticated" /> - <filter-modal v-if="store.state.auth.authenticated" /> - <report-modal /> - <shortcuts-modal v-model:show="showShortcutsModal" /> - </div> -</template> diff --git a/front/src/components/SetInstanceModal.vue b/front/src/components/SetInstanceModal.vue index a35e0f51c..765d40173 100644 --- a/front/src/components/SetInstanceModal.vue +++ b/front/src/components/SetInstanceModal.vue @@ -7,6 +7,8 @@ import { ref, computed, watch, nextTick } from 'vue' import { useStore } from '~/store' import { useI18n } from 'vue-i18n' +// TODO: Delete this file? + const { t } = useI18n() interface Props { @@ -65,6 +67,7 @@ const checkAndSwitch = async (url: string) => { </script> <template> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <Modal v-model="show" :title="t('views.ChooseInstance.header.chooseInstance')" @@ -181,4 +184,5 @@ const checkAndSwitch = async (url: string) => { </button> </div> </Modal> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> </template> diff --git a/front/src/components/admin/SettingsGroup.vue b/front/src/components/admin/SettingsGroup.vue index 48110fab5..73d79f229 100644 --- a/front/src/components/admin/SettingsGroup.vue +++ b/front/src/components/admin/SettingsGroup.vue @@ -250,8 +250,7 @@ const save = async () => { red > <h4 class="header"> - {{ group.label }}: - {{ t('components.admin.SettingsGroup.header.error') }} + {{ t('components.admin.SettingsGroup.header.error', {label: group.label}) }} </h4> <ul class="list"> <li diff --git a/front/src/components/channels/AlbumSelect.vue b/front/src/components/channels/AlbumSelect.vue index 094061eb1..8a2342dee 100644 --- a/front/src/components/channels/AlbumSelect.vue +++ b/front/src/components/channels/AlbumSelect.vue @@ -71,7 +71,7 @@ watch(albums, (value) => { icon="bi-plus" :to="useModal('album').to" > - Add Album + {{ t('components.channels.AlbumSelect.add') }} <AlbumModal v-model="model.channel" @created="fetchAlbums" diff --git a/front/src/components/library/EditForm.vue b/front/src/components/library/EditForm.vue index 15b51a2c3..401f21af1 100644 --- a/front/src/components/library/EditForm.vue +++ b/front/src/components/library/EditForm.vue @@ -6,6 +6,7 @@ import { computed, reactive, ref } from 'vue' import { isEqual, clone } from 'lodash-es' import { useI18n } from 'vue-i18n' import { useStore } from '~/store' +import { useRoute } from 'vue-router' import axios from 'axios' @@ -36,6 +37,7 @@ const props = withDefaults(defineProps<Props>(), { const { t } = useI18n() const configs = useEditConfigs() const store = useStore() +const route = useRoute() const config = computed(() => configs[props.objectType]) const currentState = computed(() => config.value.fields.reduce((state: ReviewState, field) => { @@ -157,13 +159,15 @@ const resetField = (fieldId: string) => { {{ t('components.library.EditForm.button.new') }} </Button> + <!-- TODO: Implement link back to all types of object --> <Link + v-if="route.path.includes('album')" solid secondary raised :to="{ name: 'library.albums.detail', params: { id: object.id } }" > - Back to Album + {{ t('components.library.EditForm.button.backToAlbum') }} </Link> </Alert> <Layout diff --git a/front/src/components/library/FileUpload.vue b/front/src/components/library/FileUpload.vue index 259e9aee4..278b77f4e 100644 --- a/front/src/components/library/FileUpload.vue +++ b/front/src/components/library/FileUpload.vue @@ -426,7 +426,7 @@ const isServerDisclosureOpen = ref(false) 'yellow': files.length > uploadedFilesCount + erroredFilesCount }" > - {{ uploadedFilesCount + erroredFilesCount }} / {{ files.length }} + {{ t('components.library.FileUpload.table.upload.progressNum', {current: uploadedFilesCount + erroredFilesCount, total: files.length}) }} </Pill> </Layout> <Layout @@ -435,7 +435,7 @@ const isServerDisclosureOpen = ref(false) > <label>{{ t('components.library.FileUpload.link.processing') }}</label> <Pill> - {{ processedFilesCount }} / {{ processableFiles }} + {{ t('components.library.FileUpload.table.upload.progressNum', {current: processedFilesCount, total: processableFiles}) }} </Pill> </Layout> </Layout> diff --git a/front/src/locales/en_US.json b/front/src/locales/en_US.json index f06a2852c..91afccea1 100644 --- a/front/src/locales/en_US.json +++ b/front/src/locales/en_US.json @@ -330,7 +330,7 @@ "save": "Save" }, "header": { - "error": "Error while saving settings.", + "error": "{label}: Error while saving settings.", "image": "Current image" }, "message": { @@ -1027,6 +1027,7 @@ } }, "AlbumSelect": { + "add": "Add Album", "label": { "album": "Album", "series": "Series" @@ -1642,6 +1643,7 @@ }, "EditForm": { "button": { + "backToAlbum": "Back to Album", "cancel": "Cancel", "clear": "Clear", "new": "Submit another edit", @@ -1716,6 +1718,7 @@ "size": "Size", "status": "Status" }, + "progressNum": "{current} / {total}", "progress": "{percent}%", "status": { "pending": "Pending", @@ -3253,6 +3256,16 @@ "newAppVersion": "A new version of the app is available." } }, + "modals": { + "search": { + "tryAgain": "If the following link does not work, wait a few seconds and try again" + }, + "upload": { + "library": "Host music you listen to", + "musicChannel": "Publish music you make", + "podcastChannel": "Publish podcasts you make" + } + }, "views": { "ChooseInstance": { "button": { @@ -4371,7 +4384,7 @@ "currentUsage": "Current usage" }, "label": { - "currentUsage": "{amount} used on {max} allowed", + "currentUsage": "{currentAmount} used on {max} allowed", "errored": "Errored files", "pending": "Pending files", "percentUsed": "{progress}%", @@ -4583,7 +4596,9 @@ "tracks": "Tracks" }, "meta": { - "tracks": "Playlist containing {n} track, by {username} | Playlist containing {n} tracks, by {username}" + "attribution": "by", + "tracks": "Playlist containing {n} track, by {username} | Playlist containing {n} tracks, by {username}", + "updated": "updated" }, "modal": { "delete": { diff --git a/front/src/ui/components/Sidebar.vue b/front/src/ui/components/Sidebar.vue index 6a11b990c..640164d0e 100644 --- a/front/src/ui/components/Sidebar.vue +++ b/front/src/ui/components/Sidebar.vue @@ -355,19 +355,6 @@ const moderationNotifications = computed(() => {{ t('components.Sidebar.link.about') }} </Link> <Spacer shrink /> - <Link - thin-font - to="/privacy" - > - Privacy - </Link> - <Spacer shrink /> - <Link - thin-font - to="/legal" - > - Legal - </Link> </Layout> </Layout> </Layout> diff --git a/front/src/ui/components/UploadGroupList.vue b/front/src/ui/components/UploadGroupList.vue index 3782389b5..3dcdef5ec 100644 --- a/front/src/ui/components/UploadGroupList.vue +++ b/front/src/ui/components/UploadGroupList.vue @@ -6,6 +6,8 @@ import UploadList from '~/ui/components/UploadList.vue' import { UseTimeAgo } from '@vueuse/components' import { Icon } from '@iconify/vue' +// TODO: Delete this file, please. + defineProps<{ groups: UploadGroup[], isUploading?: boolean }>() const openUploadGroup = ref<UploadGroup>() @@ -45,6 +47,7 @@ const getDescription = (group: UploadGroup) => { </script> <template> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <div> <div v-for="group of groups" @@ -161,6 +164,7 @@ const getDescription = (group: UploadGroup) => { </VerticalCollapse> </div> </div> + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> </template> <style scoped lang="scss"> diff --git a/front/src/ui/components/UploadList.vue b/front/src/ui/components/UploadList.vue index 26380cda5..a13db2021 100644 --- a/front/src/ui/components/UploadList.vue +++ b/front/src/ui/components/UploadList.vue @@ -10,9 +10,12 @@ defineProps<{ wide?: boolean }>() +// TODO: Delete this file, please. + </script> <template> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <div class="file-list"> <div v-for="track in uploads" @@ -106,6 +109,7 @@ defineProps<{ /> </div> </div> + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> </template> <style scoped lang="scss"> diff --git a/front/src/ui/components/UploadModal.vue b/front/src/ui/components/UploadModal.vue index b25ba5e3e..4335d049e 100644 --- a/front/src/ui/components/UploadModal.vue +++ b/front/src/ui/components/UploadModal.vue @@ -63,7 +63,7 @@ const continueInBackground = () => { return router.push('/upload/running') } -// TODO (whole file): Translations +// TODO (whole file): Delete this file, please. // Sorting const sortItems = reactive([ @@ -94,6 +94,7 @@ const isOpen = computed({ </script> <template> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <Modal v-model="isOpen" title="Upload..." @@ -162,6 +163,7 @@ const isOpen = computed({ </Button> </template> </Modal> + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> </template> <style scoped lang="scss"> diff --git a/front/src/ui/modals/Search.vue b/front/src/ui/modals/Search.vue index 8920a3286..b684ce3a1 100644 --- a/front/src/ui/modals/Search.vue +++ b/front/src/ui/modals/Search.vue @@ -492,7 +492,7 @@ watch(queryDebounced, search, { immediate: true }) <!-- If response has "url": "webfinger://node1@node1.funkwhale.test" -> Link to go directly to the federation page --> <span v-if="category.type === 'rss' && count(category) > 0"> - <Alert>If the following link does not work, wait a few seconds and try again</Alert> + <Alert>{{ t('modals.search.tryAgain') }}</Alert> <Link v-for="channel in resultsPerCategory(category)" :key="channel.artist.fid" @@ -504,7 +504,8 @@ watch(queryDebounced, search, { immediate: true }) </span> <span v-else-if="category.type === 'federation'"> - TODO: {{ resultsPerCategory(category) }} + <!-- TODO: Federation search: backend adapter + display, fix results_per_category query --> + <!-- {{ resultsPerCategory(category) }} --> </span> <EmptyState diff --git a/front/src/ui/modals/Upload.vue b/front/src/ui/modals/Upload.vue index efdb1f5fe..14658c6fe 100644 --- a/front/src/ui/modals/Upload.vue +++ b/front/src/ui/modals/Upload.vue @@ -110,7 +110,7 @@ const channelUpload = ref() :class="$style.icon" /> </template> - {{ "Host music you listen to" /* TODO: Translate */ }} + {{ t('modals.upload.library') }} </Card> <Card small @@ -125,7 +125,7 @@ const channelUpload = ref() :class="$style.icon" /> </template> - {{ "Publish music you make" /* TODO: Translate */ }} + {{ t('modals.upload.musicChannel') }} </Card> <Card small @@ -140,7 +140,7 @@ const channelUpload = ref() :class="$style.icon" /> </template> - {{ "Publish podcasts you make" /* TODO: Translate */ }} + {{ t('modals.upload.podcastChannel') }} </Card> </Layout> diff --git a/front/src/ui/pages/upload.vue b/front/src/ui/pages/upload.vue index 57876765d..e3d08fe3e 100644 --- a/front/src/ui/pages/upload.vue +++ b/front/src/ui/pages/upload.vue @@ -4,6 +4,8 @@ import { useUploadsStore } from '~/ui/stores/upload' import { bytesToHumanSize } from '~/ui/composables/bytes' import UploadModal from '~/ui/components/UploadModal.vue' +// TODO: Delete this file? + const filesystemStats = reactive({ total: 10737418240, used: 3e9 @@ -40,6 +42,7 @@ const tabs = computed(() => [ </script> <template> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <div class="flex items-center"> <h1 class="mr-auto"> Upload @@ -82,6 +85,7 @@ const tabs = computed(() => [ <RouterView /> <UploadModal /> + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> </template> <style scoped lang="scss"> diff --git a/front/src/ui/pages/upload/all.vue b/front/src/ui/pages/upload/all.vue index ac47a4802..7cae73c72 100644 --- a/front/src/ui/pages/upload/all.vue +++ b/front/src/ui/pages/upload/all.vue @@ -5,6 +5,8 @@ import { bytesToHumanSize } from '~/ui/composables/bytes' import { useUploadsStore, type UploadGroupEntry } from '~/ui/stores/upload' import CoverArt from '~/ui/components/CoverArt.vue' +// TODO: Delete this file? + interface Recording { guid: string title: string @@ -49,6 +51,7 @@ const columns = [ </script> <template> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <div v-if="allTracks.length === 0" class="flex flex-col items-center py-32" @@ -80,6 +83,7 @@ const columns = [ {{ intl.format(value) }} </template> </FwTable> + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> </template> <style scoped> diff --git a/front/src/ui/pages/upload/index.vue b/front/src/ui/pages/upload/index.vue index d9b371116..4361fd18d 100644 --- a/front/src/ui/pages/upload/index.vue +++ b/front/src/ui/pages/upload/index.vue @@ -5,6 +5,8 @@ import { ref } from 'vue' import axios from 'axios' import { useAsyncState } from '@vueuse/core' +// TODO: Delete this file? + interface Tab { label: string icon: string @@ -49,6 +51,7 @@ const { state: items } = useAsyncState( </script> <template> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <div class="upload"> <p> Select a destination for your audio files: </p> @@ -98,6 +101,7 @@ const { state: items } = useAsyncState( Open library </FwButton> </div> + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> </template> <style scoped lang="scss"> diff --git a/front/src/views/Search.vue b/front/src/views/Search.vue index 394d6b375..b9ce02b42 100644 --- a/front/src/views/Search.vue +++ b/front/src/views/Search.vue @@ -22,6 +22,8 @@ import Button from '~/components/ui/Button.vue' import useErrorHandler from '~/composables/useErrorHandler' import useLogger from '~/composables/useLogger' +// TODO: Depreciate & refactor essential functionality to new ui/modals/Search.vue + type QueryType = 'artists' | 'albums' | 'tracks' | 'playlists' | 'tags' | 'radios' | 'podcasts' | 'series' | 'rss' const type = useRouteQuery<QueryType>('type', 'artists') @@ -226,7 +228,6 @@ const radioConfig = computed(() => { class="main" > <section class="ui vertical stripe segment"> - /front/src/components/audio/Search.vue <div v-if="id" class="ui small text container" diff --git a/front/src/views/content/libraries/Quota.vue b/front/src/views/content/libraries/Quota.vue index d1bbcdc32..91338cf16 100644 --- a/front/src/views/content/libraries/Quota.vue +++ b/front/src/views/content/libraries/Quota.vue @@ -84,7 +84,7 @@ const purgeErroredFiles = () => purge('errored') v-if="quotaStatus" class="label" > - {{ t('views.content.libraries.Quota.label.currentUsage', {max: humanSize(quotaStatus.max * 1000 * 1000), current: humanSize(quotaStatus.current * 1000 * 1000)}) }} + {{ t('views.content.libraries.Quota.label.currentUsage', {max: humanSize(quotaStatus.max * 1000 * 1000), currentAmount: humanSize(quotaStatus.current * 1000 * 1000)}) }} </div> </div> <div class="ui hidden divider" /> diff --git a/front/src/views/content/upload/Home.vue b/front/src/views/content/upload/Home.vue index 3e406f327..767008bd5 100644 --- a/front/src/views/content/upload/Home.vue +++ b/front/src/views/content/upload/Home.vue @@ -21,6 +21,8 @@ import Upload from '~/ui/pages/upload.vue' import useErrorHandler from '~/composables/useErrorHandler' +// TODO: Delete this file. + const { t } = useI18n() const router = useRouter() @@ -103,6 +105,7 @@ const openModal = (object_: Library | Channel) => { <template> <!-- TODO: Remove this module --> + <!-- eslint-disable @intlify/vue-i18n/no-raw-text --> <section v-title="labels.title" class="ui vertical aligned stripe segment" @@ -160,4 +163,5 @@ const openModal = (object_: Library | Channel) => { <upload /> </fw-modal> <!-- <channel-upload-modal v-if="store.state.auth.authenticated" /> --> + <!-- eslint-enable @intlify/vue-i18n/no-raw-text --> </template> diff --git a/front/src/views/playlists/Detail.vue b/front/src/views/playlists/Detail.vue index 54cf4aae3..f3fc007fb 100644 --- a/front/src/views/playlists/Detail.vue +++ b/front/src/views/playlists/Detail.vue @@ -162,15 +162,14 @@ const deletePlaylist = async () => { flex gap-8 > - <!-- TODO: Translations --> - by + {{ t('views.playlists.Detail.meta.attribution') }} <ActorLink :actor="playlist.actor" :avatar="false" :discrete="true" /> <i class="bi bi-dot" /> - updated + {{ t('views.playlists.Detail.meta.updated') }} <HumanDate :date="playlist.modification_date" />