Merge pull request 'main' (#6) from RemixDev/deemix-webui:main into main

Reviewed-on: https://codeberg.org/m3troux/deemix-webui/pulls/6
This commit is contained in:
m3troux 2020-08-22 10:08:26 +02:00
commit 242b6f9e90
24 changed files with 706 additions and 141 deletions

1
.gitignore vendored
View File

@ -3,7 +3,6 @@ __pycache__
.DS_Store .DS_Store
node_modules node_modules
jsconfig.json
# pyinstaller build dirs # pyinstaller build dirs
/dist /dist

View File

@ -5,33 +5,33 @@ This is just the WebUI for deemix, it should be used with deemix-pyweb or someth
## What's left to do? ## What's left to do?
- [ ] Use Vue app-wide - [ ] Use Vue app-wide
- First step: rewrite the app in Single File Components way - [X] First step: rewrite the app in Single File Components way
- Second step: Implement routing for the whole app using Vue Router ⚒ - [ ] Second step: Implement routing for the whole app using Vue Router ⚒
- Third step: Remove jQuery - [ ] Third step: Remove jQuery
- [ ] Implement custom contextmenu ⚒ - [ ] Implement custom contextmenu ⚒
- Copy and paste functions - [X] Copy and paste functions
- Copy Link where possible - [X] Copy Link where possible
- Download Quality - [X] Download Quality
- Copy Image URL where possible - [X] Copy Image URL where possible
- Resolve problem when positioning out of window (e.g. clicking on the bottom of the window) - [ ] Resolve cut/copy/paste compatibility issues
- [ ] Make i18n async (https://kazupon.github.io/vue-i18n/guide/lazy-loading.html) - [ ] Make i18n async (https://kazupon.github.io/vue-i18n/guide/lazy-loading.html)
- Use ES2020 async imports, if possible - Use ES2020 async imports, if possible
- [ ] Make the UI look coherent - [ ] Make the UI look coherent
- Style buttons - [ ] Style buttons
- Style text inputs - [ ] Style text inputs
- Style checkboxes - [ ] Style checkboxes
- [ ] Search tab - [ ] Search tab
- Better placeholer before search - [ ] Better placeholer before search
- [ ] Link Analyzer - [ ] Link Analyzer
- Better placeholer before analyzing and error feedback - [ ] Better placeholer before analyzing and error feedback
- [ ] Settings tab - [ ] Settings tab
- [ ] Variable selector near template inputs
- Maybe tabbing the section for easy navigation - Maybe tabbing the section for easy navigation
- Could use a carousel, but it's not worth adding a new dep - Could use a carousel, but it's not worth adding a new dep
- Variable selector near template inputs
- [ ] Block selection where it's not needed (keep only titles artists albums labels and useful data) - [ ] Block selection where it's not needed (keep only titles artists albums labels and useful data)
- There's a SASS mixin for this. Need to use it in the proper classes - There's a SASS mixin for this. Need to use it in the proper classes
- [ ] Better feedback for socket.io possible errors - [ ] Better feedback for socket.io possible errors
- [ ] Remove images size limit and add warning if > 1200 - [X] Remove images size limit and add warning if > 1200
- ? - ?
# License # License

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -122,6 +122,10 @@ socket.on('logged_out', function() {
document.getElementById('home_not_logged_in').classList.remove('hide') document.getElementById('home_not_logged_in').classList.remove('hide')
}) })
socket.on('restoringQueue', function() {
toast(i18n.t('toasts.restoringQueue'), 'loading', false, 'restoring_queue')
})
socket.on('cancellingCurrentItem', function(uuid) { socket.on('cancellingCurrentItem', function(uuid) {
toast(i18n.t('toasts.cancellingCurrentItem'), 'loading', false, 'cancelling_' + uuid) toast(i18n.t('toasts.cancellingCurrentItem'), 'loading', false, 'cancelling_' + uuid)
}) })

102
src/assets/ar.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,3 +0,0 @@
<svg viewBox="0 0 65 65" xmlns="http://www.w3.org/2000/svg">
<path d="M37.4984 36.7246C37.7727 37.3718 37.822 38.0823 37.6813 38.9194C37.4984 39.8901 37.0763 40.6499 36.408 41.2408C35.7327 41.8106 35.0011 42.1552 34.1922 42.2326C33.3762 42.31 32.3843 42.2326 31.2166 42.0216L29.1977 41.6277C28.5294 41.494 28.1284 41.3955 27.9807 41.29C27.84 41.1915 27.7626 41.0508 27.7274 40.8539C27.7134 40.7061 27.7696 40.1856 27.9103 39.3485L29.0288 33.3691L32.6094 34.0304C34.1781 34.3329 35.3177 34.6987 36.0211 35.1207C36.7316 35.5428 37.217 36.0915 37.4984 36.7246ZM37.6883 24.1539C37.0552 23.8022 35.8453 23.4645 34.0726 23.1339L31.0477 22.5641L29.7815 29.2962L32.8134 29.8659C34.3962 30.1544 35.592 30.2458 36.401 30.1192C37.21 29.9855 37.8431 29.6971 38.3355 29.2399C38.8068 28.7967 39.1234 28.1988 39.25 27.4953C39.3907 26.7637 39.3203 26.1236 39.053 25.5327C38.7998 24.9629 38.3496 24.4916 37.6883 24.1539ZM64.5179 32.518C64.5179 50.2027 50.1886 64.518 32.5109 64.518C14.8402 64.518 0.517944 50.2027 0.517944 32.518C0.517944 14.8332 14.8402 0.51796 32.5109 0.51796C50.1886 0.51796 64.5179 14.8332 64.5179 32.518ZM45.6866 24.0625C45.1449 22.8807 44.2304 21.8818 42.9712 21.0587C42.4929 20.7351 41.8809 20.4538 41.2126 20.1935L42.1201 15.3326L37.4491 14.4533L36.5909 18.9976L33.5872 18.4349L34.4454 13.8906L29.7675 13.0113L28.9092 17.5626L21.7692 16.226L21.0587 20.0458L22.1491 20.2498C22.8807 20.3764 23.359 20.5382 23.5841 20.7211C23.8233 20.8899 23.964 21.108 24.0273 21.3542C24.0906 21.6004 24.0484 22.128 23.8936 22.9581L21.0095 38.3496C20.8618 39.1515 20.7211 39.665 20.5663 39.869C20.4115 40.073 20.2005 40.2278 19.9191 40.3052C19.6378 40.3825 19.1383 40.3544 18.4137 40.1996L17.3164 40.0168L16.6059 43.8224L23.7389 45.1449L22.8947 49.6892L27.5727 50.5685L28.4168 46.0242L31.0759 46.5166C31.1955 46.5448 31.301 46.5518 31.4206 46.5799L30.5764 51.1242L35.2473 51.9895L36.1759 47.0724C36.9849 47.0583 37.7094 46.988 38.3496 46.8824C40.0871 46.5377 41.5081 45.8272 42.6265 44.751C43.738 43.6958 44.4344 42.4296 44.7017 40.9594C44.9339 39.7213 44.8494 38.5536 44.4555 37.4702C44.0545 36.3588 43.4144 35.4373 42.5281 34.6916C41.9231 34.1711 40.9945 33.6224 39.7705 33.0596C41.0508 32.8697 42.113 32.5391 42.9642 32.0396C43.8224 31.5472 44.5188 30.9282 45.0534 30.1755C45.5951 29.4298 45.9609 28.5927 46.1227 27.6852C46.3759 26.4894 46.2282 25.2513 45.6866 24.0625Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -82,10 +82,6 @@
<strong>PayPal:</strong> <strong>PayPal:</strong>
<a href="https://paypal.me/RemixDev" target="_blank">PayPal.me/RemixDev</a> <a href="https://paypal.me/RemixDev" target="_blank">PayPal.me/RemixDev</a>
</li> </li>
<li>
<i class="bitcoin" v-html="bitcoin" />
<strong>Bitcoin:</strong> 1sdNymSJrMBWyHM4u2m9uco5nv6uV4Qs1
</li>
<li> <li>
<i class="ethereum" v-html="ethereum" /> <i class="ethereum" v-html="ethereum" />
<strong>Ethereum:</strong> 0x1d2aa67e671485CD4062289772B662e0A6Ff976c <strong>Ethereum:</strong> 0x1d2aa67e671485CD4062289772B662e0A6Ff976c
@ -123,10 +119,6 @@ i /deep/ svg {
width: 20px; width: 20px;
} }
.bitcoin /deep/ svg {
fill: #ff9900;
}
.ethereum /deep/ svg { .ethereum /deep/ svg {
fill: var(--foreground); fill: var(--foreground);
} }
@ -200,14 +192,12 @@ ul {
</style> </style>
<script> <script>
import paypal from '@/assets/paypal.svg' import paypal from '@/assets/paypal.svg'
import bitcoin from '@/assets/bitcoin.svg'
import ethereum from '@/assets/ethereum.svg' import ethereum from '@/assets/ethereum.svg'
export default { export default {
data: () => ({ data: () => ({
paypal, paypal,
ethereum, ethereum
bitcoin
}) })
} }
</script> </script>

View File

@ -15,7 +15,7 @@
<script> <script>
import Downloads from '@/utils/downloads' import Downloads from '@/utils/downloads'
import downloadQualities from '@js/qualities' import downloadQualities from '@js/qualities'
import { generatePath } from '@/utils/utils' import { generatePath, copyToClipboard } from '@/utils/utils'
export default { export default {
data() { data() {
@ -36,7 +36,7 @@ export default {
const options = { const options = {
cut: { cut: {
label: this.$t('globals.cut'), label: this.$t('globals.cut'),
show: true, show: false,
position: 1, position: 1,
action: () => { action: () => {
document.execCommand('Cut') document.execCommand('Cut')
@ -44,7 +44,7 @@ export default {
}, },
copy: { copy: {
label: this.$t('globals.copy'), label: this.$t('globals.copy'),
show: true, show: false,
position: 2, position: 2,
action: () => { action: () => {
document.execCommand('Copy') document.execCommand('Copy')
@ -55,9 +55,7 @@ export default {
show: false, show: false,
position: 3, position: 3,
action: () => { action: () => {
navigator.clipboard.writeText(this.generalHref).catch(err => { copyToClipboard(this.generalHref)
console.error('Link copying failed', err)
})
} }
}, },
copyImageLink: { copyImageLink: {
@ -65,9 +63,7 @@ export default {
show: false, show: false,
position: 4, position: 4,
action: () => { action: () => {
navigator.clipboard.writeText(this.imgSrc).catch(err => { copyToClipboard(this.imgSrc)
console.error('Image copying failed', err)
})
} }
}, },
copyDeezerLink: { copyDeezerLink: {
@ -75,19 +71,22 @@ export default {
show: false, show: false,
position: 5, position: 5,
action: () => { action: () => {
navigator.clipboard.writeText(this.generalHref).catch(err => { copyToClipboard(this.deezerHref)
console.error('Deezer link copying failed', err)
})
} }
}, },
paste: { paste: {
label: this.$t('globals.paste'), label: this.$t('globals.paste'),
show: true, show: false,
position: 6, position: 6,
action: () => { action: () => {
// Paste does not always work
if (clipboard in navigator) {
navigator.clipboard.readText().then(text => { navigator.clipboard.readText().then(text => {
document.execCommand('insertText', undefined, text) document.execCommand('insertText', undefined, text)
}) })
} else {
document.execCommand('paste')
}
} }
} }
} }
@ -105,9 +104,13 @@ export default {
return options return options
}, },
// This computed property is used for rendering the options in the wanted order /**
// while keeping the options computed property an Object to make the properties * This computed property is used for rendering the options in the wanted order
// accessible via property name (es this.options.copyLink) * while keeping the options computed property an Object to make the properties
* accessible via property name (es this.options.copyLink)
*
* @return {object[]} Options in order according to position property
*/
sortedOptions() { sortedOptions() {
return Object.values(this.options).sort((first, second) => { return Object.values(this.options).sort((first, second) => {
return first.position < second.position ? -1 : 1 return first.position < second.position ? -1 : 1
@ -120,28 +123,13 @@ export default {
}, },
methods: { methods: {
showMenu(contextMenuEvent) { showMenu(contextMenuEvent) {
contextMenuEvent.preventDefault() // contextMenuEvent.preventDefault()
const { pageX, pageY, target: elementClicked } = contextMenuEvent const { pageX, pageY, target: elementClicked } = contextMenuEvent
const path = generatePath(elementClicked) const path = generatePath(elementClicked)
this.positionMenu(pageX, pageY)
// Show 'Copy Link' option
if (elementClicked.matches('a')) {
this.generalHref = elementClicked.href
this.options.copyLink.show = true
}
// Show 'Copy Image Link' option
if (elementClicked.matches('img')) {
this.imgSrc = elementClicked.src
this.options.copyImageLink.show = true
}
let deezerLink = null let deezerLink = null
// Searching for the first element with a data-link attribute
// let deezerLink = this.searchForDataLink(...)
for (let i = 0; i < path.length; i++) { for (let i = 0; i < path.length; i++) {
if (path[i] == document) break if (path[i] == document) break
@ -151,13 +139,33 @@ export default {
} }
} }
// Show 'Copy Deezer Link' option const isLink = elementClicked.matches('a')
const isImage = elementClicked.matches('img')
const hasDeezerLink = !!deezerLink
if (!isLink && !isImage && !hasDeezerLink) return
contextMenuEvent.preventDefault()
this.menuOpen = true
this.positionMenu(pageX, pageY)
if (isLink) {
// Show 'Copy Link' option
this.generalHref = elementClicked.href
this.options.copyLink.show = true
}
if (isImage) {
// Show 'Copy Image Link' option
this.imgSrc = elementClicked.src
this.options.copyImageLink.show = true
}
if (deezerLink) { if (deezerLink) {
// Show 'Copy Deezer Link' option
this.deezerHref = deezerLink this.deezerHref = deezerLink
this.showDeezerOptions() this.showDeezerOptions()
} }
this.menuOpen = true
}, },
hideMenu() { hideMenu() {
if (!this.menuOpen) return if (!this.menuOpen) return
@ -182,6 +190,22 @@ export default {
positionMenu(newX, newY) { positionMenu(newX, newY) {
this.xPos = `${newX}px` this.xPos = `${newX}px`
this.yPos = `${newY}px` this.yPos = `${newY}px`
this.$nextTick().then(() => {
const { innerHeight, innerWidth } = window
const menuXOffest = newX + this.$refs.contextMenu.getBoundingClientRect().width
const menuYOffest = newY + this.$refs.contextMenu.getBoundingClientRect().height
if (menuXOffest > innerWidth) {
const difference = menuXOffest - innerWidth + 15
this.xPos = `${newX - difference}px`
}
if (menuYOffest > innerHeight) {
const difference = menuYOffest - innerHeight + 15
this.yPos = `${newY - difference}px`
}
})
}, },
showDeezerOptions() { showDeezerOptions() {
this.options.copyDeezerLink.show = true this.options.copyDeezerLink.show = true
@ -203,9 +227,10 @@ export default {
top: 0; top: 0;
left: 0; left: 0;
min-width: 100px; min-width: 100px;
background: var(--foreground-inverted);
border-radius: 7px; border-radius: 7px;
background: var(--foreground-inverted);
box-shadow: 4px 10px 18px 0px hsla(0, 0%, 0%, 0.15); box-shadow: 4px 10px 18px 0px hsla(0, 0%, 0%, 0.15);
overflow: hidden;
z-index: 10000; z-index: 10000;
} }
@ -219,14 +244,6 @@ export default {
color: var(--foreground); color: var(--foreground);
cursor: pointer; cursor: pointer;
&:first-child {
border-radius: 7px 7px 0 0;
}
&:last-child {
border-radius: 0 0 7px 7px;
}
&:hover { &:hover {
background: var(--table-highlight); background: var(--table-highlight);
filter: brightness(150%); filter: brightness(150%);

View File

@ -15,9 +15,7 @@
:title="$t('globals.toggle_download_tab_hint')" :title="$t('globals.toggle_download_tab_hint')"
></i> ></i>
<div id="queue_buttons"> <div id="queue_buttons">
<i id="open_downloads_folder" class="material-icons download_bar_icon hide" @click="openDownloadsFolder"> <i id="open_downloads_folder" class="material-icons download_bar_icon hide" :title="$t('globals.open_downloads_folder')" @click="openDownloadsFolder">folder_open</i>
folder_open
</i>
<i id="clean_queue" class="material-icons download_bar_icon" @click="cleanQueue" :title="$t('globals.clean_queue_hint')">clear_all</i> <i id="clean_queue" class="material-icons download_bar_icon" @click="cleanQueue" :title="$t('globals.clean_queue_hint')">clear_all</i>
<i id="cancel_queue" class="material-icons download_bar_icon" @click="cancelQueue" :title="$t('globals.cancel_queue_hint')">delete_sweep</i> <i id="cancel_queue" class="material-icons download_bar_icon" @click="cancelQueue" :title="$t('globals.cancel_queue_hint')">delete_sweep</i>
</div> </div>
@ -42,6 +40,7 @@ export default {
}), }),
mounted() { mounted() {
socket.on('startDownload', this.startDownload) socket.on('startDownload', this.startDownload)
socket.on('startConversion', this.startConversion)
socket.on('init_downloadQueue', this.initQueue) socket.on('init_downloadQueue', this.initQueue)
socket.on('addedToQueue', this.addToQueue) socket.on('addedToQueue', this.addToQueue)
socket.on('updateQueue', this.updateQueue) socket.on('updateQueue', this.updateQueue)
@ -97,26 +96,43 @@ export default {
} }
}, },
initQueue(data) { initQueue(data) {
const { queue: initQueue, queueComplete: initQueueComplete, currentItem, queueList: initQueueList } = data const { queue: initQueue, queueComplete: initQueueComplete, currentItem, queueList: initQueueList, restored } = data
if (initQueueComplete.length) { if (initQueueComplete.length) {
initQueueComplete.forEach(item => { initQueueComplete.forEach(item => {
initQueueList[item].init = true initQueueList[item].silent = true
this.addToQueue(initQueueList[item]) this.addToQueue(initQueueList[item])
}) })
} }
if (currentItem) { if (currentItem) {
initQueueList[currentItem].init = true initQueueList[currentItem].silent = true
this.addToQueue(initQueueList[currentItem], true) this.addToQueue(initQueueList[currentItem], true)
} }
initQueue.forEach(item => { initQueue.forEach(item => {
initQueueList[item].init = true initQueueList[item].silent = true
this.addToQueue(initQueueList[item]) this.addToQueue(initQueueList[item])
}) })
if (restored){
toast(this.$t('toasts.queueRestored'), 'done', true, 'restoring_queue')
socket.emit('queueRestored')
}
}, },
addToQueue(queueItem, current = false) { addToQueue(queueItem, current = false) {
if (Array.isArray(queueItem)){
if (queueItem.length > 1){
queueItem.forEach((item, i) => {
item.silent = true
this.addToQueue(item)
});
toast(this.$t('toasts.addedMoreToQueue', [queueItem.length]), 'playlist_add_check')
return
}else{
queueItem = queueItem[0]
}
}
this.queueList[queueItem.uuid] = queueItem this.queueList[queueItem.uuid] = queueItem
if (queueItem.downloaded + queueItem.failed == queueItem.size) { if (queueItem.downloaded + queueItem.failed == queueItem.size) {
@ -188,13 +204,13 @@ export default {
} }
} }
if (!queueItem.init) { if (!queueItem.silent) {
toast(this.$t('toasts.addedToQueue', [queueItem.title]), 'playlist_add_check') toast(this.$t('toasts.addedToQueue', [queueItem.title]), 'playlist_add_check')
} }
}, },
updateQueue(update) { updateQueue(update) {
// downloaded and failed default to false? // downloaded and failed default to false?
const { uuid, downloaded, failed, progress, error, data, errid } = update const { uuid, downloaded, failed, progress, conversion, error, data, errid } = update
if (uuid && this.queue.indexOf(uuid) > -1) { if (uuid && this.queue.indexOf(uuid) > -1) {
if (downloaded) { if (downloaded) {
@ -224,6 +240,10 @@ export default {
this.queueList[uuid].progress = progress this.queueList[uuid].progress = progress
$('#bar_' + uuid).css('width', progress + '%') $('#bar_' + uuid).css('width', progress + '%')
} }
if (conversion) {
$('#bar_' + uuid).css('width', (100-conversion) + '%')
}
} }
}, },
removeFromQueue(uuid) { removeFromQueue(uuid) {
@ -339,9 +359,17 @@ export default {
}, },
startDownload(uuid) { startDownload(uuid) {
$('#bar_' + uuid) $('#bar_' + uuid)
.removeClass('converting')
.removeClass('indeterminate') .removeClass('indeterminate')
.addClass('determinate') .addClass('determinate')
}, },
startConversion(uuid) {
$('#bar_' + uuid)
.addClass('converting')
.removeClass('indeterminate')
.addClass('determinate')
.css('width', '100%')
},
showErrorsTab(clickEvent) { showErrorsTab(clickEvent) {
this.$root.$emit('showTabErrors', clickEvent.data.item, clickEvent.target) this.$root.$emit('showTabErrors', clickEvent.data.item, clickEvent.target)
} }

View File

@ -194,6 +194,7 @@
<select v-model="settings.overwriteFile"> <select v-model="settings.overwriteFile">
<option value="y">{{ $t('settings.downloads.overwriteFile.y') }}</option> <option value="y">{{ $t('settings.downloads.overwriteFile.y') }}</option>
<option value="n">{{ $t('settings.downloads.overwriteFile.n') }}</option> <option value="n">{{ $t('settings.downloads.overwriteFile.n') }}</option>
<option value="e">{{ $t('settings.downloads.overwriteFile.e') }}</option>
<option value="b">{{ $t('settings.downloads.overwriteFile.b') }}</option> <option value="b">{{ $t('settings.downloads.overwriteFile.b') }}</option>
<option value="t">{{ $t('settings.downloads.overwriteFile.t') }}</option> <option value="t">{{ $t('settings.downloads.overwriteFile.t') }}</option>
</select> </select>
@ -273,12 +274,14 @@
<div class="input_group"> <div class="input_group">
<p class="input_group_text">{{ $t('settings.covers.localArtworkSize') }}</p> <p class="input_group_text">{{ $t('settings.covers.localArtworkSize') }}</p>
<input type="number" min="100" max="1800" step="100" v-model.number="settings.localArtworkSize" /> <input type="number" min="100" max="10000" step="100" v-model.number="settings.localArtworkSize" />
<p v-if="settings.localArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22;"> {{ $t('settings.covers.imageSizeWarning') }}</p>
</div> </div>
<div class="input_group"> <div class="input_group">
<p class="input_group_text">{{ $t('settings.covers.embeddedArtworkSize') }}</p> <p class="input_group_text">{{ $t('settings.covers.embeddedArtworkSize') }}</p>
<input type="number" min="100" max="1800" step="100" v-model.number="settings.embeddedArtworkSize" /> <input type="number" min="100" max="10000" step="100" v-model.number="settings.embeddedArtworkSize" />
<p v-if="settings.embeddedArtworkSize > 1200" class="input_group_text" style="opacity: 0.75; color: #ffcc22;"> {{ $t('settings.covers.imageSizeWarning') }}</p>
</div> </div>
<div class="input_group"> <div class="input_group">
@ -290,6 +293,12 @@
</select> </select>
</div> </div>
<label class="with_checkbox">
<input type="checkbox" v-model="settings.embeddedArtworkPNG" />
<span class="checkbox_text">{{ $t('settings.covers.embeddedArtworkPNG') }}</span>
</label>
<p v-if="settings.embeddedArtworkPNG" style="opacity: 0.75; color: #ffcc22;"> {{ $t('settings.covers.embeddedPNGWarning') }}</p>
<div class="input_group"> <div class="input_group">
<p class="input_group_text">{{ $t('settings.covers.jpegImageQuality') }}</p> <p class="input_group_text">{{ $t('settings.covers.jpegImageQuality') }}</p>
<input type="number" min="1" max="100" v-model.number="settings.jpegImageQuality" /> <input type="number" min="1" max="100" v-model.number="settings.jpegImageQuality" />

View File

@ -9,11 +9,11 @@
<span class="main_tablinks_text">{{ $t('sidebar.search') }}</span> <span class="main_tablinks_text">{{ $t('sidebar.search') }}</span>
</span> </span>
<span id="main_charts_tablink" class="main_tablinks" role="link" aria-label="charts"> <span id="main_charts_tablink" class="main_tablinks" role="link" aria-label="charts">
<i class="material-icons side_icon">bubble_chart</i> <i class="material-icons side_icon">show_chart</i>
<span class="main_tablinks_text">{{ $t('sidebar.charts') }}</span> <span class="main_tablinks_text">{{ $t('sidebar.charts') }}</span>
</span> </span>
<span id="main_favorites_tablink" class="main_tablinks" role="link" aria-label="favorites"> <span id="main_favorites_tablink" class="main_tablinks" role="link" aria-label="favorites">
<i class="material-icons side_icon">album</i> <i class="material-icons side_icon">star</i>
<span class="main_tablinks_text">{{ $t('sidebar.favorites') }}</span> <span class="main_tablinks_text">{{ $t('sidebar.favorites') }}</span>
</span> </span>
<span id="main_analyzer_tablink" class="main_tablinks" role="link" aria-label="link analyzer"> <span id="main_analyzer_tablink" class="main_tablinks" role="link" aria-label="link analyzer">
@ -173,4 +173,3 @@ export default {
} }
} }
</script> </script>

View File

@ -30,7 +30,10 @@ export default {
onTimeUpdate() { onTimeUpdate() {
// Prevents first time entering in this function // Prevents first time entering in this function
if (isNaN(this.$refs.preview.duration)) return if (isNaN(this.$refs.preview.duration)) return
if (this.$refs.preview.currentTime <= this.$refs.preview.duration - 1) return let duration = this.$refs.preview.duration
if (!isFinite(duration)) duration = 30
if (duration - this.$refs.preview.currentTime >= 1) return
if (this.previewStopped) return
$(this.$refs.preview).animate({ volume: 0 }, 800) $(this.$refs.preview).animate({ volume: 0 }, 800)

12
src/jsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"checkJs": true,
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"@js/*": ["./js/*"],
"@components/*": ["./components/*"]
}
},
"exclude": ["assets/**/*", "styles/**/*"]
}

346
src/lang/ar.js Normal file
View File

@ -0,0 +1,346 @@
const ar = {
globals: {
welcome: 'مرحبأ بك في ديمكس',
back: 'رجوع',
loading: 'جار التحميل',
download: 'تحميل {0}',
by: 'بواسطة {0}',
in: 'في {0}',
download_hint: 'تحميل',
play_hint: 'تشغيل',
toggle_download_tab_hint: 'عرض/اخفاء',
clean_queue_hint: 'تم المسح',
cancel_queue_hint: 'الغاء الكل',
listTabs: {
empty: '',
all: 'الكل',
top_result: 'افضل النتائج',
album: 'البوم | البومات',
artist: 'فنان | فنانون',
single: 'اغنية | اغنية',
title: 'عنوان | عناوين',
track: 'مقطع | مقاطع',
trackN: '0 مقاطع | {n} مقطع | {n} مقطع',
releaseN: '0 اصدار | {n} اصدار | {n} اصدار',
playlist: 'قائمة تشغيل | قوائم تشغيل',
compile: 'مجموعة | مجموعات',
ep: 'ep | eps',
spotifyPlaylist: 'قائمة تشغيل سبوتفاي | قوائم تشغيل سبوتفاي',
releaseDate: 'تاريخ الاصدار',
error: 'خطأ'
}
},
about: {
titles: {
usefulLinks: 'روابط مهمة',
bugReports: 'ابلاغ عن مشكلة',
contributing: 'مساهمة',
donations: 'التبرع',
license: 'الرخصة'
},
subtitles: {
bugReports: "هل هناك شيء لا يعمل في ديمكس؟ أخبرنا",
contributing: 'تريد المساهمة في هذا المشروع؟ يمكنك القيام بذلك بعدة طرق',
donations: 'تريد المساهمة مادياً؟ يمكنك التبرع'
},
usesLibrary: 'هذا البرنامج يستخدم مكتبة <strong>ديمكس</strong> والتي يمكنك استخدامها لإنشاء واجهة مستخدم خاصة بك لديمكس.',
thanks: `شكرا لـ <strong>rtonno</strong>, و <strong>uhwot</strong> و <strong>lollilol</strong> لمساعدتي في هذا المشروع و لـ <strong>BasCurtiz</strong> و <strong>scarvimane</strong> لصنع الايقونة.`,
upToDate: `تابع اخر التحديثات في قناة الاخبار على تلكرام <a href="https://t.me/RemixDevNews" target="_blank"></a>.`,
officialWebsite: 'الموقع الرسمي',
officialRepo: 'مستودع المكتبة الرسمي',
officialWebuiRepo: 'مستودع واجهة الويب الرسمي',
officialSubreddit: 'السب ريدت الرسمي',
newsChannel: 'قناة الاخبار',
questions: `إذا كانت لديك أسئلة أو مشاكل في التطبيق ، فابحث عن حل في السب ريدت اولاً <a href="https://www.reddit.com/r/deemix" target="_blank">subreddit</a> بعد ذلك ، إذا لم تعثر على أي شيء ، يمكنك نشرمشكلتك على السب ريدت .`,
beforeReporting: `قبل ان تبلغ عن خطأ، تأكد من أنك تشغل أحدث إصدار من التطبيق وأن ما تريد الإبلاغ عنه هو في الواقع خطأ وليس مشكلة من جهتك.`,
beSure: `تأكد من أن الخطأ يمكن إعادة إنتاجه على أجهزة أخرى و ايضاً <strong>لا</strong> تبلغ علة خطأ تم التبليغ عنه سابقاً.`,
duplicateReports: 'سيتم إغلاق تقارير الأخطاء المكررة ، لذلك ترقب ذلك.',
dontOpenIssues: `<strong>DO NOT</strong> open issues for asking questions, there is a subreddit for that.`,
newUI: `If you're fluent in python you could try to make a new UI for the app using the base library, or fix bugs in the library with a pull request on the <a href="https://codeberg.org/RemixDev/deemix" target="_blank">repo</a>.`,
acceptFeatures: `أقبل اقتراحات الميزات أيضًا ، ولكن لا أشياء معقدة ، فقط اشياء يمكنني تنفيذها مباشرة في التطبيق وليس في المكتبة.`,
otherLanguages: `إذا كنت تجيد لغة برمجة أخرى ، يمكنك محاولة تحويل ديمكس إلى لغات برمجة أخرى!`,
understandingCode: `أنت بحاجة إلى مساعدة في فهم الكود؟ فقط اتصل بـ RemixDev على تيليكرام او ريديت.`,
contributeWebUI: `If you know Vue.js (JavaScript), HTML or CSS you could contribute to the <a href="https://codeberg.org/RemixDev/deemix-webui" target="_blank">WebUI</a>.`,
itsFree: `يجب ان تتذكر ان <strong>هذا هو مشروع مجاني</strong> <strong>و عليك دعم الفنانين المفضلين لك قبل ان تدعم مطورين البرنامج</strong>.`,
notObligated: `لا تشعر بالالتزام بالتبرع ، لكني أقدر ذلك على أي حال!`,
lincensedUnder: `This work is licensed under the
<a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank"
>GNU General Public License 3.0</a
>.`
},
charts: {
title: 'قائمة الجداول',
changeCountry: 'تغيير الدولة',
download: 'تحميل قائمة الجدول'
},
errors: {
title: 'خطأ في {0}',
ids: {
invalidURL: 'الرابط غير صحيح',
unsupportedURL: 'الرابط غير متاح حتى الانً',
ISRCnotOnDeezer: 'رمز المقطع غير متوفر في ديزر',
notYourPrivatePlaylist: "لا يمكنك تحميل قوائم التشغيل الخاصة و المخفية.",
spotifyDisabled: 'لم يتم اعداد ميزات سبوتفاي بالطرقة الصحيحة .',
trackNotOnDeezer: 'المقطع لا يوجد في ديزر!',
albumNotOnDeezer: 'الالبوم لا يوجد في ديزر!',
notOnDeezer: 'المقطع لا يوجد في ديزر!',
notEncoded: 'لم يتم تشفير المقطع حتى الانً!',
notEncodedNoAlternative: 'لم يتم تشفير المقطع حتى الآن ولم يتم العثور على بديل!',
wrongBitrate: 'لم يتم العثور على المقطع في الدقة المطلوبة.',
wrongBitrateNoAlternative: 'لم يتم العثور على المقطع في الدقة المطلوبة و لا توجد دقة بديلة!',
no360RA: 'المقطع غير متوفر في Reality Audio 360.',
notAvailable: "المقطع غير متوفر في سيرفرات ديزر!",
notAvailableNoAlternative: "المقطع غير متوفر في سيرفرات ديزر و لا يوجد بديل حتى الان!"
}
},
favorites: {
title: 'المفضلات',
noPlaylists: 'لا يوجد قوائم تشغيل',
noAlbums: 'لم لا توجد البومات مفضلة',
noArtists: 'لا يوجد فنانين مفضلين',
noTracks: 'لا توجد مقاطع مفضلة'
},
home: {
needTologin: 'يجب عليك التسجيل في حساب ديزر قبل بدء التحميل.',
openSettings: 'فتح الاعدادات',
sections: {
popularPlaylists: 'قوائم تشغيل مشهورة',
popularAlbums: 'اكثر الالبومات سماعأ'
}
},
linkAnalyzer: {
info: 'يمكنك استخدام هذا القسم للعثور على مزيد من المعلومات حول الرابط الذي تحاول تنزيله.',
useful:
" كمثال هذا مفيد إذا كنت تحاول تنزيل بعض المقاطع الغير المتاحة في بلدك وتريد معرفة مكان توفرها .",
linkNotSupported: 'هذا الرابط غير معتمد حتى الآن',
linkNotSupportedYet: 'يبدو أن هذا الرابط غير معتمد حتى الآن ، حاول تحليل رابط آخر.',
table: {
id: 'ID',
isrc: 'ISRC',
upc: 'UPC',
duration: 'المدة الزمنية',
diskNumber: 'رقم القرص',
trackNumber: 'رقم المقطع',
releaseDate: 'تاريخ الاصدار',
bpm: 'BPM',
label: 'Label',
recordType: 'نوع التسجيل',
genres: 'النوع الفني',
tracklist: 'قائمة المقاطع'
}
},
search: {
startSearching: 'ابدأ البحث!',
description:
'يمكنك البحث عن مقطع ، ألبوم كامل ، فنان ، قائمة تشغيل .... كل شيء! يمكنك أيضًا لصق رابط ديزر',
fans: '{0} متابعون',
noResults: 'لا يوجد نتائج',
noResultsTrack: 'لم يتم العثور على مقاطع',
noResultsAlbum: 'لم يتم العثور على البومات',
noResultsArtist: 'لم يتم العثور على فنانين',
noResultsPlaylist: 'لم يتم العثور على قوائم تشغيل'
},
searchbar: 'ابحث عن أي شيء تريده (أو الصق رابط)',
downloads: 'التحميلات',
toasts: {
addedToQueue: '{0} تمت إلأضافة إلى قائمة الانتظار',
alreadyInQueue: '{0} حالياً في قائمة الانتظار!',
finishDownload: '{0} انتهى تحميل.',
allDownloaded: 'اكتملت جميع التنزيلات!',
refreshFavs: 'اكتمل التحديث!',
loggingIn: 'جار تسجيل الدخول...',
loggedIn: 'تم تسجيل الدخول',
alreadyLogged: 'تم تسجيل الدخول بالفعل',
loginFailed: "تعذر تسجيل الدخول",
loggedOut: 'تم تسجيل الخروج',
cancellingCurrentItem: 'جار الغاء العنصر الحالي.',
currentItemCancelled: 'تم الغاء العنصر الحالي.',
startAddingArtist: 'جار اضافة {0} البوم الى قائمة الانتضار',
finishAddingArtist: 'تم اضافة {0} البوم الى قائمة الانتضار',
startConvertingSpotifyPlaylist: 'جار تحويل مقاطع سبوتفاي الى مقاطع ديزر',
finishConvertingSpotifyPlaylist: 'تم تحويل قائمة تشغيل سبوتفاي',
loginNeededToDownload: 'يجب عليك تسجيل الدخول لتحميل المقاطع!'
},
settings: {
title: 'الاعدادات',
languages: 'اللغات',
login: {
title: 'تسجيل الدخول',
loggedIn: 'تم تسجيل الدخول كـ {username}',
arl: {
question: 'كيفية الحصول علة ARL',
update: 'ارفع ال ARL'
},
logout: 'تسجيل الخروج'
},
appearance: {
title: 'المظهر',
slimDownloadTab: 'لوحة التحميل الرفيعة'
},
downloadPath: {
title: 'مسار التحميل'
},
templates: {
title: 'القوالب',
tracknameTemplate: 'قالب اسم المقطع',
albumTracknameTemplate: 'قالب مقطع الالبوم',
playlistTracknameTemplate: 'قالب مقطع قائمة التشغيل'
},
folders: {
title: 'الملفات',
createPlaylistFolder: 'اصنع ملف لقائمة التشغيل',
playlistNameTemplate: 'قالب ملف قائمة التشغيل',
createArtistFolder: 'اصنع ملف للفنان',
artistNameTemplate: 'قالب ملف الفنان',
createAlbumFolder: 'اصنع ملف للالبوم',
albumNameTemplate: 'قالب ملف الالبوم',
createCDFolder: 'اصنع ملف للاقراص',
createStructurePlaylist: 'اصنع هيكل مجلدات لقوائم التشغيل',
createSingleFolder: 'اصنع هيكل مجلدات لالبومات ذات الاغنية الواحدة'
},
trackTitles: {
title: 'عنوان المقطع',
padTracks: 'Pad tracks',
paddingSize: 'Overwrite padding size',
illegalCharacterReplacer: 'محول الكتابات الغير مسموح بها'
},
downloads: {
title: 'التحميلات',
queueConcurrency: 'التنزيلات المتزامنة',
maxBitrate: {
title: 'الدقة المفضلة',
9: 'FLAC 1411kbps',
3: 'MP3 320kbps',
1: 'MP3 128kbps'
},
overwriteFile: {
title: 'هل يمكنني استبدال الملفات?',
y: 'نعم, استبدال الملفات',
n: "لا, لا تبدل الملفات",
t: 'استبدل العلامات فقط',
b: 'لا ، احتفظ بالملفين وأضف رقمًا إلى الملف المكرر'
},
fallbackBitrate: 'تراجع الدقة',
fallbackSearch: 'تراجع البحث',
logErrors: 'إنشاء ملفات سجل للأخطاء',
logSearched: 'إنشاء ملفات سجل للمقاطع التي تم البحث عنها',
createM3U8File: 'انشاء ملف لقوائم التشغيل',
syncedLyrics: 'انشاء ملف لكلمات الاغنية',
playlistFilenameTemplate: 'قالب اسم ملف قائمة التشغيل',
saveDownloadQueue: 'حفظ قائمة انتظار التنزيل عند إغلاق التطبيق'
},
covers: {
title: 'غلاف الالبوم',
saveArtwork: 'احفظ الغلاف',
coverImageTemplate: 'تغطية قالب الاسم',
saveArtworkArtist: 'احفظ صورة الفنان',
artistImageTemplate: 'قالب صورة الفنان',
localArtworkSize: 'حجم الصورة',
embeddedArtworkSize: 'حجم الصورة المدمجة',
localArtworkFormat: {
title: 'بأي صيغة تريد حفظ الصورة?',
jpg: 'jpeg صورة',
png: 'png صورة',
both: 'الاثنين png و jpeg'
},
jpegImageQuality: 'JPEG دقة صورة'
},
tags: {
head: 'العلامات التي يتم حفظها',
title: 'العنوان',
artist: 'الفنان',
album: 'الالبوم',
cover: 'الغلاف',
trackNumber: 'رقم المقطع',
trackTotal: 'مجموع المقاطع',
discNumber: 'رقم القرص',
discTotal: 'مجموع الاقراص',
albumArtist: 'فنان الالبوم',
genre: 'Genre',
year: 'السنة',
date: 'التاريخ',
explicit: 'كلمات اغنية صريحة للكبار',
isrc: 'ISRC',
length: 'طول المقطع',
barcode: 'Album Barcode (UPC)',
bpm: 'BPM',
replayGain: 'Replay Gain',
label: 'Album Label',
lyrics: 'كلمات غير متزامنة',
copyright: 'حقوق النشر',
composer: 'ملحن',
involvedPeople: 'الناس المشتركون'
},
other: {
title: 'غير',
savePlaylistAsCompilation: 'حفظ قوائم التشغيل كمجموعة',
useNullSeparator: 'استخدم فاصل فارغ',
saveID3v1: 'احفظ ملف ID3v1',
multiArtistSeparator: {
title: 'كيف تريد فصل الفنانين?',
nothing: 'احفظ الفنان الرئيسي فقط',
default: 'استخدام المواصفات القياسية',
andFeat: 'استخدام& و feat.',
using: 'استخدام "{0}"'
},
singleAlbumArtist: 'احفظ فقط فنان الألبوم الرئيسي',
albumVariousArtists: 'احتفظ بـ "فنانين متنوعين" في ألبوم الفنانين',
removeAlbumVersion: 'إزالة "إصدار الألبوم" من عنوان المسار',
removeDuplicateArtists: 'إزالة مجموعات الفنانين',
dateFormat: {
title: 'صيغة التاريخ لملفات flac',
year: 'س س س س',
month: 'ش ش',
day: 'ي ي'
},
featuredToTitle: {
title: 'ماذا علي أن أفعل مع الفنانين الغير رئيسيين ?',
0: 'لا شيء',
1: 'حذفه من العنوان',
3: 'حذفه من عنوان الاغنية و الالبوم',
2: 'وضعه في العنوان'
},
titleCasing: 'نوع كتابة العناون',
artistCasing: 'نوع كتابة الفنان',
casing: {
nothing: 'بدون تغيير',
lower: 'احرف صغيرة',
upper: 'احرف كبيرة',
start: 'حرف كبير في بداية كل كلمة',
sentence: 'مثل جملة'
},
previewVolume: 'معاينة الحجم',
executeCommand: {
title: 'الأمر للتنفيذ بعد التنزيل',
description: 'اتركه فارغًا بدون إجراء'
}
},
spotify: {
title: 'مميزات سبوتفاي',
clientID: 'معرف عميل سبوتفاي',
clientSecret: 'الكود السري لعميل سبوتفاي',
username: 'اسم مستخدم سبوتفاي'
},
reset: 'إعادة تعيين إلى الافتراضي',
save: 'حفظ',
toasts: {
init: 'تم تحميل الإعدادات!',
update: 'تم تحديث الاعدادات!',
ARLcopied: 'تم نسخ ARL إلى الحافظة'
}
},
sidebar: {
home: 'الرئيسية',
search: 'بحث',
charts: 'قائمة الجداول',
favorites: 'المفضلة',
linkAnalyzer: 'محلل الروابط',
settings: 'الاعدادات',
about: 'حول'
},
tracklist: {
downloadSelection: 'تحميل الاختيار'
}
}
export default ar

View File

@ -11,6 +11,7 @@ const en = {
toggle_download_tab_hint: 'Expand/Collapse', toggle_download_tab_hint: 'Expand/Collapse',
clean_queue_hint: 'Clear Finished', clean_queue_hint: 'Clear Finished',
cancel_queue_hint: 'Cancel All', cancel_queue_hint: 'Cancel All',
open_downloads_folder: 'Open Downloads Folder',
cut: 'cut', cut: 'cut',
copy: 'copy', copy: 'copy',
copyLink: 'copy link', copyLink: 'copy link',
@ -72,9 +73,7 @@ const en = {
itsFree: `You should remember that <strong>this is a free project</strong> and <strong>you should support the artists you love</strong> before supporting the developers.`, itsFree: `You should remember that <strong>this is a free project</strong> and <strong>you should support the artists you love</strong> before supporting the developers.`,
notObligated: `Don't feel obligated to donate, I appreciate you anyway!`, notObligated: `Don't feel obligated to donate, I appreciate you anyway!`,
lincensedUnder: `This work is licensed under the lincensedUnder: `This work is licensed under the
<a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank" <a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">GNU General Public License 3.0</a>.`
>GNU General Public License 3.0</a
>.`
}, },
charts: { charts: {
title: 'Charts', title: 'Charts',
@ -118,8 +117,7 @@ const en = {
}, },
linkAnalyzer: { linkAnalyzer: {
info: 'You can use this section to find more information about the link you are trying to download.', info: 'You can use this section to find more information about the link you are trying to download.',
useful: useful: "This is useful if you're trying to download some tracks that are not available in your country and want to know where they are available, for instance.",
"This is useful if you're trying to download some tracks that are not available in your country and want to know where they are available, for instance.",
linkNotSupported: 'This link is not yet supported', linkNotSupported: 'This link is not yet supported',
linkNotSupportedYet: 'Seems like this link is not yet supported, try analyzing another one.', linkNotSupportedYet: 'Seems like this link is not yet supported, try analyzing another one.',
table: { table: {
@ -139,8 +137,7 @@ const en = {
}, },
search: { search: {
startSearching: 'Start searching!', startSearching: 'Start searching!',
description: description: 'You can search a track, a whole album, an artist, a playlist.... everything! You can also paste a Deezer link',
'You can search a track, a whole album, an artist, a playlist.... everything! You can also paste a Deezer link',
fans: '{0} fans', fans: '{0} fans',
noResults: 'No results', noResults: 'No results',
noResultsTrack: 'No Tracks found', noResultsTrack: 'No Tracks found',
@ -151,7 +148,10 @@ const en = {
searchbar: 'Search anything you want (or just paste a link)', searchbar: 'Search anything you want (or just paste a link)',
downloads: 'downloads', downloads: 'downloads',
toasts: { toasts: {
restoringQueue: 'Restoring download queue...',
queueRestored: 'Download queue restored!',
addedToQueue: '{0} added to queue', addedToQueue: '{0} added to queue',
addedMoreToQueue: '{0} items added to queue',
alreadyInQueue: '{0} is already in queue!', alreadyInQueue: '{0} is already in queue!',
finishDownload: '{0} finished downloading.', finishDownload: '{0} finished downloading.',
allDownloaded: 'All downloads completed!', allDownloaded: 'All downloads completed!',
@ -226,7 +226,8 @@ const en = {
y: 'Yes, overwrite the file', y: 'Yes, overwrite the file',
n: "No, don't overwrite the file", n: "No, don't overwrite the file",
t: 'Overwrite only the tags', t: 'Overwrite only the tags',
b: 'No, keep both files and add a number to the duplicate' b: 'No, keep both files and add a number to the duplicate',
e: "No, and don't look at the extensions"
}, },
fallbackBitrate: 'Bitrate fallback', fallbackBitrate: 'Bitrate fallback',
fallbackSearch: 'Search fallback', fallbackSearch: 'Search fallback',
@ -251,7 +252,10 @@ const en = {
png: 'A png image', png: 'A png image',
both: 'Both a jpeg and a png' both: 'Both a jpeg and a png'
}, },
jpegImageQuality: 'JPEG image quality' jpegImageQuality: 'JPEG image quality',
embeddedArtworkPNG: 'Save embedded artwork as PNG',
embeddedPNGWarning: 'PNGs are not officialy supported by Deezer and can be buggy',
imageSizeWarning: 'Anything above x1200 is not officialy used by Deezer, you may encounter issues'
}, },
tags: { tags: {
head: 'Which tags to save', head: 'Which tags to save',

View File

@ -11,6 +11,13 @@ const es = {
toggle_download_tab_hint: 'Expandir/Colapsar', toggle_download_tab_hint: 'Expandir/Colapsar',
clean_queue_hint: 'Limpiar terminados', clean_queue_hint: 'Limpiar terminados',
cancel_queue_hint: 'Cancelar todos', cancel_queue_hint: 'Cancelar todos',
open_downloads_folder: 'Abrir carpeta de descargas',
cut: 'cortar',
copy: 'copiar',
copyLink: 'copiar link',
copyImageLink: 'copiar link de la imagen',
copyDeezerLink: 'copiar link de Deezer',
paste: 'pegar',
listTabs: { listTabs: {
empty: '', empty: '',
all: 'todos', all: 'todos',
@ -25,6 +32,8 @@ const es = {
playlist: 'lista de reproducción | listas de reproducción', playlist: 'lista de reproducción | listas de reproducción',
compile: 'compilación | compilaciones', compile: 'compilación | compilaciones',
ep: 'ep | eps', ep: 'ep | eps',
more: 'Más álbumes',
featured: 'Apareció en',
spotifyPlaylist: 'lista de reproducción spotify | listas de reproducción spotify', spotifyPlaylist: 'lista de reproducción spotify | listas de reproducción spotify',
releaseDate: 'fecha de publicación', releaseDate: 'fecha de publicación',
error: 'error' error: 'error'
@ -65,8 +74,7 @@ const es = {
itsFree: `Debes recordar que <strong>este es un proyecto libre</fuerte> y <strong>debes apoyar a los artistas que amas</fuerte> antes de apoyar a los desarrolladores.`, itsFree: `Debes recordar que <strong>este es un proyecto libre</fuerte> y <strong>debes apoyar a los artistas que amas</fuerte> antes de apoyar a los desarrolladores.`,
notObligated: `No te sientas obligado a donar, ¡te aprecio de todas formas!`, notObligated: `No te sientas obligado a donar, ¡te aprecio de todas formas!`,
lincensedUnder: `Esta obra está autorizada bajo una lincensedUnder: `Esta obra está autorizada bajo una
<a rel="licencia" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank" <a rel="licencia" href="https://www.gnu.org/licenses/gpl-3.0.en.html" target="_blank">GNU Licencia Pública General 3.0</a>.`
>GNU Licencia Pública General 3.0</a>.`
}, },
charts: { charts: {
title: 'Tablas', title: 'Tablas',
@ -87,12 +95,10 @@ const es = {
notEncoded: '¡Pista aún no codificada!', notEncoded: '¡Pista aún no codificada!',
notEncodedNoAlternative: '¡Pista aún no codificada y no se ha encontrado ninguna alternativa!', notEncodedNoAlternative: '¡Pista aún no codificada y no se ha encontrado ninguna alternativa!',
wrongBitrate: 'La pista no se encuentra a la velocidad de bitrate deseada.', wrongBitrate: 'La pista no se encuentra a la velocidad de bitrate deseada.',
wrongBitrateNoAlternative: wrongBitrateNoAlternative: '¡Pista no encontrada a la tasa de bits deseada y no se ha encontrado ninguna alternativa!',
'¡Pista no encontrada a la tasa de bits deseada y no se ha encontrado ninguna alternativa!',
no360RA: 'La pista no está disponible en Reality Audio 360.', no360RA: 'La pista no está disponible en Reality Audio 360.',
notAvailable: '¡La pista no está disponible en los servidores de Deezer!', notAvailable: '¡La pista no está disponible en los servidores de Deezer!',
notAvailableNoAlternative: notAvailableNoAlternative: '¡La pista no está disponible en los servidores de Deezer y no se ha encontrado ninguna alternativa!'
'¡La pista no está disponible en los servidores de Deezer y no se ha encontrado ninguna alternativa!'
} }
}, },
favorites: { favorites: {
@ -112,8 +118,7 @@ const es = {
}, },
linkAnalyzer: { linkAnalyzer: {
info: 'Puedes usar esta sección para encontrar más información sobre el enlace que estás tratando de descargar.', info: 'Puedes usar esta sección para encontrar más información sobre el enlace que estás tratando de descargar.',
useful: useful: 'Esto es útil si está tratando de descargar algunas pistas que no están disponibles en su país y quiere saber dónde están disponibles, por ejemplo.',
'Esto es útil si está tratando de descargar algunas pistas que no están disponibles en su país y quiere saber dónde están disponibles, por ejemplo.',
linkNotSupported: 'Este enlace aún no está soportado', linkNotSupported: 'Este enlace aún no está soportado',
linkNotSupportedYet: 'Parece que este enlace aún no está soportado, intenta analizar otro.', linkNotSupportedYet: 'Parece que este enlace aún no está soportado, intenta analizar otro.',
table: { table: {
@ -133,8 +138,7 @@ const es = {
}, },
search: { search: {
startSearching: '¡Comienza a buscar!', startSearching: '¡Comienza a buscar!',
description: description: 'Puedes buscar un tema, un álbum entero, un artista, una lista de reproducción... ¡todo! También puedes pegar un enlace de Deezer',
'Puedes buscar un tema, un álbum entero, un artista, una lista de reproducción... ¡todo! También puedes pegar un enlace de Deezer',
fans: '{0} fanáticos', fans: '{0} fanáticos',
noResults: 'No hay resultados', noResults: 'No hay resultados',
noResultsTrack: 'No se encontraron pistas', noResultsTrack: 'No se encontraron pistas',
@ -145,7 +149,10 @@ const es = {
searchbar: 'Busca lo que quieras (o simplemente pega un enlace)', searchbar: 'Busca lo que quieras (o simplemente pega un enlace)',
downloads: 'descargas', downloads: 'descargas',
toasts: { toasts: {
restoringQueue: 'Restaurando cola de descarga...',
queueRestored: '¡Cola de descarga restaurada!',
addedToQueue: '{0} añadidos a la cola', addedToQueue: '{0} añadidos a la cola',
addedMoreToQueue: '{0} elementos añadidos a la cola',
alreadyInQueue: '¡{0} ya está en la cola!', alreadyInQueue: '¡{0} ya está en la cola!',
finishDownload: '{0} terminado de descargar.', finishDownload: '{0} terminado de descargar.',
allDownloaded: '¡Todas las descargas se han completado!', allDownloaded: '¡Todas las descargas se han completado!',
@ -160,7 +167,8 @@ const es = {
startAddingArtist: 'Añadiendo {0} álbumes a la cola', startAddingArtist: 'Añadiendo {0} álbumes a la cola',
finishAddingArtist: 'Añadido {0} álbumes a la cola', finishAddingArtist: 'Añadido {0} álbumes a la cola',
startConvertingSpotifyPlaylist: 'Convertir las pistas de Spotify en pistas de Deezer', startConvertingSpotifyPlaylist: 'Convertir las pistas de Spotify en pistas de Deezer',
finishConvertingSpotifyPlaylist: 'Lista de reproducción de Spotify convertida' finishConvertingSpotifyPlaylist: 'Lista de reproducción de Spotify convertida',
loginNeededToDownload: '¡Necesitas iniciar sesión para descargar títulos!'
}, },
settings: { settings: {
title: 'Configuración', title: 'Configuración',
@ -170,7 +178,7 @@ const es = {
loggedIn: 'Usted está conectado como {nombre de usuario}', loggedIn: 'Usted está conectado como {nombre de usuario}',
arl: { arl: {
question: '¿Cómo consigo mi propio ARL?', question: '¿Cómo consigo mi propio ARL?',
update: 'Actualizar la ARL' update: 'Actualiza la ARL'
}, },
logout: 'Cerrar sesión' logout: 'Cerrar sesión'
}, },
@ -218,7 +226,8 @@ const es = {
title: '¿Desea que sobreescriba los archivos?', title: '¿Desea que sobreescriba los archivos?',
y: 'Sí, sobrescribir el archivo', y: 'Sí, sobrescribir el archivo',
n: 'No, no sobrescribir el archivo', n: 'No, no sobrescribir el archivo',
t: 'Sobrescribir sólo las etiquetas' t: 'Sobrescribir sólo las etiquetas',
b: 'No, mantener los dos archivos y agrega un número al archivo duplicado'
}, },
fallbackBitrate: 'La solución alternativa de bitrate', fallbackBitrate: 'La solución alternativa de bitrate',
fallbackSearch: 'Búsqueda de la segunda opción', fallbackSearch: 'Búsqueda de la segunda opción',
@ -243,7 +252,8 @@ const es = {
png: 'Una imagen png', png: 'Una imagen png',
both: 'Ambos, jpeg y png' both: 'Ambos, jpeg y png'
}, },
jpegImageQuality: 'Calidad de la imagen JPEG' jpegImageQuality: 'Calidad de la imagen JPEG',
imageSizeWarning: 'Nada por encima de x1200 no es usado oficialmente por Deezer, tú podrías encontrar inconvenientes'
}, },
tags: { tags: {
head: '¿Qué etiquetas guardar?', head: '¿Qué etiquetas guardar?',

View File

@ -11,6 +11,13 @@ const fr = {
toggle_download_tab_hint: 'Développer/Réduire', toggle_download_tab_hint: 'Développer/Réduire',
clean_queue_hint: 'Retirer Les Tâches Terminées', clean_queue_hint: 'Retirer Les Tâches Terminées',
cancel_queue_hint: 'Tout Annuler', cancel_queue_hint: 'Tout Annuler',
open_downloads_folder: 'Ouvrir Le Dossier De Téléchargements',
cut: 'couper',
copy: 'copier',
copyLink: 'copier le lien',
copyImageLink: "copier le lien de l'image",
copyDeezerLink: 'copier le lien deezer',
paste: 'coller',
listTabs: { listTabs: {
empty: '', empty: '',
all: 'tout', all: 'tout',
@ -45,8 +52,7 @@ const fr = {
contributing: 'Vous souhaitez contribuer à ce projet ? Vous pouvez le faire de différentes manières !', contributing: 'Vous souhaitez contribuer à ce projet ? Vous pouvez le faire de différentes manières !',
donations: 'Vous souhaitez contribuer financièrement ? Vous pouvez faire un don !' donations: 'Vous souhaitez contribuer financièrement ? Vous pouvez faire un don !'
}, },
usesLibrary: usesLibrary: 'Cette application utilise la bibliothèque <strong>deemix</strong>, que vous pouvez exploiter afin de créer votre propre interface utilisateur pour deemix.',
'Cette application utilise la bibliothèque <strong>deemix</strong>, que vous pouvez exploiter afin de créer votre propre interface utilisateur pour deemix.',
thanks: "Merci à <strong>rtonno</strong>, <strong>uhwot</strong> et <strong>lollilol</strong> de m'avoir aidé dans ce projet ainsi qu'à <strong>BasCurtiz</strong> et <strong>scarvimane</strong> pour avoir réalisé l'icône.", thanks: "Merci à <strong>rtonno</strong>, <strong>uhwot</strong> et <strong>lollilol</strong> de m'avoir aidé dans ce projet ainsi qu'à <strong>BasCurtiz</strong> et <strong>scarvimane</strong> pour avoir réalisé l'icône.",
upToDate: 'Restez informé des mises à jour en suivant le <a href="https://t.me/RemixDevNews" target="_blank">canal de nouveautés</a> sur Telegram.', upToDate: 'Restez informé des mises à jour en suivant le <a href="https://t.me/RemixDevNews" target="_blank">canal de nouveautés</a> sur Telegram.',
officialWebsite: 'Site Officiel', officialWebsite: 'Site Officiel',
@ -67,8 +73,7 @@ const fr = {
itsFree: "N'oubliez pas que <strong>ceci est un projet gratuit</strong> et que <strong>vous devez soutenir les artistes que vous appréciez</strong> avant de supporter les développeurs.", itsFree: "N'oubliez pas que <strong>ceci est un projet gratuit</strong> et que <strong>vous devez soutenir les artistes que vous appréciez</strong> avant de supporter les développeurs.",
notObligated: "Ne vous sentez pas obligé de faire un don, je vous apprécie quand même !", notObligated: "Ne vous sentez pas obligé de faire un don, je vous apprécie quand même !",
lincensedUnder: `Ce projet est autorisé dans le cadre d'une lincensedUnder: `Ce projet est autorisé dans le cadre d'une
<a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.fr.html" target="_blank"> <a rel="license" href="https://www.gnu.org/licenses/gpl-3.0.fr.html" target="_blank">Licence publique générale GNU 3.0</a>.`
Licence publique générale GNU 3.0</a>.`
}, },
charts: { charts: {
title: 'Hit-Parade', title: 'Hit-Parade',
@ -111,10 +116,8 @@ const fr = {
} }
}, },
linkAnalyzer: { linkAnalyzer: {
info: info: "Vous pouvez utiliser cette section pour obtenir plus d'informations sur le lien que vous essayez de télécharger.",
"Vous pouvez utiliser cette section pour obtenir plus d'informations sur le lien que vous essayez de télécharger.", useful: "C'est utile si vous essayer de télécharger des pistes qui ne sont pas disponibles dans votre pays et que vous souhaitez savoir où elles sont disponibles, par exemple.",
useful:
"C'est utile si vous essayer de télécharger des pistes qui ne sont pas disponibles dans votre pays et que vous souhaitez savoir où elles sont disponibles, par exemple.",
linkNotSupported: "Ce lien n'est pas encore pris en charge", linkNotSupported: "Ce lien n'est pas encore pris en charge",
linkNotSupportedYet: "Il semble que ce lien ne soit pas encore pris en charge, essayez d'en analyser un autre.", linkNotSupportedYet: "Il semble que ce lien ne soit pas encore pris en charge, essayez d'en analyser un autre.",
table: { table: {
@ -134,8 +137,7 @@ const fr = {
}, },
search: { search: {
startSearching: 'Commencer une recherche !', startSearching: 'Commencer une recherche !',
description: description: 'Vous pouvez rechercher une piste, un album entier, un artiste, une playlist... tout ! Vous pouvez également copier-coller un lien Deezer',
'Vous pouvez rechercher une piste, un album entier, un artiste, une playlist... tout ! Vous pouvez également copier-coller un lien Deezer',
fans: '{0} fans', fans: '{0} fans',
noResults: 'Aucun résultat', noResults: 'Aucun résultat',
noResultsTrack: "Aucune piste n'a été trouvée", noResultsTrack: "Aucune piste n'a été trouvée",
@ -146,7 +148,10 @@ const fr = {
searchbar: 'Recherchez tout ce que vous voulez (ou copiez-collez simplement un lien)', searchbar: 'Recherchez tout ce que vous voulez (ou copiez-collez simplement un lien)',
downloads: 'téléchargements', downloads: 'téléchargements',
toasts: { toasts: {
restoringQueue: "Restauration de la file d'attente de téléchargement...",
queueRestored: "La file d'attente de téléchargement a été restaurée !",
addedToQueue: "{0} ajouté à la file d'attente", addedToQueue: "{0} ajouté à la file d'attente",
addedMoreToQueue: "{0} éléments ajoutés à la file d'attente",
alreadyInQueue: "{0} est déjà en file d'attente !", alreadyInQueue: "{0} est déjà en file d'attente !",
finishDownload: '{0} a été téléchargé.', finishDownload: '{0} a été téléchargé.',
allDownloaded: 'Tous les téléchargements sont terminés !', allDownloaded: 'Tous les téléchargements sont terminés !',
@ -245,7 +250,8 @@ const fr = {
png: 'Une image png', png: 'Une image png',
both: 'À la fois jpeg et png' both: 'À la fois jpeg et png'
}, },
jpegImageQuality: "Qualité d'image JPEG" jpegImageQuality: "Qualité de l'image JPEG",
imageSizeWarning: "Toute valeur supérieure à x1200 n'est pas officiellement utilisée par Deezer, vous pourriez donc rencontrer des problèmes"
}, },
tags: { tags: {
head: 'Métadonnées à sauvegarder', head: 'Métadonnées à sauvegarder',

View File

@ -11,6 +11,7 @@ const it = {
toggle_download_tab_hint: 'Espandi/Riduci', toggle_download_tab_hint: 'Espandi/Riduci',
clean_queue_hint: 'Pulisci Lista', clean_queue_hint: 'Pulisci Lista',
cancel_queue_hint: 'Cancella tutti i download', cancel_queue_hint: 'Cancella tutti i download',
open_downloads_folder: 'Apri la cartella di download',
cut: 'taglia', cut: 'taglia',
copy: 'copia', copy: 'copia',
copyLink: 'copia link', copyLink: 'copia link',
@ -153,7 +154,10 @@ const it = {
searchbar: 'Cerca qualsiasi cosa (o incolla semplicemente un link)', searchbar: 'Cerca qualsiasi cosa (o incolla semplicemente un link)',
downloads: 'download', downloads: 'download',
toasts: { toasts: {
restoringQueue: 'Ripristinando la coda di download...',
queueRestored: 'Coda di download ripristinata!',
addedToQueue: '{0} aggiunto alla coda', addedToQueue: '{0} aggiunto alla coda',
addedMoreToQueue: '{0} oggetti aggiunti alla coda',
alreadyInQueue: '{0} è già nella coda!', alreadyInQueue: '{0} è già nella coda!',
finishDownload: '{0} ha finito di scaricarsi.', finishDownload: '{0} ha finito di scaricarsi.',
allDownloaded: 'Tutti i download completati!', allDownloaded: 'Tutti i download completati!',
@ -228,7 +232,8 @@ const it = {
y: 'Si, sovrascrivi i file', y: 'Si, sovrascrivi i file',
n: 'No, non sovrascrivere i file', n: 'No, non sovrascrivere i file',
t: 'Sovrascrivi solo i tag', t: 'Sovrascrivi solo i tag',
b: 'No, mantieni entrambi i file e aggiungi un numero al duplicato' b: 'No, mantieni entrambi i file e aggiungi un numero al duplicato',
e: "No, e non tener conto della estensione del file"
}, },
fallbackBitrate: 'Utilizza bitrate più bassi se il bitrate preferito non è disponibile', fallbackBitrate: 'Utilizza bitrate più bassi se il bitrate preferito non è disponibile',
fallbackSearch: 'Cerca il brano se il link originale non è disponibile', fallbackSearch: 'Cerca il brano se il link originale non è disponibile',
@ -253,7 +258,10 @@ const it = {
png: 'In png', png: 'In png',
both: 'Sia in jpeg che in png' both: 'Sia in jpeg che in png'
}, },
jpegImageQuality: 'Qualità immagine JPEG' jpegImageQuality: 'Qualità immagine JPEG',
embeddedArtworkPNG: 'Salva copertina incorporata come PNG',
embeddedPNGWarning: 'Le immagini PNG non sono usate ufficialmente da Deezer e potrebbero dare problemi',
imageSizeWarning: 'Dimensioni maggiori di x1200 non sono usate ufficialmente da Deezer, potresti incontrare problemi'
}, },
tags: { tags: {
head: 'Quali tag salvare', head: 'Quali tag salvare',

View File

@ -14,6 +14,7 @@ import ru from '@/lang/ru'
import tr from '@/lang/tr' import tr from '@/lang/tr'
import vn from '@/lang/vn' import vn from '@/lang/vn'
import hr from '@/lang/hr' import hr from '@/lang/hr'
import ar from '@/lang/ar'
Vue.use(VueI18n) Vue.use(VueI18n)
@ -33,7 +34,8 @@ const locales = {
ru, ru,
tr, tr,
vn, vn,
hr hr,
ar
} }
const i18n = new VueI18n({ const i18n = new VueI18n({

View File

@ -18,6 +18,12 @@
transition: width 0.3s linear; transition: width 0.3s linear;
} }
.converting {
background-color: var(--secondary-color);
-webkit-transition: none !important;
transition: none !important;
}
.indeterminate { .indeterminate {
background-color: var(--accent-color); background-color: var(--accent-color);

View File

@ -15,6 +15,7 @@ html[data-theme='light'] {
--foreground: hsl(0, 0%, 20%); --foreground: hsl(0, 0%, 20%);
--foreground-inverted: hsl(0, 0%, 93%); --foreground-inverted: hsl(0, 0%, 93%);
--accent-color: hsl(210, 100%, 52%); --accent-color: hsl(210, 100%, 52%);
--secondary-color: hsl(46, 100%, 57%);
--panels-background: hsl(210, 3%, 14%); --panels-background: hsl(210, 3%, 14%);
--panels-text: hsl(0, 0%, 100%); --panels-text: hsl(0, 0%, 100%);
--accent-text: hsl(0, 0%, 0%); --accent-text: hsl(0, 0%, 0%);
@ -35,6 +36,7 @@ html[data-theme='dark'] {
--foreground: hsl(0, 0%, 93%); --foreground: hsl(0, 0%, 93%);
--foreground-inverted: hsl(0, 0%, 20%); --foreground-inverted: hsl(0, 0%, 20%);
--accent-color: hsl(210, 100%, 52%); --accent-color: hsl(210, 100%, 52%);
--secondary-color: hsl(46, 100%, 57%);
--panels-background: hsl(0, 0%, 10%); --panels-background: hsl(0, 0%, 10%);
--panels-text: hsl(0, 0%, 100%); --panels-text: hsl(0, 0%, 100%);
--accent-text: hsl(0, 0%, 87%); --accent-text: hsl(0, 0%, 87%);
@ -55,6 +57,7 @@ html[data-theme='purple'] {
--foreground: hsl(0, 0%, 93%); --foreground: hsl(0, 0%, 93%);
--foreground-inverted: hsl(258, 62%, 8%); --foreground-inverted: hsl(258, 62%, 8%);
--accent-color: hsl(261, 85%, 37%); --accent-color: hsl(261, 85%, 37%);
--secondary-color: hsl(46, 100%, 57%);
--panels-background: hsl(257, 70%, 9%); --panels-background: hsl(257, 70%, 9%);
--panels-text: hsl(0, 0%, 100%); --panels-text: hsl(0, 0%, 100%);
--accent-text: hsl(0, 0%, 87%); --accent-text: hsl(0, 0%, 87%);

View File

@ -10,6 +10,7 @@ import ru from 'svg-country-flags/svg/ru.svg'
import tr from 'svg-country-flags/svg/tr.svg' import tr from 'svg-country-flags/svg/tr.svg'
import vn from 'svg-country-flags/svg/vn.svg' import vn from 'svg-country-flags/svg/vn.svg'
import hr from 'svg-country-flags/svg/hr.svg' import hr from 'svg-country-flags/svg/hr.svg'
import ar from '@/assets/ar.svg'
export default { export default {
it, it,
@ -23,5 +24,6 @@ export default {
ru, ru,
tr, tr,
vn, vn,
hr hr,
ar
} }

View File

@ -73,6 +73,24 @@ export function debounce(func, wait, immediate) {
} }
} }
/**
* Workaround to copy to the clipboard cross-OS by generating a
* ghost input and copying the passed String
*
* @param {string} text Text to copy
*/
export function copyToClipboard(text) {
const ghostInput = document.createElement('input')
document.body.appendChild(ghostInput)
ghostInput.setAttribute('type', 'text')
ghostInput.setAttribute('value', text)
ghostInput.select()
ghostInput.setSelectionRange(0, 99999)
document.execCommand('copy')
ghostInput.remove()
}
export const COUNTRIES = { export const COUNTRIES = {
AF: 'Afghanistan', AF: 'Afghanistan',
AX: '\u00c5land Islands', AX: '\u00c5land Islands',