Merge branch 'development' into piped-support

This commit is contained in:
ChunkyProgrammer 2023-11-26 13:32:09 -05:00
commit d4b2ac9329
106 changed files with 2994 additions and 1151 deletions

View File

@ -64,7 +64,7 @@ jobs:
- name: Set Version Number Variable
id: versionNumber
uses: actions/github-script@v6
uses: actions/github-script@v7
env:
IS_DEV: ${{ contains(github.ref, 'development') }}
IS_RC: ${{ contains(github.ref, 'RC') }}

View File

@ -11,7 +11,7 @@ jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: github/issue-labeler@v3.2
- uses: github/issue-labeler@v3.3
with:
configuration-path: .github/issue-labeler.yml
enable-versioned-regex: 0

View File

@ -58,11 +58,11 @@
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/vue-fontawesome": "^2.0.10",
"@seald-io/nedb": "^4.0.2",
"@silvermine/videojs-quality-selector": "^1.3.0",
"@silvermine/videojs-quality-selector": "^1.3.1",
"autolinker": "^4.0.0",
"electron-context-menu": "^3.6.1",
"lodash.debounce": "^4.0.8",
"marked": "^9.1.5",
"marked": "^10.0.0",
"path-browserify": "^1.0.1",
"process": "^0.11.10",
"video.js": "7.21.5",
@ -80,23 +80,23 @@
"youtubei.js": "^7.0.0"
},
"devDependencies": {
"@babel/core": "^7.23.2",
"@babel/eslint-parser": "^7.22.15",
"@babel/core": "^7.23.3",
"@babel/eslint-parser": "^7.23.3",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.23.2",
"@babel/preset-env": "^7.23.3",
"@double-great/stylelint-a11y": "^2.0.2",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1",
"electron": "^27.0.3",
"electron": "^27.1.0",
"electron-builder": "^24.6.4",
"eslint": "^8.53.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsonc": "^2.10.0",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-n": "^16.3.1",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-unicorn": "^49.0.0",

View File

@ -70,11 +70,6 @@ const SyncEvents = {
}
}
// https://v2.vuejs.org/v2/api/#provide-inject
const Injectables = {
SHOW_OUTLINES: 'showOutlines'
}
// Utils
const MAIN_PROFILE_ID = 'allChannels'
@ -82,6 +77,5 @@ export {
IpcChannels,
DBActions,
SyncEvents,
Injectables,
MAIN_PROFILE_ID
}

View File

@ -1330,6 +1330,13 @@ function runApp() {
},
type: 'normal'
},
{
label: 'Profile Manager',
click: (_menuItem, browserWindow, _event) => {
navigateTo('/settings/profile/', browserWindow)
},
type: 'normal'
},
].filter((v) => v !== false),
},
{

View File

@ -10,7 +10,7 @@ import FtButton from './components/ft-button/ft-button.vue'
import FtToast from './components/ft-toast/ft-toast.vue'
import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
import { marked } from 'marked'
import { Injectables, IpcChannels } from '../constants'
import { IpcChannels } from '../constants'
import packageDetails from '../../package.json'
import { openExternalLink, openInternalPath, showToast } from './helpers/utils'
@ -30,15 +30,9 @@ export default defineComponent({
FtToast,
FtProgressBar
},
provide: function () {
return {
[Injectables.SHOW_OUTLINES]: this.showOutlines
}
},
data: function () {
return {
dataReady: false,
hideOutlines: true,
showUpdatesBanner: false,
showBlogBanner: false,
showReleaseNotes: false,
@ -59,6 +53,9 @@ export default defineComponent({
showProgressBar: function () {
return this.$store.getters.getShowProgressBar
},
outlinesHidden: function () {
return this.$store.getters.getOutlinesHidden
},
isLocaleRightToLeft: function () {
return this.locale === 'ar' || this.locale === 'fa' || this.locale === 'he' ||
this.locale === 'ur' || this.locale === 'yi' || this.locale === 'ku'
@ -114,6 +111,10 @@ export default defineComponent({
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
},
landingPage: function() {
return '/' + this.$store.getters.getLandingPage
},
externalLinkOpeningPromptNames: function () {
return [
this.$t('Yes'),
@ -139,7 +140,7 @@ export default defineComponent({
$route () {
// react to route changes...
// Hide top nav filter panel on page change
this.$refs.topNav.hideFilters()
this.$refs.topNav?.hideFilters()
}
},
created () {
@ -189,7 +190,13 @@ export default defineComponent({
})
this.$router.afterEach((to, from) => {
this.$refs.topNav.navigateHistory()
this.$refs.topNav?.navigateHistory()
})
this.$router.onReady(() => {
if (this.$router.currentRoute.path === '/' && this.landingPage !== '/subscriptions') {
this.$router.replace({ path: this.landingPage })
}
})
})
},
@ -302,7 +309,7 @@ export default defineComponent({
activateKeyboardShortcuts: function () {
document.addEventListener('keydown', this.handleKeyboardShortcuts)
document.addEventListener('mousedown', () => {
this.hideOutlines = true
this.hideOutlines()
})
},
@ -327,7 +334,7 @@ export default defineComponent({
}
switch (event.key) {
case 'Tab':
this.hideOutlines = false
this.showOutlines()
break
case 'L':
case 'l':
@ -528,15 +535,6 @@ export default defineComponent({
}
},
/**
* provided to all child components, see `provide` near the top of this file
* after injecting it, they can show outlines during keyboard navigation
* e.g. cycling through tabs with the arrow keys
*/
showOutlines: function () {
this.hideOutlines = false
},
...mapMutations([
'setInvidiousInstancesList'
]),
@ -555,7 +553,9 @@ export default defineComponent({
'setupListenersToSyncWindows',
'updateBaseTheme',
'updateMainColor',
'updateSecColor'
'updateSecColor',
'showOutlines',
'hideOutlines'
])
}
})

View File

@ -3,7 +3,7 @@
v-if="dataReady"
id="app"
:class="{
hideOutlines: hideOutlines,
hideOutlines: outlinesHidden,
isLocaleRightToLeft: isLocaleRightToLeft
}"
>

View File

@ -163,7 +163,7 @@ export default defineComponent({
const message = this.$t('Settings.Data Settings.Profile object has insufficient data, skipping item')
showToast(message)
} else {
if (profileObject.name === 'All Channels' || profileObject._id === MAIN_PROFILE_ID) {
if (profileObject._id === MAIN_PROFILE_ID) {
this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.concat(profileObject.subscriptions)
this.primaryProfile.subscriptions = this.primaryProfile.subscriptions.filter((sub, index) => {
const profileIndex = this.primaryProfile.subscriptions.findIndex((x) => {

View File

@ -4,6 +4,8 @@ import FtSettingsSection from '../ft-settings-section/ft-settings-section.vue'
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
import FtInputTags from '../../components/ft-input-tags/ft-input-tags.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import { showToast } from '../../helpers/utils'
import { checkYoutubeChannelId, findChannelTagInfo } from '../../helpers/channels'
export default defineComponent({
name: 'PlayerSettings',
@ -13,7 +15,18 @@ export default defineComponent({
'ft-input-tags': FtInputTags,
'ft-flex-box': FtFlexBox,
},
data: function () {
return {
channelHiderDisabled: false,
}
},
computed: {
backendOptions: function () {
return {
preference: this.$store.getters.getBackendPreference,
fallback: this.$store.getters.getBackendFallback
}
},
hideVideoViews: function () {
return this.$store.getters.getHideVideoViews
},
@ -92,14 +105,20 @@ export default defineComponent({
hideSubscriptionsLive: function () {
return this.$store.getters.getHideSubscriptionsLive
},
hideSubscriptionsCommunity: function() {
hideSubscriptionsCommunity: function () {
return this.$store.getters.getHideSubscriptionsCommunity
},
showDistractionFreeTitles: function () {
return this.$store.getters.getShowDistractionFreeTitles
},
channelsHidden: function () {
return JSON.parse(this.$store.getters.getChannelsHidden)
return JSON.parse(this.$store.getters.getChannelsHidden).map((ch) => {
// Legacy support
if (typeof ch === 'string') {
return { name: ch, preferredName: '', icon: '' }
}
return ch
})
},
hideSubscriptionsLiveTooltip: function () {
return this.$t('Tooltips.Distraction Free Settings.Hide Subscriptions Live', {
@ -109,6 +128,9 @@ export default defineComponent({
})
}
},
mounted: function () {
this.verifyChannelsHidden()
},
methods: {
handleHideRecommendedVideos: function (value) {
if (value) {
@ -117,9 +139,51 @@ export default defineComponent({
this.updateHideRecommendedVideos(value)
},
handleInvalidChannel: function () {
showToast(this.$t('Settings.Distraction Free Settings.Hide Channels Invalid'))
},
handleChannelAPIError: function () {
showToast(this.$t('Settings.Distraction Free Settings.Hide Channels API Error'))
},
handleChannelsHidden: function (value) {
this.updateChannelsHidden(JSON.stringify(value))
},
handleChannelsExists: function () {
showToast(this.$t('Settings.Distraction Free Settings.Hide Channels Already Exists'))
},
validateChannelId: function (text) {
return checkYoutubeChannelId(text)
},
findChannelTagInfo: async function (text) {
return await findChannelTagInfo(text, this.backendOptions)
},
verifyChannelsHidden: async function () {
const channelsHiddenCpy = [...this.channelsHidden]
for (let i = 0; i < channelsHiddenCpy.length; i++) {
const tag = this.channelsHidden[i]
// if channel has been processed and confirmed as non existent, skip
if (tag.invalid) continue
// process if no preferred name and is possibly a YouTube ID
if (tag.preferredName === '' && checkYoutubeChannelId(tag.name)) {
this.channelHiderDisabled = true
const { preferredName, icon, iconHref, invalidId } = await this.findChannelTagInfo(tag.name)
if (invalidId) {
channelsHiddenCpy[i] = { name: tag.name, invalid: invalidId }
} else {
channelsHiddenCpy[i] = { name: tag.name, preferredName, icon, iconHref }
}
// update on every tag in case it closes
this.handleChannelsHidden(channelsHiddenCpy)
}
}
this.channelHiderDisabled = false
},
...mapActions([
'updateHideVideoViews',

View File

@ -230,12 +230,19 @@
<br class="hide-on-mobile">
<ft-flex-box>
<ft-input-tags
:disabled="channelHiderDisabled"
:disabled-msg="$t('Settings.Distraction Free Settings.Hide Channels Disabled Message')"
:label="$t('Settings.Distraction Free Settings.Hide Channels')"
:placeholder="$t('Settings.Distraction Free Settings.Hide Channels Placeholder')"
:tag-name-placeholder="$t('Settings.Distraction Free Settings.Hide Channels Placeholder')"
:show-action-button="true"
:tag-list="channelsHidden"
:tooltip="$t('Tooltips.Distraction Free Settings.Hide Channels')"
:validate-tag-name="validateChannelId"
:find-tag-info="findChannelTagInfo"
@invalid-name="handleInvalidChannel"
@error-find-tag-info="handleChannelAPIError"
@change="handleChannelsHidden"
@already-exists="handleChannelsExists"
/>
</ft-flex-box>
</ft-settings-section>

View File

@ -11,17 +11,12 @@
overflow: hidden;
color: inherit;
text-decoration: none;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out;
}
.bubblePadding:hover {
background-color: var(--side-nav-hover-color);
color: var(--side-nav-hover-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}

View File

@ -11,9 +11,7 @@
display: block;
float: var(--float-left-ltr-rtl-value);
block-size: 10px;
inset-inline-start: 5px;
position: relative;
inset-block-start: 8px;
inline-size: 10px;
}
@ -29,30 +27,45 @@
}
.option-text {
border-radius: 5px;
border-style: solid;
border-width: 2px;
padding-block: 5px;
padding-inline: 25px;
margin-inline-start: 10px;
}
.option {
display: flex;
align-items: center;
padding-block-end: 10px;
border-radius: 5px;
border-style: solid;
border-width: 1px;
padding-block: 5px;
padding-inline-start: 10px;
margin-block-end: 10px;
}
.option > img {
margin-inline-start: 10px;
block-size: 125px;
}
.correct-option {
border-color: #78da71;
border-width: 2px;
}
.correct-option .filled-circle {
background-color: #78da71;
}
.incorrect-option {
background-color: #dd4e4e;
border-color: #dd4e4e;
border-width: 2px;
}
.reveal-answer {
text-align: center;
.reveal-answer {
justify-content: center;
cursor: pointer;
}
.reveal-answer:hover > .option-text, .reveal-answer:focus > .option-text {
.reveal-answer:hover, .reveal-answer:focus {
background-color: var(--side-nav-hover-color)
}

View File

@ -18,5 +18,11 @@ export default defineComponent({
formattedVotes: function () {
return formatNumber(this.data.totalVotes)
},
},
methods: {
// Use smallest as it's resized to 125px anyways and they're usually all larger than that
findSmallestPollImage: function (images) {
return images.reduce((prev, img) => (img.height < prev.height) ? img : prev, images[0]).url
}
}
})

View File

@ -13,13 +13,13 @@
<div
v-if="data.type === 'quiz'"
class="option quiz-option"
:class="revealAnswer && choice.isCorrect ? 'correct-option' : ''"
>
<span class="empty-circle">
<span :class="revealAnswer && choice.isCorrect ? 'filled-circle' : ''" />
</span>
<div
class="option-text"
:class="revealAnswer && choice.isCorrect ? 'correct-option' : ''"
>
{{ choice.text }}
</div>
@ -29,6 +29,11 @@
class="option poll-option"
>
<span class="empty-circle" />
<img
v-if="choice.image"
:src="findSmallestPollImage(choice.image)"
alt=""
>
<div class="option-text">
{{ choice.text }}
</div>

View File

@ -14,6 +14,7 @@
}"
tabindex="0"
role="button"
:aria-expanded="dropdownShown"
@click="handleIconClick"
@mousedown="handleIconMouseDown"
@keydown.enter.prevent="handleIconClick"
@ -33,7 +34,6 @@
v-if="dropdownOptions.length > 0"
class="list"
role="listbox"
:aria-expanded="dropdownShown"
>
<li
v-for="(option, index) in dropdownOptions"
@ -71,7 +71,6 @@
v-if="dropdownOptions.length > 0"
class="list"
role="listbox"
:aria-expanded="dropdownShown"
>
<li
v-for="(option, index) in dropdownOptions"

View File

@ -7,8 +7,13 @@
inline-size: 60%;
}
.disabledMsg {
color: rgb(233, 255, 108);
padding-block-end: 10px;
}
.ft-tag-box ul {
overflow: auto;
overflow: visible;
display: block;
padding: 0;
margin: 0;
@ -19,24 +24,36 @@
background-color: var(--card-bg-color);
margin: 5px;
border-radius: 5px;
display:flex;
display: flex;
float: var(--float-left-ltr-rtl-value);
}
.ft-tag-box li>span {
padding-block: 10px;
padding-inline-start: 10px;
padding-inline: 10px;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
hyphens: auto;
user-select: text;
}
.tag-icon {
border-radius: 50%;
block-size: 24px;
vertical-align: middle;
}
.tag-icon-link {
margin: auto;
margin-inline-start: 10px;
}
.removeTagButton {
color: var(--primary-text-color);
opacity: 0.5;
padding: 10px;
padding-inline-start: 0px;
}
.removeTagButton:hover {

View File

@ -1,15 +1,21 @@
import { defineComponent } from 'vue'
import FtInput from '../ft-input/ft-input.vue'
import FtTooltip from '../ft-tooltip/ft-tooltip.vue'
export default defineComponent({
name: 'FtInputTags',
components: {
'ft-input': FtInput,
'ft-tooltip': FtTooltip
},
props: {
placeholder: {
disabled: {
type: Boolean,
default: false
},
disabledMsg: {
type: String,
default: ''
},
tagNamePlaceholder: {
type: String,
required: true
},
@ -28,27 +34,48 @@ export default defineComponent({
tooltip: {
type: String,
default: ''
},
validateTagName: {
type: Function,
default: (_) => true
},
findTagInfo: {
type: Function,
default: (_) => ({ preferredName: '', icon: '' }),
}
},
methods: {
updateTags: function (text, e) {
// text entered add tag and update tag list
const trimmedText = text.trim()
if (!this.tagList.includes(trimmedText)) {
const newList = this.tagList.slice(0)
newList.push(trimmedText)
this.$emit('change', newList)
updateTags: async function (text, _e) {
// get text without spaces after last '/' in url, if any
const name = text.split('/').pop().trim()
if (!this.validateTagName(name)) {
this.$emit('invalid-name')
return
}
if (!this.tagList.some((tag) => tag.name === name)) {
// tag info searching allow api calls to be used
const { preferredName, icon, iconHref, err } = await this.findTagInfo(name)
if (err) {
this.$emit('error-find-tag-info')
return
}
const newTag = { name, preferredName, icon, iconHref }
this.$emit('change', [...this.tagList, newTag])
} else {
this.$emit('already-exists')
}
// clear input box
this.$refs.childinput.handleClearTextClick()
this.$refs.tagNameInput.handleClearTextClick()
},
removeTag: function (tag) {
// Remove tag from list
const tagName = tag.trim()
if (this.tagList.includes(tagName)) {
const newList = this.tagList.slice(0)
const index = newList.indexOf(tagName)
newList.splice(index, 1)
if (this.tagList.some((tmpTag) => tmpTag.name === tag.name)) {
const newList = this.tagList.filter((tmpTag) => tmpTag.name !== tag.name)
this.$emit('change', newList)
}
}

View File

@ -2,9 +2,16 @@
<div
class="ft-input-tags-component"
>
<div
v-if="disabled"
class="disabledMsg"
>
{{ disabledMsg }}
</div>
<ft-input
ref="childinput"
:placeholder="placeholder"
ref="tagNameInput"
:disabled="disabled"
:placeholder="tagNamePlaceholder"
:label="label"
:show-label="true"
:tooltip="tooltip"
@ -13,15 +20,26 @@
:force-action-button-icon-name="['fas', 'arrow-right']"
@click="updateTags"
/>
<div class="ft-tag-box">
<ul>
<li
v-for="tag in tagList"
:key="tag.id"
>
<span>{{ tag }}</span>
<router-link
v-if="tag.icon"
:to="tag.iconHref ?? ''"
class="tag-icon-link"
>
<img
:src="tag.icon"
alt=""
class="tag-icon"
>
</router-link>
<span>{{ (tag.preferredName) ? tag.preferredName : tag.name }}</span>
<font-awesome-icon
v-if="!disabled"
:icon="['fas', 'fa-times']"
class="removeTagButton"
tabindex="0"

View File

@ -57,8 +57,6 @@
border-radius: 100%;
color: var(--primary-text-color);
opacity: 0;
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}
@ -74,8 +72,6 @@
.clearInputTextButton.visible:active {
background-color: var(--tertiary-text-color);
color: var(--side-nav-active-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}
@ -172,8 +168,6 @@
.inputAction.enabled:hover {
background-color: var(--side-nav-hover-color);
color: var(--side-nav-hover-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}
@ -184,8 +178,6 @@
.inputAction.enabled:active {
background-color: var(--tertiary-text-color);
color: var(--side-nav-active-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}

View File

@ -53,7 +53,13 @@ export default defineComponent({
// Some component users like channel view will have this disabled
if (!this.useChannelsHiddenPreference) { return [] }
return JSON.parse(this.$store.getters.getChannelsHidden)
return JSON.parse(this.$store.getters.getChannelsHidden).map((ch) => {
// Legacy support
if (typeof ch === 'string') {
return { name: ch, preferredName: '', icon: '' }
}
return ch
})
},
hideUpcomingPremieres: function () {
return this.$store.getters.getHideUpcomingPremieres
@ -87,7 +93,7 @@ export default defineComponent({
// hide upcoming
return false
}
if (this.channelsHidden.includes(data.authorId) || this.channelsHidden.includes(data.author)) {
if (this.channelsHidden.some(ch => ch.name === data.authorId) || this.channelsHidden.some(ch => ch.name === data.author)) {
// hide videos by author
return false
}
@ -101,7 +107,7 @@ export default defineComponent({
data.author,
data.authorId,
]
if (attrsToCheck.some(a => a != null && this.channelsHidden.includes(a))) {
if (attrsToCheck.some(a => a != null && this.channelsHidden.some(ch => ch.name === a))) {
// hide channels by author
return false
}
@ -115,7 +121,7 @@ export default defineComponent({
data.author,
data.authorId,
]
if (attrsToCheck.some(a => a != null && this.channelsHidden.includes(a))) {
if (attrsToCheck.some(a => a != null && this.channelsHidden.some(ch => ch.name === a))) {
// hide playlists by author
return false
}

View File

@ -58,12 +58,18 @@ export default defineComponent({
// Some component users like channel view will have this disabled
if (!this.useChannelsHiddenPreference) { return [] }
return JSON.parse(this.$store.getters.getChannelsHidden)
return JSON.parse(this.$store.getters.getChannelsHidden).map((ch) => {
// Legacy support
if (typeof ch === 'string') {
return { name: ch, preferredName: '', icon: '' }
}
return ch
})
},
shouldBeVisible() {
return !(this.channelsHidden.includes(this.data.authorId) ||
this.channelsHidden.includes(this.data.author))
return !(this.channelsHidden.some(ch => ch.name === this.data.authorId) ||
this.channelsHidden.some(ch => ch.name === this.data.author))
}
},
created() {

View File

@ -234,8 +234,25 @@ export default defineComponent({
{
label: this.$t('Video.Open Channel in Invidious'),
value: 'openInvidiousChannel'
},
{
type: 'divider'
}
)
const hiddenChannels = JSON.parse(this.$store.getters.getChannelsHidden)
const channelShouldBeHidden = hiddenChannels.some(c => c === this.channelId)
if (channelShouldBeHidden) {
options.push({
label: this.$t('Video.Unhide Channel'),
value: 'unhideChannel'
})
} else {
options.push({
label: this.$t('Video.Hide Channel'),
value: 'hideChannel'
})
}
}
}
@ -386,7 +403,7 @@ export default defineComponent({
const videoId = this.id
const data = await deArrowData(this.id)
const cacheData = { videoId, title: null }
if (Array.isArray(data?.titles) && data.titles.length > 0 && (data.titles[0].locked || data.titles[0].votes > 0)) {
if (Array.isArray(data?.titles) && data.titles.length > 0 && (data.titles[0].locked || data.titles[0].votes >= 0)) {
cacheData.title = data.titles[0].title
}
@ -461,6 +478,12 @@ export default defineComponent({
case 'openInvidiousChannel':
openExternalLink(this.invidiousChannelUrl)
break
case 'hideChannel':
this.hideChannel(this.channelName, this.channelId)
break
case 'unhideChannel':
this.unhideChannel(this.channelName, this.channelId)
break
}
},
@ -648,12 +671,28 @@ export default defineComponent({
showToast(this.$t('Video.Video has been removed from your saved list'))
},
hideChannel: function(channelName, channelId) {
const hiddenChannels = JSON.parse(this.$store.getters.getChannelsHidden)
hiddenChannels.push(channelId)
this.updateChannelsHidden(JSON.stringify(hiddenChannels))
showToast(this.$t('Channel Hidden', { channel: channelName }))
},
unhideChannel: function(channelName, channelId) {
const hiddenChannels = JSON.parse(this.$store.getters.getChannelsHidden)
this.updateChannelsHidden(JSON.stringify(hiddenChannels.filter(c => c !== channelId)))
showToast(this.$t('Channel Unhidden', { channel: channelName }))
},
...mapActions([
'openInExternalPlayer',
'updateHistory',
'removeFromHistory',
'addVideo',
'removeVideo'
'removeVideo',
'updateChannelsHidden'
])
}
})

View File

@ -4,17 +4,12 @@
padding-block: 10px 30px;
padding-inline: 10px;
cursor: pointer;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out;
}
.bubblePadding:hover {
background-color: var(--side-nav-hover-color);
color: var(--side-nav-hover-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}

View File

@ -1,5 +1,6 @@
import { defineComponent } from 'vue'
import { sanitizeForHtmlId } from '../../helpers/accessibility'
import { MAIN_PROFILE_ID } from '../../../constants'
export default defineComponent({
name: 'FtProfileBubble',
@ -22,23 +23,17 @@ export default defineComponent({
}
},
computed: {
isMainProfile: function () {
return this.profileId === MAIN_PROFILE_ID
},
sanitizedId: function() {
return 'profileBubble' + sanitizeForHtmlId(this.profileId)
},
profileInitial: function () {
return this?.profileName?.length > 0 ? Array.from(this.profileName)[0].toUpperCase() : ''
}
},
methods: {
goToProfile: function (event) {
if (event instanceof KeyboardEvent) {
if (event.target.getAttribute('role') === 'link' && event.key !== 'Enter') {
return
}
}
this.$router.push({
path: `/settings/profile/edit/${this.profileId}`
})
return this?.profileName?.length > 0 ? Array.from(this.translatedProfileName)[0].toUpperCase() : ''
},
translatedProfileName: function () {
return this.isMainProfile ? this.$t('Profile.All Channels') : this.profileName
}
}
})

View File

@ -2,10 +2,10 @@
<div
class="bubblePadding"
tabindex="0"
role="button"
:aria-labelledby="sanitizedId"
@click="goToProfile"
@keydown.space.prevent="goToProfile($event)"
@keydown.enter.prevent="goToProfile($event)"
@click="$emit('click')"
@keydown.space.enter.prevent="$emit('click')"
>
<div
class="bubble"
@ -19,7 +19,7 @@
:id="sanitizedId"
class="profileName"
>
{{ profileName }}
{{ translatedProfileName }}
</div>
</div>
</template>

View File

@ -1,5 +1,15 @@
h2, .selectedCount {
text-align: center;
}
.card {
inline-size: 85%;
margin-block: 0 15px;
margin-inline: auto;
}
@media only screen and (max-width: 680px) {
.card {
inline-size: 90%;
}
}

View File

@ -86,6 +86,7 @@ export default defineComponent({
})
this.subscriptions = subscriptions
this.selectNone()
}
},
mounted: function () {
@ -114,20 +115,13 @@ export default defineComponent({
handleDeletePromptClick: function (value) {
if (value !== 'no' && value !== null) {
if (this.isMainProfile) {
const channelsToRemove = this.subscriptions.filter((channel) => {
return channel.selected
})
this.subscriptions = this.subscriptions.filter((channel) => {
return !channel.selected
})
const channelsToRemove = this.subscriptions.filter((channel) => channel.selected)
this.subscriptions = this.subscriptions.filter((channel) => !channel.selected)
this.profileList.forEach((x) => {
const profile = deepCopy(x)
profile.subscriptions = profile.subscriptions.filter((channel) => {
const index = channelsToRemove.findIndex((y) => {
return y.id === channel.id
})
const index = channelsToRemove.findIndex((y) => y.id === channel.id)
return index === -1
})
@ -157,9 +151,7 @@ export default defineComponent({
handleChannelClick: function (index) {
this.subscriptions[index].selected = !this.subscriptions[index].selected
this.selectedLength = this.subscriptions.filter((channel) => {
return channel.selected
}).length
this.selectedLength = this.subscriptions.filter((channel) => channel.selected).length
},
selectAll: function () {
@ -174,9 +166,7 @@ export default defineComponent({
return channel
})
this.selectedLength = this.subscriptions.filter((channel) => {
return channel.selected
}).length
this.selectedLength = this.subscriptions.filter((channel) => channel.selected).length
},
selectNone: function () {
@ -191,9 +181,7 @@ export default defineComponent({
return channel
})
this.selectedLength = this.subscriptions.filter((channel) => {
return channel.selected
}).length
this.selectedLength = this.subscriptions.filter((channel) => channel.selected).length
},
...mapActions([

View File

@ -4,7 +4,7 @@
<h2>
{{ $t("Profile.Subscription List") }}
</h2>
<p>
<p class="selectedCount">
{{ selectedText }}
</p>
<ft-flex-box>

View File

@ -1,46 +1,89 @@
.card {
inline-size: 85%;
margin-block: 0 15px;
margin-inline: auto;
h2 {
text-align: center;
}
h3 {
margin-block: 0 10px;
text-align: center;
}
.message {
color: var(--tertiary-text-color);
}
.profileName {
inline-size: 400px;
.profileName, .colorSelection {
margin: auto;
inline-size: 350px;
}
.bottomMargin {
margin-block-end: 30px;
.profileEdit {
flex-direction: column;
column-gap: 10px;
}
.profileEdit > * {
flex: 1;
}
.colorOptions {
max-inline-size: 1000px;
margin-block: 0 30px;
margin-inline: auto;
justify-content: center;
min-inline-size: 250px;
}
.customColorSection {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
margin-block-start: 5px;
}
.colorOption {
inline-size: 100px;
block-size: 100px;
margin: 10px;
inline-size: 40px;
block-size: 40px;
margin: 2px;
cursor: pointer;
border-radius: 50%;
-webkit-border-radius: 50%;
}
.initial {
font-size: 50px;
line-height: 1em;
font-size: 37.5px;
text-align: center;
padding-block: 25px;
padding-inline: 0px;
user-select: none;
-webkit-user-select: none;
}
.colorOption:has(.initial) {
inline-size: 75px;
block-size: 75px;
display: flex;
align-items: center;
justify-content: center;
cursor: default;
}
.profilePreviewSection {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 5px;
}
.secondEditRow {
display: flex;
flex-flow: row wrap;
justify-content: space-evenly;
}
.card {
inline-size: 85%;
margin-block: 0 60px;
margin-inline: auto;
}
@media only screen and (max-width: 680px) {
.card {
inline-size: 90%;

View File

@ -19,13 +19,17 @@ export default defineComponent({
'ft-button': FtButton
},
props: {
profile: {
type: Object,
isMainProfile: {
type: Boolean,
required: true
},
isNew: {
type: Boolean,
required: true
},
profile: {
type: Object,
required: true
}
},
data: function () {
@ -42,14 +46,11 @@ export default defineComponent({
}
},
computed: {
isMainProfile: function () {
return this.profileId === MAIN_PROFILE_ID
},
colorValues: function () {
return colors.map(color => color.value)
},
profileInitial: function () {
return this?.profileName?.length > 0 ? Array.from(this.profileName)[0].toUpperCase() : ''
return this?.profileName?.length > 0 ? Array.from(this.translatedProfileName)[0].toUpperCase() : ''
},
activeProfile: function () {
return this.$store.getters.getActiveProfile
@ -65,6 +66,15 @@ export default defineComponent({
this.$t('Yes'),
this.$t('No')
]
},
editOrCreateProfileLabel: function () {
return this.isNew ? this.$t('Profile.Create Profile') : this.$t('Profile.Edit Profile')
},
editOrCreateProfileNameLabel: function () {
return this.isNew ? this.$t('Profile.Create Profile Name') : this.$t('Profile.Edit Profile Name')
},
translatedProfileName: function () {
return this.isMainProfile ? this.$t('Profile.All Channels') : this.profileName
}
},
watch: {
@ -73,7 +83,7 @@ export default defineComponent({
}
},
created: function () {
this.profileId = this.$route.params.id
this.profileId = this.profile._id
this.profileName = this.profile.name
this.profileBgColor = this.profile.bgColor
this.profileTextColor = this.profile.textColor
@ -110,9 +120,7 @@ export default defineComponent({
if (this.isNew) {
this.createProfile(profile)
showToast(this.$t('Profile.Profile has been created'))
this.$router.push({
path: '/settings/profile/'
})
this.$emit('new-profile-created')
} else {
this.updateProfile(profile)
showToast(this.$t('Profile.Profile has been updated'))
@ -121,7 +129,7 @@ export default defineComponent({
setDefaultProfile: function () {
this.updateDefaultProfile(this.profileId)
const message = this.$t('Profile.Your default profile has been set to {profile}', { profile: this.profileName })
const message = this.$t('Profile.Your default profile has been set to {profile}', { profile: this.translatedProfileName })
showToast(message)
},
@ -132,7 +140,7 @@ export default defineComponent({
this.removeProfile(this.profileId)
const message = this.$t('Profile.Removed {profile} from your profiles', { profile: this.profileName })
const message = this.$t('Profile.Removed {profile} from your profiles', { profile: this.translatedProfileName })
showToast(message)
if (this.defaultProfile === this.profileId) {
@ -140,9 +148,7 @@ export default defineComponent({
showToast(this.$t('Profile.Your default profile has been changed to your primary profile'))
}
this.$router.push({
path: '/settings/profile/'
})
this.$emit('profile-deleted')
},
...mapActions([

View File

@ -1,95 +1,97 @@
<template>
<div>
<ft-card class="card">
<h2>{{ $t("Profile.Edit Profile") }}</h2>
<ft-flex-box>
<ft-input
class="profileName"
placeholder="Profile Name"
:value="profileName"
:show-action-button="false"
@input="e => profileName = e"
/>
</ft-flex-box>
<h3>{{ $t("Profile.Color Picker") }}</h3>
<ft-flex-box
class="bottomMargin colorOptions"
>
<div
v-for="(color, index) in colorValues"
:key="index"
class="colorOption"
:title="color + ' ' + $t('Profile.Custom Color')"
:style="{ background: color }"
tabindex="0"
@click="profileBgColor = color"
@keydown.space.prevent="profileBgColor = color"
@keydown.enter.prevent="profileBgColor = color"
/>
</ft-flex-box>
<ft-flex-box
class="bottomMargin"
>
<h2>{{ editOrCreateProfileLabel }}</h2>
<ft-flex-box class="profileEdit">
<div>
<label for="colorPicker">{{ $t("Profile.Custom Color") }}</label>
<input
id="colorPicker"
v-model="profileBgColor"
type="color"
<h3>{{ $t("Profile.Color Picker") }}</h3>
<ft-flex-box
class="colorOptions"
>
<div
v-for="(color, index) in colorValues"
:key="index"
class="colorOption"
:title="color + ' ' + $t('Profile.Custom Color')"
:style="{ background: color }"
tabindex="0"
@click="profileBgColor = color"
@keydown.space.prevent="profileBgColor = color"
@keydown.enter.prevent="profileBgColor = color"
/>
</ft-flex-box>
<div class="customColorSection">
<label for="colorPicker">{{ $t("Profile.Custom Color") }}</label>
<input
id="colorPicker"
v-model="profileBgColor"
type="color"
>
</div>
<ft-input
class="colorSelection"
placeholder=""
:value="profileBgColor"
:show-action-button="false"
:disabled="true"
/>
</div>
</ft-flex-box>
<ft-flex-box>
<ft-input
class="profileName"
placeholder=""
:value="profileBgColor"
:show-action-button="false"
:disabled="true"
/>
</ft-flex-box>
<h3>{{ $t("Profile.Profile Preview") }}</h3>
<ft-flex-box
class="bottomMargin"
>
<div
class="colorOption"
:style="{ background: profileBgColor, color: profileTextColor }"
style="cursor: default"
>
<div
class="initial"
>
{{ profileInitial }}
<div class="secondEditRow">
<div>
<h3>{{ editOrCreateProfileNameLabel }}</h3>
<ft-input
class="profileName"
:placeholder="$t('Profile.Profile Name')"
:disabled="isMainProfile"
:value="translatedProfileName"
:show-action-button="false"
@input="e => profileName = e"
/>
</div>
<div>
<h3>{{ $t("Profile.Profile Preview") }}</h3>
<div class="profilePreviewSection">
<div
class="colorOption"
:style="{ background: profileBgColor, color: profileTextColor }"
style="cursor: default"
>
<div
class="initial"
>
{{ profileInitial }}
</div>
</div>
<ft-flex-box>
<ft-button
v-if="isNew"
:label="$t('Profile.Create Profile')"
@click="saveProfile"
/>
<template
v-else
>
<ft-button
:label="$t('Profile.Update Profile')"
@click="saveProfile"
/>
<ft-button
:label="$t('Profile.Make Default Profile')"
@click="setDefaultProfile"
/>
<ft-button
v-if="!isMainProfile"
:label="$t('Profile.Delete Profile')"
text-color="var(--text-with-main-color)"
background-color="var(--primary-color)"
@click="openDeletePrompt"
/>
</template>
</ft-flex-box>
</div>
</div>
</div>
</ft-flex-box>
<ft-flex-box>
<ft-button
v-if="isNew"
:label="$t('Profile.Create Profile')"
@click="saveProfile"
/>
<template
v-else
>
<ft-button
:label="$t('Profile.Update Profile')"
@click="saveProfile"
/>
<ft-button
:label="$t('Profile.Make Default Profile')"
@click="setDefaultProfile"
/>
<ft-button
v-if="!isMainProfile"
:label="$t('Profile.Delete Profile')"
text-color="var(--text-with-main-color)"
background-color="var(--primary-color)"
@click="openDeletePrompt"
/>
</template>
</ft-flex-box>
</ft-card>
<ft-prompt
v-if="showDeletePrompt"

View File

@ -1,17 +1,19 @@
.card {
inline-size: 85%;
margin-block: 0 30px;
margin-inline: auto;
h2 {
text-align: center;
}
.selected {
text-align: center;
}
:deep(.select-label) {
inline-size: 95%;
.card {
inline-size: 85%;
margin-block: 0 15px;
margin-inline: auto;
}
:deep(.select) {
text-align-last: center;
@media only screen and (max-width: 680px) {
.card {
inline-size: 90%;
}
}

View File

@ -8,6 +8,7 @@ import FtButton from '../../components/ft-button/ft-button.vue'
import FtSelect from '../ft-select/ft-select.vue'
import { deepCopy, showToast } from '../../helpers/utils'
import { youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
import { MAIN_PROFILE_ID } from '../../../constants'
export default defineComponent({
name: 'FtProfileFilterChannelsList',
@ -46,17 +47,20 @@ export default defineComponent({
return this.$store.getters.getProfileList
},
profileNameList: function () {
return this.profileList.flatMap((profile) => profile.name !== this.profile.name ? [profile.name] : [])
return this.profileList.flatMap((profile) => profile.name !== this.profile.name ? [this.translatedProfileName(profile)] : [])
},
selectedText: function () {
return this.$t('Profile.{number} selected', { number: this.selectedLength })
},
locale: function () {
return this.$i18n.locale.replace('_', '-')
},
}
},
watch: {
profile: 'updateChannelList',
profile: function () {
this.updateChannelList()
this.selectNone()
},
filteredProfileIndex: 'updateChannelList'
},
mounted: function () {
@ -80,7 +84,9 @@ export default defineComponent({
},
methods: {
updateChannelList () {
this.channels = deepCopy(this.profileList[this.filteredProfileIndex].subscriptions).sort((a, b) => {
const filterProfileName = this.profileNameList[this.filteredProfileIndex]
const filterProfile = this.profileList.find((profile) => this.translatedProfileName(profile) === filterProfileName)
this.channels = deepCopy(filterProfile.subscriptions).sort((a, b) => {
return a.name?.toLowerCase().localeCompare(b.name?.toLowerCase(), this.locale)
}).filter((channel) => {
const index = this.profile.subscriptions.findIndex((sub) => {
@ -105,7 +111,8 @@ export default defineComponent({
},
handleProfileFilterChange: function (change) {
this.filteredProfileIndex = this.profileList.findIndex(profile => profile.name === change)
this.selectNone()
this.filteredProfileIndex = this.profileNameList.indexOf(change)
},
addChannelToProfile: function () {
@ -158,6 +165,10 @@ export default defineComponent({
}).length
},
translatedProfileName: function (profile) {
return profile._id === MAIN_PROFILE_ID ? this.$t('Profile.All Channels') : profile.name
},
...mapActions([
'updateProfile'
])

View File

@ -7,7 +7,7 @@
<ft-flex-box>
<ft-select
:placeholder="$t('Profile.Profile Filter')"
:value="profileNameList[0]"
:value="profileNameList[filteredProfileIndex]"
:select-names="profileNameList"
:select-values="profileNameList"
@change="handleProfileFilterChange"

View File

@ -27,7 +27,7 @@
inset-block-start: 60px;
inset-inline-end: 10px;
min-inline-size: 250px;
block-size: 400px;
block-size: auto;
padding: 5px;
background-color: var(--card-bg-color);
box-shadow: 0 0 4px var(--scrollbar-color-hover);
@ -35,24 +35,33 @@
.profileWrapper {
margin-block-start: 60px;
block-size: 340px;
block-size: auto;
overflow-y: auto;
/*
profile list max height: 90% of window size - 100 px. It's scaled to be 340px on 800x600 resolution.
Offset of 100px is to compensate for the fixed size of elements above the list, which takes more screen space on lower resolutions
*/
max-block-size: calc(90vh - 100px);
min-block-size: 340px;
}
/* Navbar changes position to horizontal with this media rule.
Height adjust for profile list so it won't cover navbar. */
@media only screen and (max-width: 680px){
.profileWrapper {
max-block-size: calc(95vh - 180px);
}
}
.profile {
cursor: pointer;
block-size: 50px;
-webkit-transition: background 0.2s ease-out;
-moz-transition: background 0.2s ease-out;
-o-transition: background 0.2s ease-out;
transition: background 0.2s ease-out;
}
.profile:hover {
background-color: var(--side-nav-hover-color);
color: var(--side-nav-hover-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}

View File

@ -4,6 +4,7 @@ import { mapActions } from 'vuex'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtIconButton from '../../components/ft-icon-button/ft-icon-button.vue'
import { showToast } from '../../helpers/utils'
import { MAIN_PROFILE_ID } from '../../../constants'
export default defineComponent({
name: 'FtProfileSelector',
@ -26,15 +27,19 @@ export default defineComponent({
},
activeProfileInitial: function () {
// use Array.from, so that emojis don't get split up into individual character codes
return this.activeProfile?.name?.length > 0 ? Array.from(this.activeProfile.name)[0].toUpperCase() : ''
return this.activeProfile?.name?.length > 0 ? Array.from(this.translatedProfileName(this.activeProfile))[0].toUpperCase() : ''
},
profileInitials: function () {
return this.profileList.map((profile) => {
return profile?.name?.length > 0 ? Array.from(profile.name)[0].toUpperCase() : ''
return profile?.name?.length > 0 ? Array.from(this.translatedProfileName(profile))[0].toUpperCase() : ''
})
}
},
methods: {
isActiveProfile: function (profile) {
return profile._id === this.activeProfile._id
},
toggleProfileList: function () {
this.profileListShown = !this.profileListShown
@ -77,13 +82,17 @@ export default defineComponent({
if (targetProfile) {
this.updateActiveProfile(targetProfile._id)
showToast(this.$t('Profile.{profile} is now the active profile', { profile: profile.name }))
showToast(this.$t('Profile.{profile} is now the active profile', { profile: this.translatedProfileName(profile) }))
}
}
this.profileListShown = false
},
translatedProfileName: function (profile) {
return profile._id === MAIN_PROFILE_ID ? this.$t('Profile.All Channels') : profile.name
},
...mapActions([
'updateActiveProfile'
])

View File

@ -6,6 +6,8 @@
:style="{ background: activeProfile.bgColor, color: activeProfile.textColor }"
tabindex="0"
role="button"
:aria-expanded="profileListShown"
aria-controls="profileSelectorList"
@click="toggleProfileList"
@mousedown="handleIconMouseDown"
@keydown.space.prevent="toggleProfileList"
@ -19,6 +21,7 @@
</div>
<ft-card
v-show="profileListShown"
id="profileSelectorList"
ref="profileList"
class="profileList"
tabindex="-1"
@ -46,7 +49,7 @@
:key="index"
class="profile"
:aria-labelledby="'profile-' + index + '-name'"
aria-selected="false"
:aria-selected="isActiveProfile(profile)"
tabindex="0"
role="option"
@click="setActiveProfile(profile)"
@ -66,7 +69,7 @@
:id="'profile-' + index + '-name'"
class="profileName"
>
{{ profile.name }}
{{ translatedProfileName(profile) }}
</p>
</div>
</div>

View File

@ -1,8 +1,8 @@
import { defineComponent } from 'vue'
import { mapActions } from 'vuex'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtButton from '../../components/ft-button/ft-button.vue'
import { Injectables } from '../../../constants'
import { sanitizeForHtmlId } from '../../helpers/accessibility'
export default defineComponent({
@ -12,9 +12,6 @@ export default defineComponent({
'ft-flex-box': FtFlexBox,
'ft-button': FtButton
},
inject: {
showOutlines: Injectables.SHOW_OUTLINES
},
props: {
label: {
type: String,
@ -101,6 +98,10 @@ export default defineComponent({
const direction = (e.key === 'ArrowLeft') ? -1 : 1
this.focusItem(parseInt(currentIndex) + direction)
}
}
},
...mapActions([
'showOutlines'
])
}
})

View File

@ -7,5 +7,10 @@ export default defineComponent({
type: String,
required: true
}
},
computed: {
allSettingsSectionsExpandedByDefault: function () {
return this.$store.getters.getAllSettingsSectionsExpandedByDefault
}
}
})

View File

@ -27,9 +27,8 @@
}
.sectionLine {
background-color: var(--primary-color);
border: 0;
block-size: 2px;
border-block-start: 2px solid var(--primary-color);
margin-block-start: -1px;
inline-size: 100%;
}
@ -44,6 +43,7 @@
-webkit-user-select: none;
user-select: none;
margin-inline-start: 2%;
margin-block: 0.5em;
}
:deep(.switchGrid) {

View File

@ -1,5 +1,8 @@
<template>
<details class="settingsSection">
<details
:open="allSettingsSectionsExpandedByDefault ? 'true' : null"
class="settingsSection"
>
<summary class="sectionHeader">
<h3 class="sectionTitle">
{{ title }}

View File

@ -1,8 +0,0 @@
/* Ensures style here overrides style of .btn */
.subscribeButton.btn {
align-self: center;
block-size: 50%;
margin-block-end: 10px;
min-inline-size: 150px;
white-space: initial;
}

View File

@ -25,29 +25,58 @@ export default defineComponent({
type: String,
required: true
},
hideProfileDropdownToggle: {
type: Boolean,
default: false
},
openDropdownOnSubscribe: {
type: Boolean,
default: true
},
subscriptionCountText: {
default: '',
type: String,
required: false
}
},
data: function () {
return {
isProfileDropdownOpen: false
}
},
computed: {
profileInitials: function () {
return this.profileDisplayList.map((profile) => {
return profile?.name?.length > 0 ? Array.from(profile.name)[0].toUpperCase() : ''
})
},
profileList: function () {
return this.$store.getters.getProfileList
},
/* sort by 'All Channels' -> active profile -> unsubscribed channels -> subscribed channels */
profileDisplayList: function () {
const mainProfileAndActiveProfile = [this.profileList[0]]
if (this.activeProfile._id !== MAIN_PROFILE_ID) {
mainProfileAndActiveProfile.push(this.activeProfile)
}
return [
...mainProfileAndActiveProfile,
...this.profileList.filter((profile, i) =>
i !== 0 && !this.isActiveProfile(profile) && !this.isProfileSubscribed(profile)),
...this.profileList.filter((profile, i) =>
i !== 0 && !this.isActiveProfile(profile) && this.isProfileSubscribed(profile))
]
},
activeProfile: function () {
return this.$store.getters.getActiveProfile
},
subscriptionInfo: function () {
return this.activeProfile.subscriptions.find((channel) => {
return channel.id === this.channelId
}) ?? null
},
isSubscribed: function () {
return this.subscriptionInfo !== null
return this.subscriptionInfoForProfile(this.activeProfile)
},
hideChannelSubscriptions: function () {
@ -55,23 +84,27 @@ export default defineComponent({
},
subscribedText: function () {
let subscribedValue = (this.isSubscribed ? this.$t('Channel.Unsubscribe') : this.$t('Channel.Subscribe')).toUpperCase()
let subscribedValue = (this.isProfileSubscribed(this.activeProfile) ? this.$t('Channel.Unsubscribe') : this.$t('Channel.Subscribe')).toUpperCase()
if (this.subscriptionCountText !== '' && !this.hideChannelSubscriptions) {
subscribedValue += ' ' + this.subscriptionCountText
}
return subscribedValue
},
isProfileDropdownEnabled: function () {
return !this.hideProfileDropdownToggle && this.profileList.length > 1
}
},
methods: {
handleSubscription: function () {
handleSubscription: function (profile = this.activeProfile) {
if (this.channelId === '') {
return
}
const currentProfile = deepCopy(this.activeProfile)
const currentProfile = deepCopy(profile)
if (this.isSubscribed) {
if (this.isProfileSubscribed(profile)) {
currentProfile.subscriptions = currentProfile.subscriptions.filter((channel) => {
return channel.id !== this.channelId
})
@ -79,16 +112,16 @@ export default defineComponent({
this.updateProfile(currentProfile)
showToast(this.$t('Channel.Channel has been removed from your subscriptions'))
if (this.activeProfile._id === MAIN_PROFILE_ID) {
if (profile._id === MAIN_PROFILE_ID) {
// Check if a subscription exists in a different profile.
// Remove from there as well.
let duplicateSubscriptions = 0
this.profileList.forEach((profile) => {
if (profile._id === MAIN_PROFILE_ID) {
this.profileList.forEach((profileInList) => {
if (profileInList._id === MAIN_PROFILE_ID) {
return
}
duplicateSubscriptions += this.unsubscribe(profile, this.channelId)
duplicateSubscriptions += this.unsubscribe(profileInList, this.channelId)
})
if (duplicateSubscriptions > 0) {
@ -107,7 +140,7 @@ export default defineComponent({
this.updateProfile(currentProfile)
showToast(this.$t('Channel.Added channel to your subscriptions'))
if (this.activeProfile._id !== MAIN_PROFILE_ID) {
if (profile._id !== MAIN_PROFILE_ID) {
const primaryProfile = deepCopy(this.profileList.find(prof => {
return prof._id === MAIN_PROFILE_ID
}))
@ -122,6 +155,34 @@ export default defineComponent({
}
}
}
if (this.isProfileDropdownEnabled && this.openDropdownOnSubscribe && !this.isProfileDropdownOpen) {
this.toggleProfileDropdown()
}
},
handleProfileDropdownFocusOut: function () {
if (!this.$refs.subscribeButton.matches(':focus-within')) {
this.isProfileDropdownOpen = false
}
},
toggleProfileDropdown: function() {
this.isProfileDropdownOpen = !this.isProfileDropdownOpen
},
isActiveProfile: function (profile) {
return profile._id === this.activeProfile._id
},
subscriptionInfoForProfile: function (profile) {
return profile.subscriptions.find((channel) => {
return channel.id === this.channelId
}) ?? null
},
isProfileSubscribed: function (profile) {
return this.subscriptionInfoForProfile(profile) !== null
},
unsubscribe: function(profile, channelId) {

View File

@ -0,0 +1,141 @@
.buttonList {
margin: 5px;
margin-block-end: 10px;
border-radius: 4px;
block-size: fit-content;
box-shadow: 0px 1px 2px rgb(0 0 0 / 50%);
display: flex;
flex-wrap: nowrap;
/* addresses odd clipping behavior when adjusting window size */
background-color: var(--primary-color);
}
.ftSubscribeButton {
position: relative;
text-align: start;
}
/* Ensures style here overrides style of .btn */
.subscribeButton.btn {
min-inline-size: 150px;
white-space: initial;
}
.subscribeButton.btn, .profileDropdownToggle.btn {
align-self: center;
margin-block: 0;
margin-inline: 0;
}
.dropdownOpened {
.subscribeButton, .profileDropdownToggle {
border-end-start-radius: 0;
border-end-end-radius: 0;
}
}
.profileDropdownToggle.btn {
border-inline-start: none !important;
border-start-start-radius: 0;
border-end-start-radius: 0;
display: inline-block;
min-inline-size: 1em;
padding-inline: 10px;
box-sizing: content-box;
}
.hasProfileDropdownToggle {
.subscribeButton.btn {
min-inline-size: 100px;
padding-inline: 5px;
border-inline-end: 2px solid var(--primary-color-active) !important;
border-start-end-radius: 0;
border-end-end-radius: 0;
box-sizing: content-box;
}
}
.hasProfileDropdownToggle > .subscribeButton.btn, .profileDropdownToggle.btn {
padding-block: 5px;
padding-inline: 6px;
box-shadow: none;
flex: auto;
block-size: 2em;
}
.profileDropdown {
background-color: var(--side-nav-color);
box-shadow: 0 1px 2px rgb(0 0 0 / 50%);
color: var(--secondary-text-color);
display: inline;
font-size: 12px;
max-block-size: 200px;
margin-block: -10px 0;
margin-inline: 5px 0;
overflow-y: scroll;
position: absolute;
text-align: center;
-webkit-user-select: none;
user-select: none;
z-index: 3;
// accounts for parent's left and right margins
inline-size: calc(100% - 10px);
.profileList {
list-style-type: none;
margin: 0;
padding-inline: 0;
}
.profile {
cursor: pointer;
display: flex;
gap: 0.5em;
padding-inline-start: 0.5em;
block-size: 50px;
align-items: center;
transition: background 0.2s ease-out;
&:hover {
background-color: var(--side-nav-hover-color);
color: var(--side-nav-hover-text-color);
transition: background 0.2s ease-in;
}
.colorOption {
inline-size: 40px;
block-size: 40px;
cursor: pointer;
align-items: center;
display: flex;
justify-content: center;
flex-shrink: 0;
border-radius: 50%;
-webkit-border-radius: 50%;
}
.initial {
font-size: 20px;
line-height: 1em;
text-align: center;
user-select: none;
-webkit-user-select: none;
}
.profileName {
padding-inline-end: 1em;
text-align: start;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&.subscribed {
background-color: var(--primary-color);
.profileName {
color: var(--text-with-main-color);
}
}
}
}

View File

@ -1,12 +1,83 @@
<template>
<ft-button
:label="subscribedText"
class="subscribeButton"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="handleSubscription"
/>
<div
ref="subscribeButton"
class="ftSubscribeButton"
:class="{ dropdownOpened: isProfileDropdownOpen}"
@focusout="handleProfileDropdownFocusOut"
>
<div
class="buttonList"
:class="{ hasProfileDropdownToggle: isProfileDropdownEnabled}"
>
<ft-button
:label="subscribedText"
:no-border="true"
class="subscribeButton"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
@click="handleSubscription"
/>
<ft-button
v-if="isProfileDropdownEnabled"
:no-border="true"
:title="isProfileDropdownOpen ? $t('Profile.Close Profile Dropdown') : $t('Profile.Open Profile Dropdown')"
class="profileDropdownToggle"
background-color="var(--primary-color)"
text-color="var(--text-with-main-color)"
:aria-expanded="isProfileDropdownOpen"
@click="toggleProfileDropdown"
>
<font-awesome-icon
:icon="isProfileDropdownOpen ? ['fas', 'angle-up'] : ['fas', 'angle-down']"
/>
</ft-button>
</div>
<template v-if="isProfileDropdownOpen">
<div
tabindex="-1"
class="profileDropdown"
>
<ul
class="profileList"
>
<li
v-for="(profile, index) in profileDisplayList"
:id="'subscription-profile-' + index"
:key="index"
class="profile"
:class="{
subscribed: isProfileSubscribed(profile)
}"
:aria-labelledby="'subscription-profile-' + index + '-name'"
:aria-selected="isActiveProfile(profile)"
:aria-checked="isProfileSubscribed(profile)"
tabindex="0"
role="checkbox"
@click.stop.prevent="handleSubscription(profile)"
@keydown.space.stop.prevent="handleSubscription(profile)"
>
<div
class="colorOption"
:style="{ background: profile.bgColor, color: profile.textColor }"
>
<div
class="initial"
>
{{ isProfileSubscribed(profile) ? '✓' : profileInitials[index] }}
</div>
</div>
<p
:id="'subscription-profile-' + index + '-name'"
class="profileName"
>
{{ profile.name }}
</p>
</li>
</ul>
</div>
</template>
</div>
</template>
<script src="./ft-subscribe-button.js" />
<style src="./ft-subscribe-button.css" />
<style lang="scss" src="./ft-subscribe-button.scss" />

View File

@ -32,9 +32,9 @@
}
.text {
background-color: rgba(0, 0, 0, 0.8);
background-color: color-mix(in srgb, var(--primary-text-color) 80%, transparent);
border-radius: 20px;
color: #fff;
color: var(--card-bg-color);
font-size: 1rem;
line-height: 120%;
margin: 0;

View File

@ -24,20 +24,6 @@ export default defineComponent({
'local',
'piped'
],
defaultPageNames: [
'Subscriptions',
'Trending',
'Most Popular',
'Playlists',
'History'
],
defaultPageValues: [
'subscriptions',
'trending',
'mostPopular',
'playlists',
'history'
],
viewTypeValues: [
'grid',
'list'
@ -54,6 +40,15 @@ export default defineComponent({
'',
'openLinkAfterPrompt',
'doNothing'
],
includedDefaultPageNames: [
'subscriptions',
'subscribedChannels',
'trending',
'popular',
'userPlaylists',
'history',
'settings'
]
}
},
@ -82,10 +77,38 @@ export default defineComponent({
checkForBlogPosts: function () {
return this.$store.getters.getCheckForBlogPosts
},
hidePlaylists: function () {
return this.$store.getters.getHidePlaylists
},
hidePopularVideos: function () {
return this.$store.getters.getHidePopularVideos
},
hideTrendingVideos: function () {
return this.$store.getters.getHideTrendingVideos
},
defaultPages: function () {
let includedPageNames = this.includedDefaultPageNames
if (this.hideTrendingVideos) includedPageNames = includedPageNames.filter((pageName) => pageName !== 'trending')
if (this.hidePlaylists) includedPageNames = includedPageNames.filter((pageName) => pageName !== 'userPlaylists')
if (!(!this.hidePopularVideos && (this.backendFallback || this.backendPreference === 'invidious'))) includedPageNames = includedPageNames.filter((pageName) => pageName !== 'popular')
return this.$router.getRoutes().filter((route) => includedPageNames.includes(route.name))
},
defaultPageNames: function () {
return this.defaultPages.map((route) => this.$t(route.meta.title))
},
defaultPageValues: function () {
// avoid Vue parsing issues by excluding '/' from path values
return this.defaultPages.map((route) => route.path.substring(1))
},
backendPreference: function () {
return this.$store.getters.getBackendPreference
},
landingPage: function () {
const landingPage = this.$store.getters.getLandingPage
// invalidate landing page selection & restore to default value if no longer valid
if (!this.defaultPageValues.includes(landingPage)) {
this.updateLandingPage('subscriptions')
}
return this.$store.getters.getLandingPage
},
region: function () {

View File

@ -53,7 +53,6 @@
@change="handleFallbackApiBackend"
/>
<ft-select
v-if="false"
:placeholder="$t('Settings.General Settings.Default Landing Page')"
:value="landingPage"
:select-names="defaultPageNames"

View File

@ -14,16 +14,12 @@
.navOption:hover {
background-color: var(--side-nav-hover-color);
color: var(--side-nav-hover-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}
.navOption:active {
background-color: var(--side-nav-active-color);
color: var(--side-nav-active-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}

View File

@ -53,16 +53,12 @@
.navOption:hover, .navChannel:hover {
background-color: var(--side-nav-hover-color);
color: var(--side-nav-hover-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}
.navOption:active, .navChannel:active {
background-color: var(--side-nav-active-color);
color: var(--side-nav-active-text-color);
-moz-transition: background 0.2s ease-in;
-o-transition: background 0.2s ease-in;
transition: background 0.2s ease-in;
}

View File

@ -13,6 +13,9 @@ export default defineComponent({
hideWatchedSubs: function () {
return this.$store.getters.getHideWatchedSubs
},
onlyShowLatestFromChannel: function () {
return this.$store.getters.getOnlyShowLatestFromChannel
},
useRssFeeds: function () {
return this.$store.getters.getUseRssFeeds
},
@ -24,7 +27,8 @@ export default defineComponent({
...mapActions([
'updateHideWatchedSubs',
'updateUseRssFeeds',
'updateFetchSubscriptionsAutomatically'
'updateFetchSubscriptionsAutomatically',
'updateOnlyShowLatestFromChannel'
])
}
})

View File

@ -26,6 +26,12 @@
:compact="true"
@change="updateHideWatchedSubs"
/>
<ft-toggle-switch
:label="$t('Settings.Subscription Settings.Only Show Latest Video for Each Channel')"
:default-value="onlyShowLatestFromChannel"
:compact="true"
@change="updateOnlyShowLatestFromChannel"
/>
</div>
</div>
</ft-settings-section>

View File

@ -40,6 +40,20 @@ export default defineComponent({
return this.$store.getters.getHideHeaderLogo
},
landingPage: function () {
return this.$store.getters.getLandingPage
},
headerLogoTitle: function () {
return this.$t('Go to page',
{
page: this.$t(this.$router.getRoutes()
.find((route) => route.path === '/' + this.landingPage)
.meta.title
)
})
},
enableSearchSuggestions: function () {
return this.$store.getters.getEnableSearchSuggestions
},

View File

@ -59,10 +59,10 @@
dir="ltr"
role="link"
tabindex="0"
:title="$t('Subscriptions.Subscriptions')"
@click="navigate('subscriptions')"
@keydown.space.prevent="navigate('subscriptions')"
@keydown.enter.prevent="navigate('subscriptions')"
:title="headerLogoTitle"
@click="navigate(landingPage)"
@keydown.space.prevent="navigate(landingPage)"
@keydown.enter.prevent="navigate(landingPage)"
>
<div
class="logoIcon"

View File

@ -24,12 +24,21 @@
.commentsTitle {
padding-block: 1em;
display: inline-block;
}
.commentSort {
float: var(--float-right-ltr-rtl-value);
}
@media only screen and (max-width: 800px) {
.commentSort {
float: none;
inline-size: fit-content;
}
}
.comment {
padding: 15px;
position: relative;

View File

@ -2,6 +2,22 @@
<ft-card
class="card"
>
<h3
v-if="commentData.length > 0 && !isLoading && showComments"
class="commentsTitle"
>
{{ $t("Comments.Comments") }}
<span
class="hideComments"
role="button"
tabindex="0"
@click="showComments = false"
@keydown.space.prevent="showComments = false"
@keydown.enter.prevent="showComments = false"
>
{{ $t("Comments.Hide Comments") }}
</span>
</h3>
<h4
v-if="canPerformInitialCommentLoading"
class="getCommentsTitle"
@ -33,22 +49,6 @@
:select-values="sortValues"
@change="handleSortChange"
/>
<h3
v-if="commentData.length > 0 && !isLoading && showComments"
class="commentsTitle"
>
{{ $t("Comments.Comments") }}
<span
class="hideComments"
role="button"
tabindex="0"
@click="showComments = false"
@keydown.space.prevent="showComments = false"
@keydown.enter.prevent="showComments = false"
>
{{ $t("Comments.Hide Comments") }}
</span>
</h3>
<div
v-if="commentData.length > 0 && showComments"
>

View File

@ -22,6 +22,13 @@
row-gap: 16px;
}
.watchingCount {
font-weight: normal;
margin-inline-start: 5px;
font-size: 15px;
color: var(--tertiary-text-color);
}
.message {
font-size: 18px;
color: var(--tertiary-text-color);

View File

@ -6,6 +6,7 @@ import FtButton from '../ft-button/ft-button.vue'
import autolinker from 'autolinker'
import { getRandomColorClass } from '../../helpers/colors'
import { getLocalVideoInfo, parseLocalTextRuns } from '../../helpers/api/local'
import { formatNumber } from '../../helpers/utils'
export default defineComponent({
name: 'WatchVideoLiveChat',
@ -30,6 +31,7 @@ export default defineComponent({
},
data: function () {
return {
/** @type {import('youtubei.js').YT.LiveChat|null} */
liveChatInstance: null,
isLoading: true,
hasError: false,
@ -52,7 +54,9 @@ export default defineComponent({
amount: '',
colorClass: ''
}
}
},
/** @type {number|null} */
watchingCount: null,
}
},
computed: {
@ -78,6 +82,14 @@ export default defineComponent({
scrollingBehaviour: function () {
return this.$store.getters.getDisableSmoothScrolling ? 'auto' : 'smooth'
},
hideVideoViews: function () {
return this.$store.getters.getHideVideoViews
},
formattedWatchingCount: function () {
return this.watchingCount !== null ? formatNumber(this.watchingCount) : '0'
}
},
beforeDestroy: function () {
@ -185,6 +197,12 @@ export default defineComponent({
}
})
this.liveChatInstance.on('metadata-update', metadata => {
if (!this.hideVideoViews && metadata.views && !isNaN(metadata.views.original_view_count)) {
this.watchingCount = metadata.views.original_view_count
}
})
this.liveChatInstance.once('end', () => {
this.hasEnded = true
this.liveChatInstance = null

View File

@ -42,7 +42,15 @@
v-else
class="relative"
>
<h4>{{ $t("Video.Live Chat") }}</h4>
<h4>
{{ $t("Video.Live Chat") }}
<span
v-if="!hideVideoViews && watchingCount !== null"
class="watchingCount"
>
{{ $tc('Global.Counts.Watching Count', watchingCount, { count: formattedWatchingCount }) }}
</span>
</h4>
<div
v-if="superChatComments.length > 0"
class="superChatComments"

View File

@ -482,9 +482,13 @@ export function parseLocalListPlaylist(playlist, author = undefined) {
channelName = playlist.author.name
channelId = playlist.author.id
}
} else {
} else if (author) {
channelName = author.name
channelId = author.id
} else if (playlist.author?.name) {
// auto-generated album playlists don't have an author
// so in search results, the author text is "Playlist" and doesn't have a link or channel ID
channelName = playlist.author.name
}
return {

View File

@ -0,0 +1,76 @@
import { invidiousGetChannelInfo } from './api/invidious'
import { getLocalChannel } from './api/local'
/**
* @param {string} id
* @param {{
* preference: string,
* fallback: boolean,
* invalid: boolean,
* }} backendOptions
*/
async function findChannelById(id, backendOptions) {
try {
if (!process.env.IS_ELECTRON || backendOptions.preference === 'invidious') {
return await invidiousGetChannelInfo(id)
} else {
return await getLocalChannel(id)
}
} catch (err) {
// don't bother with fallback if channel doesn't exist
if (err.message && err.message === 'This channel does not exist.') {
return { invalid: true }
}
if (process.env.IS_ELECTRON && backendOptions.fallback) {
if (backendOptions.preference === 'invidious') {
return await getLocalChannel(id)
}
if (backendOptions.preference === 'local') {
return await invidiousGetChannelInfo(id)
}
} else {
throw err
}
}
}
/**
* @param {string} id
* @param {{
* preference: string,
* fallback: boolean,
* }} backendOptions
* @returns {Promise<{icon: string, iconHref: string, preferredName: string} | { invalidId: boolean }>}
*/
export async function findChannelTagInfo(id, backendOptions) {
if (!checkYoutubeChannelId(id)) return { invalidId: true }
try {
const channel = await findChannelById(id, backendOptions)
if (!process.env.IS_ELECTRON || backendOptions.preference === 'invidious') {
if (channel.invalid) return { invalidId: true }
return {
preferredName: channel.author,
icon: channel.authorThumbnails[0].url
}
} else {
if (channel.alert) return { invalidId: true }
return {
preferredName: channel.header.author.name,
icon: channel.header.author.thumbnails.pop().url,
iconHref: `/channel/${id}`
}
}
} catch (err) {
console.error(err)
return { preferredName: '', icon: '', iconHref: '', err }
}
}
/**
* Check whether Id provided might be a YouTube Id
* @param {string} id
* @returns {boolean}
*/
export function checkYoutubeChannelId(id) {
return /^UC[\w-]{22}$/.test(id)
}

View File

@ -1,15 +1,15 @@
import store from '../store/index'
async function getVideoHash(videoId) {
const videoIdBuffer = new TextEncoder().encode(videoId)
const hashBuffer = await crypto.subtle.digest('SHA-256', videoIdBuffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashArray = new Uint8Array(hashBuffer)
return hashArray
.map(byte => byte.toString(16).padStart(2, '0'))
.slice(0, 4)
.join('')
return hashArray[0].toString(16).padStart(2, '0') +
hashArray[1].toString(16).padStart(2, '0')
}
export async function sponsorBlockSkipSegments(videoId, categories) {
const videoIdHashPrefix = await getVideoHash(videoId)
const requestUrl = `${store.getters.getSponsorBlockUrl}/api/skipSegments/${videoIdHashPrefix}?categories=${JSON.stringify(categories)}`
@ -22,6 +22,11 @@ export async function sponsorBlockSkipSegments(videoId, categories) {
return []
}
// Sometimes the sponsor block server goes down or returns other errors
if (!response.ok) {
throw new Error(await response.text())
}
const json = await response.json()
return json
.filter((result) => result.videoID === videoId)
@ -33,7 +38,7 @@ export async function sponsorBlockSkipSegments(videoId, categories) {
}
export async function deArrowData(videoId) {
const videoIdHashPrefix = (await getVideoHash(videoId)).substring(0, 4)
const videoIdHashPrefix = await getVideoHash(videoId)
const requestUrl = `${store.getters.getSponsorBlockUrl}/api/branding/${videoIdHashPrefix}`
try {

View File

@ -8,11 +8,6 @@ import { calculatePublishedDate } from './utils'
export function updateVideoListAfterProcessing(videos) {
let videoList = videos
// Filtering and sorting based in preference
videoList.sort((a, b) => {
return b.publishedDate - a.publishedDate
})
if (store.getters.getHideLiveStreams) {
videoList = videoList.filter(item => {
return (!item.liveNow && !item.isUpcoming)
@ -45,6 +40,26 @@ export function updateVideoListAfterProcessing(videos) {
})
}
// ordered last to show first eligible video from channel
// if the first one incidentally failed one of the above checks
if (store.getters.getOnlyShowLatestFromChannel) {
const authors = new Set()
videoList = videoList.filter((video) => {
if (!video.authorId) {
return true
} else if (!authors.has(video.authorId)) {
authors.add(video.authorId)
return true
}
return false
})
}
videoList.sort((a, b) => {
return b.publishedDate - a.publishedDate
})
return videoList
}

View File

@ -9,6 +9,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
// to avoid code conflict and duplicate entries
import {
faAngleDown,
faAngleUp,
faArrowDown,
faArrowLeft,
faArrowRight,
@ -81,6 +82,7 @@ Vue.config.productionTip = process.env.NODE_ENV === 'development'
library.add(
// solid icons
faAngleDown,
faAngleUp,
faArrowDown,
faArrowLeft,
faArrowRight,

View File

@ -3,7 +3,6 @@ import Router from 'vue-router'
import Subscriptions from '../views/Subscriptions/Subscriptions.vue'
import SubscribedChannels from '../views/SubscribedChannels/SubscribedChannels.vue'
import ProfileSettings from '../views/ProfileSettings/ProfileSettings.vue'
import ProfileEdit from '../views/ProfileEdit/ProfileEdit.vue'
import Trending from '../views/Trending/Trending.vue'
import Popular from '../views/Popular/Popular.vue'
import UserPlaylists from '../views/UserPlaylists/UserPlaylists.vue'
@ -22,6 +21,7 @@ const router = new Router({
routes: [
{
path: '/',
name: 'default',
meta: {
title: 'Subscriptions.Subscriptions'
},
@ -29,6 +29,7 @@ const router = new Router({
},
{
path: '/subscriptions',
name: 'subscriptions',
meta: {
title: 'Subscriptions.Subscriptions'
},
@ -36,36 +37,15 @@ const router = new Router({
},
{
path: '/subscribedchannels',
name: 'subscribedChannels',
meta: {
title: 'Channels.Title'
},
component: SubscribedChannels
},
{
path: '/settings/profile',
meta: {
title: 'Profile.Profile Settings'
},
component: ProfileSettings
},
{
path: '/settings/profile/new',
name: 'newProfile',
meta: {
title: 'Profile.Create New Profile'
},
component: ProfileEdit
},
{
path: '/settings/profile/edit/:id',
name: 'editProfile',
meta: {
title: 'Profile.Edit Profile'
},
component: ProfileEdit
},
{
path: '/trending',
name: 'trending',
meta: {
title: 'Trending.Trending'
},
@ -73,6 +53,7 @@ const router = new Router({
},
{
path: '/popular',
name: 'popular',
meta: {
title: 'Most Popular'
},
@ -80,6 +61,7 @@ const router = new Router({
},
{
path: '/userplaylists',
name: 'userPlaylists',
meta: {
title: 'User Playlists.Your Playlists'
},
@ -95,6 +77,7 @@ const router = new Router({
},
{
path: '/settings',
name: 'settings',
meta: {
title: 'Settings.Settings'
},
@ -102,11 +85,20 @@ const router = new Router({
},
{
path: '/about',
name: 'about',
meta: {
title: 'About.About'
},
component: About
},
{
path: '/settings/profile',
name: 'profileSettings',
meta: {
title: 'Profile.Profile Settings'
},
component: ProfileSettings
},
{
path: '/search/:query',
meta: {

View File

@ -162,6 +162,7 @@ const defaultSideEffectsTriggerId = settingId =>
/*****/
const state = {
allSettingsSectionsExpandedByDefault: false,
autoplayPlaylists: true,
autoplayVideos: true,
backendFallback: process.env.IS_ELECTRON,
@ -230,6 +231,7 @@ const state = {
landingPage: 'subscriptions',
listType: 'grid',
maxVideoPlaybackRate: 3,
onlyShowLatestFromChannel: false,
playNextVideo: false,
proxyHostname: '127.0.0.1',
proxyPort: '9050',

View File

@ -19,6 +19,7 @@ import {
const state = {
isSideNavOpen: false,
outlinesHidden: true,
sessionSearchHistory: [],
popularCache: null,
trendingCache: {
@ -51,6 +52,10 @@ const getters = {
return state.isSideNavOpen
},
getOutlinesHidden() {
return state.outlinesHidden
},
getCurrentVolume () {
return state.currentVolume
},
@ -117,6 +122,14 @@ const getters = {
}
const actions = {
showOutlines({ commit }) {
commit('setOutlinesHidden', false)
},
hideOutlines({ commit }) {
commit('setOutlinesHidden', true)
},
async downloadMedia({ rootState }, { url, title, extension, fallingBackPath }) {
if (!process.env.IS_ELECTRON) {
openExternalLink(url)
@ -633,6 +646,10 @@ const mutations = {
state.isSideNavOpen = !state.isSideNavOpen
},
setOutlinesHidden(state, value) {
state.outlinesHidden = value
},
setShowProgressBar (state, value) {
state.showProgressBar = value
},

View File

@ -69,6 +69,7 @@
display: flex;
gap: 30px;
justify-content: space-between;
align-items: center;
}
.subscribeButton {

View File

@ -32,7 +32,6 @@ import {
parseLocalListVideo,
parseLocalSubscriberCount
} from '../../helpers/api/local'
import { Injectables } from '../../../constants'
export default defineComponent({
name: 'Channel',
@ -48,9 +47,6 @@ export default defineComponent({
'ft-subscribe-button': FtSubscribeButton,
'channel-about': ChannelAbout
},
inject: {
showOutlines: Injectables.SHOW_OUTLINES
},
data: function () {
return {
isLoading: false,
@ -1874,6 +1870,7 @@ export default defineComponent({
},
...mapActions([
'showOutlines',
'updateSubscriptionDetails'
])
}

View File

@ -1,86 +0,0 @@
import { defineComponent } from 'vue'
import { mapGetters } from 'vuex'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtProfileEdit from '../../components/ft-profile-edit/ft-profile-edit.vue'
import FtProfileChannelList from '../../components/ft-profile-channel-list/ft-profile-channel-list.vue'
import FtProfileFilterChannelsList from '../../components/ft-profile-filter-channels-list/ft-profile-filter-channels-list.vue'
import { MAIN_PROFILE_ID } from '../../../constants'
import { calculateColorLuminance, getRandomColor } from '../../helpers/colors'
import { showToast } from '../../helpers/utils'
export default defineComponent({
name: 'ProfileEdit',
components: {
'ft-loader': FtLoader,
'ft-profile-edit': FtProfileEdit,
'ft-profile-channel-list': FtProfileChannelList,
'ft-profile-filter-channels-list': FtProfileFilterChannelsList
},
data: function () {
return {
isLoading: true,
isNew: false,
profileId: '',
profile: {}
}
},
computed: {
...mapGetters([
'profileById'
]),
profileList: function () {
return this.$store.getters.getProfileList
},
isMainProfile: function () {
return this.profileId === MAIN_PROFILE_ID
}
},
watch: {
profileList: {
handler: function () {
const profile = this.profileById(this.profileId)
if (!profile) {
showToast(this.$t('Profile.Profile could not be found'))
this.$router.push({
path: '/settings/profile/'
})
}
this.profile = profile
},
deep: true
}
},
mounted: function () {
const profileType = this.$route.name
this.deletePromptLabel = `${this.$t('Profile.Are you sure you want to delete this profile?')} ${this.$t('Profile["All subscriptions will also be deleted."]')}`
if (profileType === 'newProfile') {
this.isNew = true
const bgColor = getRandomColor()
const textColor = calculateColorLuminance(bgColor)
this.profile = {
name: '',
bgColor: bgColor,
textColor: textColor,
subscriptions: []
}
} else {
this.isNew = false
this.profileId = this.$route.params.id
const profile = this.profileById(this.profileId)
if (!profile) {
showToast(this.$t('Profile.Profile could not be found'))
this.$router.push({
path: '/settings/profile/'
})
}
this.profile = profile
}
this.isLoading = false
}
})

View File

@ -1,27 +0,0 @@
<template>
<div>
<ft-loader
v-if="isLoading"
:fullscreen="true"
/>
<div
v-else
>
<ft-profile-edit
:profile="profile"
:is-new="isNew"
/>
<ft-profile-channel-list
v-if="!isNew"
:profile="profile"
:is-main-profile="isMainProfile"
/>
<ft-profile-filter-channels-list
v-if="!isNew && !isMainProfile"
:profile="profile"
/>
</div>
</div>
</template>
<script src="./ProfileEdit.js" />

View File

@ -1,7 +1,5 @@
.card {
inline-size: 85%;
margin-block: 0 60px;
margin-inline: auto;
h2 {
text-align: center;
}
.message {
@ -14,6 +12,23 @@
margin-inline: auto;
}
.profileList > .openedProfile {
background-color: var(--primary-text-color);
transition: background 0.2s ease-out;
color: var(--card-bg-color);
}
.profileList > .openedProfile:hover, .profileList > .openedProfile:focus-visible {
background-color: var(--tertiary-text-color);
transition: background 0.2s ease-in;
}
.card {
inline-size: 85%;
margin-block: 0 15px;
margin-inline: auto;
}
@media only screen and (max-width: 680px) {
.card {
inline-size: 90%;

View File

@ -1,8 +1,15 @@
import { defineComponent } from 'vue'
import { mapGetters } from 'vuex'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import FtProfileBubble from '../../components/ft-profile-bubble/ft-profile-bubble.vue'
import FtButton from '../../components/ft-button/ft-button.vue'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtProfileEdit from '../../components/ft-profile-edit/ft-profile-edit.vue'
import FtProfileChannelList from '../../components/ft-profile-channel-list/ft-profile-channel-list.vue'
import FtProfileFilterChannelsList from '../../components/ft-profile-filter-channels-list/ft-profile-filter-channels-list.vue'
import { calculateColorLuminance, getRandomColor } from '../../helpers/colors'
import { MAIN_PROFILE_ID } from '../../../constants'
export default defineComponent({
name: 'ProfileSettings',
@ -10,18 +17,74 @@ export default defineComponent({
'ft-card': FtCard,
'ft-flex-box': FtFlexBox,
'ft-profile-bubble': FtProfileBubble,
'ft-button': FtButton
'ft-button': FtButton,
'ft-loader': FtLoader,
'ft-profile-edit': FtProfileEdit,
'ft-profile-channel-list': FtProfileChannelList,
'ft-profile-filter-channels-list': FtProfileFilterChannelsList
},
data: function () {
return {
isNewProfileOpen: false,
openSettingsProfileId: null,
openSettingsProfile: null
}
},
computed: {
...mapGetters([
'profileById'
]),
profileList: function () {
return this.$store.getters.getProfileList
},
isMainProfile: function () {
return this.openSettingsProfileId === MAIN_PROFILE_ID
}
},
watch: {
profileList: {
handler: function () {
this.openSettingsProfile = this.getProfileById(this.openSettingsProfileId)
},
deep: true
}
},
methods: {
newProfile: function () {
this.$router.push({
path: '/settings/profile/new/'
})
openSettingsForNewProfile: function () {
this.isNewProfileOpen = true
const bgColor = getRandomColor()
const textColor = calculateColorLuminance(bgColor)
this.openSettingsProfile = {
name: '',
bgColor: bgColor,
textColor: textColor,
subscriptions: []
}
this.openSettingsProfileId = -1
},
openSettingsForProfileWithId: function (profileId) {
if (this.profileId === this.openSettingsProfileId) {
return
}
this.isNewProfileOpen = false
this.openSettingsProfileId = profileId
this.openSettingsProfile = this.getProfileById(profileId)
},
getProfileById: function (profileId) {
if (!profileId) {
return null
}
return this.profileById(profileId)
},
handleNewProfileCreated: function () {
this.isNewProfileOpen = false
this.openSettingsProfile = null
this.openSettingsProfileId = -1
},
handleProfileDeleted: function () {
this.openSettingsProfile = null
this.openSettingsProfileId = -1
}
}
})

View File

@ -12,15 +12,39 @@
:profile-name="profile.name"
:background-color="profile.bgColor"
:text-color="profile.textColor"
:class="{ openedProfile: openSettingsProfile?._id === profile._id }"
@click="openSettingsForProfileWithId(profile._id)"
/>
</ft-flex-box>
<ft-flex-box>
<ft-button
v-if="!isNewProfileOpen"
:label="$t('Profile.Create New Profile')"
@click="newProfile"
@click="openSettingsForNewProfile"
/>
</ft-flex-box>
</ft-card>
<div
v-if="openSettingsProfile"
:key="openSettingsProfileId"
>
<ft-profile-channel-list
v-if="!isNewProfileOpen"
:profile="openSettingsProfile"
:is-main-profile="isMainProfile"
/>
<ft-profile-filter-channels-list
v-if="!isNewProfileOpen && !isMainProfile"
:profile="openSettingsProfile"
/>
<ft-profile-edit
:profile="openSettingsProfile"
:is-new="isNewProfileOpen"
:is-main-profile="isMainProfile"
@new-profile-created="handleNewProfileCreated"
@profile-deleted="handleProfileDeleted"
/>
</div>
</div>
</template>

View File

@ -1,8 +1,23 @@
hr {
block-size: 2px;
inline-size: 85%;
margin-block: 0;
margin-inline: auto;
border: 0;
background-color: var(--scrollbar-color-hover);
border-block-start: 2px solid var(--scrollbar-color-hover);
}
@media only screen and (max-width: 800px) {
hr {
inline-size: 100%;
}
}
.switchColumnGrid {
inline-size: 85%;
margin-inline: auto;
}
.settingsToggle {
padding-block: 0;
margin-block: 10px 5px;
}

View File

@ -1,4 +1,5 @@
import { defineComponent } from 'vue'
import { mapActions } from 'vuex'
import GeneralSettings from '../../components/general-settings/general-settings.vue'
import ThemeSettings from '../../components/theme-settings/theme-settings.vue'
import PlayerSettings from '../../components/player-settings/player-settings.vue'
@ -14,6 +15,7 @@ import ParentControlSettings from '../../components/parental-control-settings/pa
import ExperimentalSettings from '../../components/experimental-settings/experimental-settings.vue'
import PasswordSettings from '../../components/password-settings/password-settings.vue'
import PasswordDialog from '../../components/password-dialog/password-dialog.vue'
import FtToggleSwitch from '../../components/ft-toggle-switch/ft-toggle-switch.vue'
export default defineComponent({
name: 'Settings',
@ -33,6 +35,7 @@ export default defineComponent({
'experimental-settings': ExperimentalSettings,
'password-settings': PasswordSettings,
'password-dialog': PasswordDialog,
'ft-toggle-switch': FtToggleSwitch
},
data: function () {
return {
@ -46,11 +49,20 @@ export default defineComponent({
settingsPassword: function () {
return this.$store.getters.getSettingsPassword
}
},
allSettingsSectionsExpandedByDefault: function () {
return this.$store.getters.getAllSettingsSectionsExpandedByDefault
},
},
created: function () {
if (this.settingsPassword === '') {
this.unlocked = true
}
},
methods: {
...mapActions([
'updateAllSettingsSectionsExpandedByDefault',
])
}
})

View File

@ -1,6 +1,17 @@
<template>
<div>
<template v-if="unlocked">
<div class="switchColumnGrid">
<div class="switchColumn">
<ft-toggle-switch
class="settingsToggle"
:label="$t('Settings.Expand All Settings Sections')"
:default-value="allSettingsSectionsExpandedByDefault"
:compact="false"
@change="updateAllSettingsSectionsExpandedByDefault"
/>
</div>
</div>
<general-settings />
<hr>
<theme-settings />

View File

@ -26,7 +26,7 @@
<ft-flex-box class="channels">
<div
v-for="channel in channelList"
:key="channel.key"
:key="channel.id"
class="channel"
>
<router-link
@ -53,10 +53,10 @@
class="unsubscribeContainer"
>
<ft-subscribe-button
class="btn"
:channel-id="channel.id"
:channel-name="channel.name"
:channel-thumbnail="channel.thumbnail"
:open-dropdown-on-subscribe="false"
/>
</div>
</div>

View File

@ -1,4 +1,5 @@
import { defineComponent } from 'vue'
import { mapActions } from 'vuex'
import SubscriptionsVideos from '../../components/subscriptions-videos/subscriptions-videos.vue'
import SubscriptionsLive from '../../components/subscriptions-live/subscriptions-live.vue'
@ -7,7 +8,6 @@ import SubscriptionsCommunity from '../../components/subscriptions-community/sub
import FtCard from '../../components/ft-card/ft-card.vue'
import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import { Injectables } from '../../../constants'
export default defineComponent({
name: 'Subscriptions',
@ -19,9 +19,6 @@ export default defineComponent({
'ft-card': FtCard,
'ft-flex-box': FtFlexBox
},
inject: {
showOutlines: Injectables.SHOW_OUTLINES
},
data: function () {
return {
currentTab: 'videos'
@ -148,6 +145,10 @@ export default defineComponent({
this.$refs[visibleTabs[index]].focus()
this.showOutlines()
}
}
},
...mapActions([
'showOutlines'
])
}
})

View File

@ -1,4 +1,5 @@
import { defineComponent } from 'vue'
import { mapActions } from 'vuex'
import FtCard from '../../components/ft-card/ft-card.vue'
import FtLoader from '../../components/ft-loader/ft-loader.vue'
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
@ -8,7 +9,6 @@ import FtFlexBox from '../../components/ft-flex-box/ft-flex-box.vue'
import { copyToClipboard, showToast } from '../../helpers/utils'
import { getLocalTrending } from '../../helpers/api/local'
import { invidiousAPICall } from '../../helpers/api/invidious'
import { Injectables } from '../../../constants'
import { getPipedTrending } from '../../helpers/api/piped'
export default defineComponent({
@ -20,9 +20,6 @@ export default defineComponent({
'ft-icon-button': FtIconButton,
'ft-flex-box': FtFlexBox
},
inject: {
showOutlines: Injectables.SHOW_OUTLINES
},
data: function () {
return {
isLoading: false,
@ -248,6 +245,10 @@ export default defineComponent({
}
break
}
}
},
...mapActions([
'showOutlines'
])
}
})

View File

@ -413,7 +413,7 @@ Settings:
Hide Chapters: إخفاء الفصول
Hide Upcoming Premieres: إخفاء العروض الأولى القادمة
Hide Channels: إخفاء مقاطع الفيديو من القنوات
Hide Channels Placeholder: اسم القناة أو معرفها
Hide Channels Placeholder: معرف القناة
Display Titles Without Excessive Capitalisation: عرض العناوين بدون احرف كبيرة
بشكل مفرط
Hide Featured Channels: إخفاء القنوات المميزة
@ -434,6 +434,12 @@ Settings:
Hide Profile Pictures in Comments: إخفاء صور الملف الشخصي في التعليقات
Blur Thumbnails: اخفاء الصور المصغرة
Hide Subscriptions Community: إخفاء مجتمع الاشتراكات
Hide Channels Invalid: معرف القناة المقدم غير صالح
Hide Channels Disabled Message: تم حظر بعض القنوات باستخدام المعرّف ولم تتم معالجتها.
يتم حظر الميزة أثناء تحديث هذه المعرفات
Hide Channels Already Exists: معرف القناة موجود بالفعل
Hide Channels API Error: ‬حدث خطأ أثناء استرداد المستخدم بالمعرف المدخل. يرجى
التحقق مرة أخرى إذا كان المعرف صحيحا.
The app needs to restart for changes to take effect. Restart and apply change?: البرنامج
يحتاج لإعادة التشغيل كي يسري مفعول التغييرات. هل تريد إعادة التشغيل و تطبيق التغييرات؟
Proxy Settings:
@ -603,6 +609,8 @@ Profile:
Profile Filter: مرشح الملف الشخصي
Profile Settings: إعدادات الملف الشخصي
Toggle Profile List: تبديل قائمة الملف الشخصي
Open Profile Dropdown: فتح القائمة المنسدلة للملف الشخصي
Close Profile Dropdown: إغلاق القائمة المنسدلة للملف الشخصي
Channel:
Subscribe: 'اشتراك'
Unsubscribe: 'إلغاء الاشتراك'
@ -805,6 +813,8 @@ Video:
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': الدردشة
المباشرة غير متاحة لهذا البث. ربما تم تعطيلها من قبل القائم بالتحميل.
Pause on Current Video: توقف مؤقتًا على الفيديو الحالي
Unhide Channel: عرض القناة
Hide Channel: إخفاء القناة
Videos:
#& Sort By
Sort By:
@ -964,9 +974,9 @@ Tooltips:
وتمكين ذاكرة تخزين مؤقت للصور في الذاكرة. سيؤدي إلى زيادة استخدام ذاكرة الوصول
العشوائي.
Distraction Free Settings:
Hide Channels: أدخل اسم قناة أو معرّف القناة لإخفاء جميع مقاطع الفيديو وقوائم
التشغيل والقناة نفسها من الظهور في البحث والشهرة والأكثر شعبية والموصى بها.
يجب أن يكون اسم القناة الذي تم إدخاله مطابقًا تمامًا وحساسًا لحالة الأحرف.
Hide Channels: أدخل معرف القناة لإخفاء جميع مقاطع الفيديو وقوائم التشغيل والقناة
نفسها من الظهور في نتائج البحث والشائعة والأكثر شهرة والموصى بها. يجب أن يكون
معرف القناة الذي تم إدخاله متطابقًا تمامًا وأن يكون حساسًا لحالة الأحرف.
Hide Subscriptions Live: يتم تجاوز هذا الإعداد من خلال إعداد "{appWideSetting}"
على مستوى التطبيق، في قسم "{subsection}" من "{settingsSection}"
SponsorBlock Settings:
@ -1031,3 +1041,6 @@ Playlist will pause when current video is finished: ستتوقف قائمة ال
انتهاء الفيديو الحالي
Playlist will not pause when current video is finished: لن تتوقف قائمة التشغيل مؤقتًا
عند انتهاء الفيديو الحالي
Channel Hidden: تم إضافة {channel} إلى مرشح القناة
Go to page: إذهب إلى {page}
Channel Unhidden: تمت إزالة {channel} من مرشح القناة

870
static/locales/ckb.yaml Normal file
View File

@ -0,0 +1,870 @@
# Put the name of your locale in the same language
Locale Name: 'ئیگلیزی (وڵاتە یەکگرتووەکانی ئەمریکا)'
FreeTube: 'فریتیوب'
# Currently on Subscriptions, Playlists, and History
'This part of the app is not ready yet. Come back later when progress has been made.': >-
بەشێک لە نەرمەواڵەکە هێشتا ئامادە نییە. کە ڕەوتەکە درووست کرا دووبارە وەرەوە.
# Webkit Menu Bar
File: 'پەڕگە'
New Window: 'پەنجەرەی نوێ'
Preferences: 'هەڵبژاردەکان'
Quit: 'دەرچوون'
Edit: 'دەستکاری'
Undo: 'پووچکردنەوە'
Redo: 'کردنەوە'
Cut: 'بڕین'
Copy: 'لەبەرگرتنەوە'
Paste: 'لکاندن'
Delete: 'سڕینەوە'
Select all: 'دیاریکردنی گشتیان'
Reload: 'بارکردنەوە'
Force Reload: 'باکردنەوەی بەزۆر'
Toggle Developer Tools: 'زامنی ئامرازەکانی گەشەپێدەر'
Actual size: 'قەبارەی ڕاستەقینە'
Zoom in: ''
Zoom out: ''
Toggle fullscreen: ''
Window: 'پەنجەرە'
Minimize: ''
Close: 'داخستن'
Back: 'دواوە'
Forward: 'پێشەوە'
Open New Window: 'کردنەوەی پەنجەرەیەکی نوێ'
Version {versionNumber} is now available! Click for more details: 'ئێستا وەشانی {versionNumber}
بەردەستە..بۆ زانیاری زۆرتر کرتە بکە'
Download From Site: 'لە وێبگەوە دایگرە'
A new blog is now available, {blogTitle}. Click to view more: 'بلۆگێکی نوێ بەردەستە،
{blogTitle}. کرتە بکە بۆ بینینی'
Are you sure you want to open this link?: 'دڵنیایت دەتەوێت ئەم بەستەرە بکەیتەوە؟'
# Global
# Anything shared among components / views should be put here
Global:
Videos: 'ڤیدیۆکان'
Shorts: ''
Live: 'ڕاستەوخۆ'
Community: 'کۆمەڵگە'
Counts:
Video Count: '١ ڤیدیۆ | {count} ڤیدیۆ'
Channel Count: '١ کەناڵ | {count} کەناڵ'
Subscriber Count: '١ بەشداربوو | {count} بەشداربوو'
View Count: 'بینینەک | {count} بینین'
Watching Count: '١ تەمەشاکردن | {count} تەمەشاکردن'
# Search Bar
Search / Go to URL: 'گەڕان/ بڕۆ بۆ ئرڵ'
Search Bar:
Clear Input: ''
# In Filter Button
Search Filters:
Search Filters: 'پاڵفتەکردنی گەڕان'
Sort By:
Sort By: 'ڕیزکردن بە'
Most Relevant: ''
Rating: 'هەڵسەنگاندن'
Upload Date: 'ڕێکەوتی بارکردن'
View Count: 'ژمارەی بینین'
Time:
Time: 'کات'
Any Time: 'هەر کاتێک'
Last Hour: 'پێش کاتژمێرێک'
Today: 'ئەمڕۆ'
This Week: 'ئەم هەفتەیە'
This Month: 'ئەم مانگە'
This Year: 'ئەمساڵ'
Type:
Type: 'جۆر'
All Types: 'گشت جۆرەکان'
Videos: 'ڤیدیۆ'
Channels: 'کەناڵ'
Movies: 'فیلم'
#& Playlists
Duration:
Duration: 'ماوە'
All Durations: 'گشت ماوەکان'
Short (< 4 minutes): 'کورت (< ٤ خولەک)'
Medium (4 - 20 minutes): 'ناوەند (٤ - ٢٠ خولەک)'
Long (> 20 minutes): 'درێژ (> ٢٠ خولەک)'
# On Search Page
Search Results: 'ئەنجامەکانی گەڕان'
Fetching results. Please wait: ''
Fetch more results: ''
There are no more results for this search: 'ئەنجامەکی تر نییە بۆ ئەم گەڕانە'
# Sidebar
Subscriptions:
# On Subscriptions Page
Subscriptions: ''
# channels that were likely deleted
Error Channels: ''
Latest Subscriptions: ''
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: ''
'Your Subscription list is currently empty. Start adding subscriptions to see them here.': ''
Disabled Automatic Fetching: ''
Empty Channels: 'کەناڵە بەشداربووەکانت هێشتا هیچ ڤیدیۆیەکیان نییە.'
'Getting Subscriptions. Please wait.': ''
Empty Posts: ''
Refresh Subscriptions: ''
Load More Videos: 'بارکردنی ڤیدیۆی زۆرتر'
Load More Posts: ''
Subscriptions Tabs: ''
All Subscription Tabs Hidden: ''
More: 'زۆرتر'
Channels:
Channels: 'کەناڵەکان'
Title: 'پێڕستی کەناڵەکان'
Search bar placeholder: ''
Count: '{number} کەناڵ دۆزرانەوە.'
Empty: 'ئێستا پێڕستی کەناڵەکانت بەتاڵە.'
Unsubscribe: ''
Unsubscribed: ''
Unsubscribe Prompt: ''
Trending:
Trending: ''
Default: 'بنەڕەت'
Music: 'مۆسیقا'
Gaming: 'یاری'
Movies: 'فیلم'
Trending Tabs: ''
Most Popular: 'باوترین'
Playlists: 'پێڕستی لێدانەکان'
User Playlists:
Your Playlists: 'پێڕستی لێدانەکانت'
Playlist Message: ''
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: ''
Empty Search Message: ''
Search bar placeholder: 'لەناو پێڕستی لێدان بگەڕێ'
History:
# On History Page
History: 'مێژوو'
Watch History: 'مێژووی تەمەشاکردن'
Your history list is currently empty.: 'ئێستا لیستەی مێژووت بەتاڵە.'
Empty Search Message: 'هیچ ڤیدیۆیەک لە مێژووت نەدۆزرایەوە کە بەرانبەری گەڕانەکەت
بێت'
Search bar placeholder: "لەناو مێژوو بگەڕێ"
Settings:
# On Settings Page
Settings: 'ڕێکخستنەکان'
The app needs to restart for changes to take effect. Restart and apply change?: ''
General Settings:
General Settings: 'ڕێکخستنە گشتییەکان'
Check for Updates: ''
Check for Latest Blog Posts: ''
Fallback to Non-Preferred Backend on Failure: ''
Enable Search Suggestions: 'کاراکردنی پێشنیارەکانی گەڕان'
Default Landing Page: ''
Locale Preference: ''
System Default: 'بنەڕەتی سیستەم'
Preferred API Backend:
Preferred API Backend: ''
Local API: ''
Invidious API: ''
Video View Type:
Video View Type: ''
Grid: 'خانەخانە'
List: 'لیستە'
Thumbnail Preference:
Thumbnail Preference: ''
Default: 'بنەڕەت'
Beginning: 'سەرەتا'
Middle: 'ناوەڕاست'
End: 'کۆتایی'
Hidden: 'شاراوە'
Blur: ''
Current Invidious Instance: ''
The currently set default instance is {instance}: ''
No default instance has been set: ''
Current instance will be randomized on startup: ''
Set Current Instance as Default: ''
Clear Default Instance: ''
View all Invidious instance information: ''
Region for Trending: ''
#! List countries
External Link Handling:
External Link Handling: ''
Open Link: 'کردنەوەی بەستەر'
Ask Before Opening Link: 'پێش کردنەوەی بەستەر بپرسە'
No Action: 'هیچ مەکە'
Theme Settings:
Theme Settings: 'ڕێکخستنەکانی ڕووکار'
Match Top Bar with Main Color: ''
Expand Side Bar by Default: ''
Disable Smooth Scrolling: ''
UI Scale: ''
Hide Side Bar Labels: ''
Hide FreeTube Header Logo: ''
Base Theme:
Base Theme: 'ڕووکاری بنچینە'
Black: 'ڕەش'
Dark: 'تاریک'
System Default: 'بنەڕەتی سیستەم'
Light: 'ڕووناک'
Dracula: ''
Catppuccin Mocha: ''
Pastel Pink: ''
Hot Pink: ''
Main Color Theme:
Main Color Theme: 'ڕەنگی سەرەکی ڕووکار'
Red: 'سوور'
Pink: 'پەمبە'
Purple: 'وەنەوشەیی'
Deep Purple: 'وەنەوشەیی تۆخ'
Indigo: 'نیلی'
Blue: 'شین'
Light Blue: 'شینی ئاڵ'
Cyan: 'شینی تۆخ'
Teal: 'شەدری'
Green: 'کەسک'
Light Green: 'کەسکی ئاڵ'
Lime: ''
Yellow: 'زەرد'
Amber: ''
Orange: 'نارنجی'
Deep Orange: 'نارنجی تۆخ'
Dracula Cyan: ''
Dracula Green: ''
Dracula Orange: ''
Dracula Pink: ''
Dracula Purple: ''
Dracula Red: ''
Dracula Yellow: ''
Catppuccin Mocha Rosewater: ''
Catppuccin Mocha Flamingo: ''
Catppuccin Mocha Pink: ''
Catppuccin Mocha Mauve: ''
Catppuccin Mocha Red: ''
Catppuccin Mocha Maroon: ''
Catppuccin Mocha Peach: ''
Catppuccin Mocha Yellow: ''
Catppuccin Mocha Green: ''
Catppuccin Mocha Teal: ''
Catppuccin Mocha Sky: ''
Catppuccin Mocha Sapphire: ''
Catppuccin Mocha Blue: ''
Catppuccin Mocha Lavender: ''
Secondary Color Theme: 'ڕەنگی لاوەکی ڕووکار'
#* Main Color Theme
Player Settings:
Player Settings: 'ڕێکخستنەکانی لێدەر'
Force Local Backend for Legacy Formats: ''
Play Next Video: 'لێدانی ڤیدیۆی دواتر'
Turn on Subtitles by Default: 'هەڵکردنی بنەڕەتی ژێرنووس'
Autoplay Videos: 'خۆلێدانی ڤیدیۆ'
Proxy Videos Through Invidious: ''
Autoplay Playlists: 'خۆلێدانی پێڕستی لێدان'
Enable Theatre Mode by Default: 'کاراکردنی بنەڕەتیی شێوازی شانۆیی'
Scroll Volume Over Video Player: ''
Scroll Playback Rate Over Video Player: ''
Skip by Scrolling Over Video Player: ''
Display Play Button In Video Player: ''
Enter Fullscreen on Display Rotate: ''
Next Video Interval: ''
Fast-Forward / Rewind Interval: ''
Default Volume: ''
Default Playback Rate: ''
Max Video Playback Rate: ''
Video Playback Rate Interval: ''
Default Video Format:
Default Video Format: ''
Dash Formats: ''
Legacy Formats: ''
Audio Formats: ''
Default Quality:
Default Quality: 'جۆرایەتی بنەڕەت'
Auto: 'خۆکار'
144p: '١٤٤p'
240p: '٢٤٠p'
360p: '٣٦٠p'
480p: '٤٨٠p'
720p: '٧٢٠p'
1080p: '١٠٨٠p'
1440p: '١٤٤٠p'
4k: '٤k'
8k: '٨k'
Allow DASH AV1 formats: ''
Screenshot:
Enable: ''
Format Label: ''
Quality Label: ''
Ask Path: 'بۆ بوخچەی پاشەکەوت بپرسە'
Folder Label: ''
Folder Button: 'دیاریکردنی بوخچە'
File Name Label: ''
File Name Tooltip: ''
Error:
Forbidden Characters: ''
Empty File Name: ''
Comment Auto Load:
Comment Auto Load: ''
External Player Settings:
External Player Settings: 'ڕێکخستنەکانی لێدەری دەرەکی'
External Player: 'لێدەری دەرەکی'
Ignore Unsupported Action Warnings: ''
Custom External Player Executable: ''
Custom External Player Arguments: ''
Players:
None:
Name: 'هیچیان'
Privacy Settings:
Privacy Settings: 'ڕێکخستنەکانی نهێنێتی'
Remember History: ''
Save Watched Progress: ''
Save Watched Videos With Last Viewed Playlist: ''
Automatically Remove Video Meta Files: ''
Clear Search Cache: ''
Are you sure you want to clear out your search cache?: ''
Search cache has been cleared: ''
Remove Watch History: 'سڕینەوەی مێژووی تەمەشاکردن'
Are you sure you want to remove your entire watch history?: 'دڵنیایت دەتەوێت تەواوی
مێژووی تەمەشاکردنت بسڕیەوە؟'
Watch history has been cleared: 'مێژووی تەمەشاکردن لابرا'
Remove All Subscriptions / Profiles: ''
Are you sure you want to remove all subscriptions and profiles? This cannot be undone.: ''
Subscription Settings:
Subscription Settings: ''
Hide Videos on Watch: ''
Fetch Feeds from RSS: ''
Manage Subscriptions: ''
Fetch Automatically: ''
Distraction Free Settings:
Distraction Free Settings: ''
Sections:
Side Bar: ''
Subscriptions Page: ''
Channel Page: 'پەڕەی کەناڵ'
Watch Page: 'پەڕەی تەمەشاکردن'
General: 'گشتی'
Hide Video Views: 'شاردنەوەی ژمارەی بینراوەکانی ڤیدیۆ'
Hide Video Likes And Dislikes: ''
Hide Channel Subscribers: ''
Hide Comment Likes: ''
Hide Recommended Videos: 'شاردنەوەی ڤیدیۆ پێشنیازکراوەکان'
Hide Trending Videos: ''
Hide Popular Videos: ''
Hide Playlists: 'شاردنەوەی پێڕستی لێدان'
Hide Live Chat: ''
Hide Active Subscriptions: ''
Hide Video Description: ''
Hide Comments: ''
Hide Profile Pictures in Comments: ''
Display Titles Without Excessive Capitalisation: ''
Hide Live Streams: ''
Hide Upcoming Premieres: ''
Hide Sharing Actions: ''
Hide Chapters: ''
Hide Channels: ''
Hide Channels Disabled Message: ''
Hide Channels Placeholder: ''
Hide Channels Invalid: ''
Hide Channels API Error: ''
Hide Channels Already Exists: ''
Hide Featured Channels: ''
Hide Channel Playlists: ''
Hide Channel Community: ''
Hide Channel Shorts: ''
Hide Channel Podcasts: ''
Hide Channel Releases: ''
Hide Subscriptions Videos: ''
Hide Subscriptions Shorts: ''
Hide Subscriptions Live: ''
Hide Subscriptions Community: ''
Data Settings:
Data Settings: 'ڕێکخستنەکانی دراوە'
Select Import Type: 'دیاریکردنی جۆری هاوردە'
Select Export Type: 'دیاریکردنی جۆری هەناردە'
Import Subscriptions: ''
Subscription File: ''
History File: 'پەڕگەی مێژوو'
Playlist File: 'پەڕگەی پێڕستی لێدان'
Check for Legacy Subscriptions: ''
Export Subscriptions: ''
Export FreeTube: 'هەناردەکردنی فریتیوب'
Export YouTube: 'هەناردەکردنی یوتیوب'
Export NewPipe: 'هەناردەکردنی نیوپایپ'
Import History: 'هاوردەکردنی مێژوو'
Export History: 'هەناردەکردنی مێژوو'
Import Playlists: 'هاوردەکردنی پێڕستی لێدان'
Export Playlists: 'هەناردەکردنی پێڕستی لێدان'
Profile object has insufficient data, skipping item: ''
All subscriptions and profiles have been successfully imported: ''
All subscriptions have been successfully imported: ''
One or more subscriptions were unable to be imported: ''
Invalid subscriptions file: ''
This might take a while, please wait: 'تکایە چاوەڕوانبە لەوانەیە هەندێک کاتی پێ
بچێت'
Invalid history file: 'پەڕگەی نادرووستی مێژوو'
Subscriptions have been successfully exported: ''
History object has insufficient data, skipping item: ''
All watched history has been successfully imported: ''
All watched history has been successfully exported: ''
Playlist insufficient data: ''
All playlists has been successfully imported: ''
All playlists has been successfully exported: ''
Unable to read file: ''
Unable to write file: ''
Unknown data key: ''
How do I import my subscriptions?: ''
Manage Subscriptions: ''
Proxy Settings:
Proxy Settings: 'ڕێکخستنەکانی پێشکار'
Enable Tor / Proxy: 'کاراکردنی تۆر / پێشکار'
Proxy Protocol: 'پرۆتۆکۆلی پێشکار'
Proxy Host: 'خانەخوێی پێشکار'
Proxy Port Number: 'ژمارەی دەرچەی پێشکار'
Clicking on Test Proxy will send a request to: 'کرتە کردن لە تاقیکردنەوەی پێشکار،
داخوازییەک دەنێرێت بۆ'
Test Proxy: 'تاقیکردنەوەی پێشکار'
Your Info: 'زانیارییەکانت'
Ip: 'ئای پی'
Country: 'وڵات'
Region: 'هەرێم'
City: 'شار'
Error getting network information. Is your proxy configured properly?: ''
SponsorBlock Settings:
SponsorBlock Settings: ''
Enable SponsorBlock: ''
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': ''
Notify when sponsor segment is skipped: ''
UseDeArrowTitles: ''
Skip Options:
Skip Option: ''
Auto Skip: ''
Show In Seek Bar: ''
Prompt To Skip: ''
Do Nothing: 'هیچ مەکە'
Category Color: ''
Parental Control Settings:
Parental Control Settings: ''
Hide Unsubscribe Button: ''
Show Family Friendly Only: ''
Hide Search Bar: 'شاردنەوەی میلی گەڕان'
Download Settings:
Download Settings: 'ڕێکخستنەکانی داگرتن'
Ask Download Path: ''
Choose Path: ''
Download Behavior: ''
Download in app: ''
Open in web browser: ''
Experimental Settings:
Experimental Settings: ''
Warning: ''
Replace HTTP Cache: ''
Password Dialog:
Password: 'تێپەڕەوشە'
Enter Password To Unlock: 'تێپەڕەوشە بنووسە بۆ کردنەوەی کڵۆمی ڕێکخستنەکان'
Password Incorrect: 'تێپەڕەوشەی نادرووست'
Unlock: 'کردنەوەی کڵۆم'
Password Settings:
Password Settings: 'ڕێکخستنەکانی تێپەڕەوشە'
Set Password To Prevent Access: ''
Set Password: 'دانانی تێپەڕەوشە'
Remove Password: 'لادانی تێپەڕەوشە'
About:
#On About page
About: 'دەربارە'
Beta: ''
Source code: 'کۆدی سەرچاوە'
Licensed under the AGPLv3: 'مۆڵەتی وەشانی سێیەمی AGPL هەیە'
View License: 'بینینی مۆڵەت'
Downloads / Changelog: ''
GitHub releases: ''
Help: 'یارمەتی'
FreeTube Wiki: 'ویکی فریتیوب'
FAQ: 'پرسیارە دووبارەکان'
Discussions: ''
Report a problem: 'سکاڵا لە کێشەیەک بکە'
GitHub issues: ''
Please check for duplicates before posting: ''
Website: 'وێبگە'
Blog: 'بلۆگ'
Email: 'ئیمێڵ'
Mastodon: 'ماستادۆن'
Chat on Matrix: ''
Please read the: ''
room rules: ''
Translate: 'وەرگێڕان'
Credits: ''
FreeTube is made possible by: ''
these people and projects: ''
Donate: 'بەخشین'
Profile:
Profile Settings: ''
Toggle Profile List: ''
Profile Select: ''
Profile Filter: ''
All Channels: 'هەموو کەناڵەکان'
Profile Manager: ''
Create New Profile: ''
Edit Profile: ''
Color Picker: ''
Custom Color: ''
Profile Preview: ''
Create Profile: ''
Update Profile: ''
Make Default Profile: ''
Delete Profile: ''
Are you sure you want to delete this profile?: ''
All subscriptions will also be deleted.: ''
Profile could not be found: ''
Your profile name cannot be empty: ''
Profile has been created: ''
Profile has been updated: ''
Your default profile has been set to {profile}: ''
Removed {profile} from your profiles: ''
Your default profile has been changed to your primary profile: ''
'{profile} is now the active profile': ''
Subscription List: ''
Other Channels: ''
'{number} selected': '{number} دیاریکراوە'
Select All: 'دیاریکردنی هەموویان'
Select None: 'دیاری نەکردنی هیچیان'
Delete Selected: 'سڕینەوەی دیاریکراوەکان'
Add Selected To Profile: ''
No channel(s) have been selected: 'هیچ کەناڵێک دیاری نەکراوە'
? This is your primary profile. Are you sure you want to delete the selected channels? The
same channels will be deleted in any profile they are found in.
: ''
Are you sure you want to delete the selected channels? This will not delete the channel from any other profile.: ''
#On Channel Page
Channel:
Subscribe: ''
Unsubscribe: ''
Channel has been removed from your subscriptions: ''
Removed subscription from {count} other channel(s): ''
Added channel to your subscriptions: ''
Search Channel: ''
Your search results have returned 0 results: ''
Sort By: 'ڕیزکردن بە'
This channel does not exist: 'ئەم کەناڵە بوونی نییە'
This channel does not allow searching: 'ئەم کەناڵە ڕێ بە گەڕان نادات'
This channel is age-restricted and currently cannot be viewed in FreeTube.: ''
Channel Tabs: ''
Videos:
Videos: 'ڤیدیۆکان'
This channel does not currently have any videos: 'ئەم کەناڵە ئێستا هیچ ڤیدیۆیەکی
نییە'
Sort Types:
Newest: 'نوێترین'
Oldest: 'کۆنترین'
Most Popular: ''
Shorts:
This channel does not currently have any shorts: ''
Live:
Live: 'ڕاستەوخۆ'
This channel does not currently have any live streams: ''
Playlists:
Playlists: 'پێڕستی لێدان'
This channel does not currently have any playlists: 'ئەم کەناڵە ئێستا هیچ پێڕستێکی
لێدانی نییە'
Sort Types:
Last Video Added: 'دوایین ڤیدیۆ زیادکراوەکان'
Newest: 'نوێترین'
Oldest: 'کۆنترین'
Podcasts:
Podcasts: 'پۆدکاستەکان'
This channel does not currently have any podcasts: 'ئەم کەناڵە ئێستا هیچ پۆدکاستێکی
نییە'
Releases:
Releases: ''
This channel does not currently have any releases: ''
About:
About: 'دەربارە'
Channel Description: 'پێناسی کەناڵ'
Tags:
Tags: ''
Search for: ''
Details: 'وردەکاری'
Joined: ''
Location: ''
Featured Channels: ''
Community:
This channel currently does not have any posts: ''
votes: '{votes} دەنگ'
Reveal Answers: ''
Hide Answers: 'شاردنەوەی وەڵامەکان'
Video:
Mark As Watched: 'وەکو تەمەشاکراو نیشانی بکە'
Remove From History: 'لە مێژوو لای ببە'
Video has been marked as watched: 'ڤیدیۆکە وەکو تەمەشاکراو نیشان کراوە'
Video has been removed from your history: 'ڤیدیۆکە لە مێژووەکەت لابرا'
Save Video: 'پاشەکەوتکردنی ڤیدیۆ'
Video has been saved: 'ڤیدیۆکە پاشەکەوت کرا'
Video has been removed from your saved list: 'ڤیدیۆکە لە لیستەی پاشەکەوت کراوەکان
لابرا'
Open in YouTube: 'کردنەوە لە یوتیوب'
Copy YouTube Link: 'بەستەری یوتیوب لەبەر بگرەوە'
Open YouTube Embedded Player: ''
Copy YouTube Embedded Player Link: ''
Open in Invidious: ''
Copy Invidious Link: ''
Open Channel in YouTube: 'کردنەوەی کەناڵ لە یوتیوب'
Copy YouTube Channel Link: 'لەبەرگرتنەوەی بەستەری کەناڵی یوتیوب'
Open Channel in Invidious: ''
Copy Invidious Channel Link: ''
Views: 'بینراو'
Loop Playlist: ''
Shuffle Playlist: ''
Reverse Playlist: ''
Play Next Video: 'لێدانی ڤیدیۆی دواتر'
Play Previous Video: 'لێدانی ڤیدیۆی پێشوو'
Pause on Current Video: ''
Watched: 'تەمەشاکراو'
Autoplay: 'خۆلێدان'
Starting soon, please refresh the page to check again: ''
# As in a Live Video
Premieres on: ''
Premieres: ''
Upcoming: ''
Live: ''
Live Now: ''
Live Chat: ''
Enable Live Chat: ''
Live Chat is currently not supported in this build.: ''
'Chat is disabled or the Live Stream has ended.': ''
Live chat is enabled. Chat messages will appear here once sent.: ''
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': ''
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': ''
Show Super Chat Comment: ''
Scroll to Bottom: ''
Download Video: 'داگرتنی ڤیدیۆ'
video only: 'تەنیا ڤیدیۆ'
audio only: 'تەنیا دەنگ'
Audio:
Low: 'نزم'
Medium: 'مامناوەند'
High: 'بەرز'
Best: 'باشترین'
Published:
Jan: ''
Feb: ''
Mar: ''
Apr: ''
May: ''
Jun: ''
Jul: ''
Aug: ''
Sep: ''
Oct: ''
Nov: ''
Dec: ''
Second: 'چرکە'
Seconds: 'چرکە'
Minute: 'خولەک'
Minutes: 'خولەک'
Hour: 'کاتژمێر'
Hours: 'کاتژمێر'
Day: 'ڕۆژ'
Days: 'ڕۆژ'
Week: 'هەفتە'
Weeks: 'هەفتە'
Month: 'مانگ'
Months: 'مانگ'
Year: 'ساڵ'
Years: 'ساڵ'
Ago: ''
Upcoming: ''
In less than a minute: ''
Published on: 'بڵاوکرایەوە لە'
Streamed on: ''
Started streaming on: ''
translated from English: ''
Publicationtemplate: ''
Skipped segment: ''
Sponsor Block category:
sponsor: ''
intro: ''
outro: ''
self-promotion: ''
interaction: ''
music offtopic: ''
recap: ''
filler: ''
External Player:
OpenInTemplate: ''
video: 'ڤیدیۆ'
playlist: 'پێڕستی لێدان'
OpeningTemplate: ''
UnsupportedActionTemplate: ''
Unsupported Actions:
starting video at offset: ''
setting a playback rate: ''
opening playlists: ''
opening specific video in a playlist (falling back to opening the video): ''
reversing playlists: ''
shuffling playlists: ''
looping playlists: ''
Stats:
Video statistics are not available for legacy videos: ''
Video ID: ''
Resolution: ''
Player Dimensions: ''
Bitrate: ''
Volume: ''
Bandwidth: ''
Buffered: ''
Dropped / Total Frames: ''
Mimetype: ''
#& Videos
Unhide Channel: پیشاندانی کەناڵ
Hide Channel: شاردنەوەی کەناڵ
Videos:
#& Sort By
Sort By:
Newest: 'نوێترین'
Oldest: 'کۆنترین'
#& Most Popular
#& Playlists
Playlist:
#& About
Playlist: 'پێڕستی لێدان'
View Full Playlist: ''
Videos: 'ڤیدیۆکان'
View: 'بینراو'
Views: 'بینراو'
Last Updated On: ''
# On Video Watch Page
#* Published
#& Views
Toggle Theatre Mode: ''
Change Format:
Change Media Formats: ''
Use Dash Formats: ''
Use Legacy Formats: ''
Use Audio Formats: ''
Dash formats are not available for this video: ''
Audio formats are not available for this video: ''
Share:
Share Video: 'هاوبەشکردنی ڤیدیۆ'
Share Channel: 'هاوبەشکردنی کەناڵ'
Share Playlist: 'هاوبەشکردنی پێڕستی لێدان'
Include Timestamp: ''
Copy Link: 'لەبەرگرتنەوەی بەستەر'
Open Link: 'کردنەوەی بەستەر'
Copy Embed: ''
Open Embed: ''
# On Click
Invidious URL copied to clipboard: ''
Invidious Embed URL copied to clipboard: ''
Invidious Channel URL copied to clipboard: ''
YouTube URL copied to clipboard: ''
YouTube Embed URL copied to clipboard: ''
YouTube Channel URL copied to clipboard: ''
Clipboard:
Copy failed: ''
Cannot access clipboard without a secure connection: ''
Chapters:
Chapters: ''
'Chapters list visible, current chapter: {chapterName}': ''
'Chapters list hidden, current chapter: {chapterName}': ''
Mini Player: 'لێدەری گچکە'
Comments:
Comments: ''
Click to View Comments: ''
Getting comment replies, please wait: ''
There are no more comments for this video: ''
Show Comments: ''
Hide Comments: ''
Sort by: 'ڕیزکردن بە'
Top comments: ''
Newest first: 'سەرەتا نوێترینەکان'
View {replyCount} replies: ''
# Context: View 10 Replies, View 1 Reply, View 1 Reply from Owner, View 2 Replies from Owner and others
View: 'بینین'
Hide: 'شاردنەوە'
Replies: 'وەڵامدانەوەکان'
Show More Replies: 'پیشاندانی وەڵامدانەوەی زۆرتر'
Reply: 'وەڵامدانەوە'
From {channelName}: 'لە {channelName}ەوە'
And others: 'هی تر'
There are no comments available for this video: ''
Load More Comments: ''
No more comments available: ''
Pinned by: ''
Member: 'ئەندام'
Subscribed: ''
Hearted: ''
Up Next: ''
#Tooltips
Tooltips:
General Settings:
Preferred API Backend: ''
Fallback to Non-Preferred Backend on Failure: ''
Thumbnail Preference: ''
Invidious Instance: ''
Region for Trending: ''
External Link Handling: |
Player Settings:
Force Local Backend for Legacy Formats: ''
Proxy Videos Through Invidious: ''
Default Video Format: ''
Allow DASH AV1 formats: ''
Scroll Playback Rate Over Video Player: ''
Skip by Scrolling Over Video Player: ''
External Player Settings:
External Player: ''
Custom External Player Executable: ''
Ignore Warnings: ''
Custom External Player Arguments: ''
DefaultCustomArgumentsTemplate: "(بنەڕەت: '{defaultCustomArguments}')"
Distraction Free Settings:
Hide Channels: ''
Hide Subscriptions Live: ''
Subscription Settings:
Fetch Feeds from RSS: ''
Fetch Automatically: ''
Privacy Settings:
Remove Video Meta Files: ''
Experimental Settings:
Replace HTTP Cache: ''
SponsorBlock Settings:
UseDeArrowTitles: ''
# Toast Messages
Local API Error (Click to copy): ''
Invidious API Error (Click to copy): ''
Falling back to Invidious API: ''
Falling back to the local API: ''
This video is unavailable because of missing formats. This can happen due to country unavailability.: ''
Subscriptions have not yet been implemented: ''
Unknown YouTube url type, cannot be opened in app: ''
Hashtags have not yet been implemented, try again later: ''
Loop is now disabled: ''
Loop is now enabled: ''
Shuffle is now disabled: ''
Shuffle is now enabled: ''
The playlist has been reversed: ''
Playing Next Video: 'لێدانی ڤیدیۆی دواتر'
Playing Previous Video: 'لێدانی ڤیدیۆی پێشوو'
Playlist will not pause when current video is finished: ''
Playlist will pause when current video is finished: ''
Playing Next Video Interval: ''
Canceled next video autoplay: ''
Default Invidious instance has been set to {instance}: ''
Default Invidious instance has been cleared: ''
'The playlist has ended. Enable loop to continue playing': ''
Age Restricted:
This {videoOrPlaylist} is age restricted: ''
Type:
Channel: 'کەناڵ'
Video: 'ڤیدیۆ'
External link opening has been disabled in the general settings: ''
Downloading has completed: ''
Starting download: ''
Downloading failed: ''
Screenshot Success: ''
Screenshot Error: ''
Hashtag:
Hashtag: ''
This hashtag does not currently have any videos: ''
Yes: 'بەڵێ'
No: 'نەخێر'
Ok: 'باشە'
Go to page: بڕۆ بۆ {page}

View File

@ -346,7 +346,7 @@ Settings:
Hide Chapters: Skrýt kapitoly
Hide Upcoming Premieres: Skrýt nadcházející premiéry
Hide Channels: Skrýt videa z kanálů
Hide Channels Placeholder: Název nebo ID kanálu
Hide Channels Placeholder: ID kanálu
Display Titles Without Excessive Capitalisation: Zobrazit názvy bez nadměrného
použití velkých písmen
Hide Featured Channels: Skrýt doporučené kanály
@ -367,6 +367,12 @@ Settings:
Hide Profile Pictures in Comments: Skrýt profilové obrázky v komentářích
Blur Thumbnails: Rozmazat náhledy
Hide Subscriptions Community: Skrýt komunitu odběratelů
Hide Channels Invalid: Zadané ID kanálu je neplatné
Hide Channels Disabled Message: Některé kanály byly zablokovány pomocí ID a nebyly
zpracovány. Při aktualizaci těchto ID je funkce zablokována
Hide Channels Already Exists: ID kanálu již existuje
Hide Channels API Error: Chyba při načítání uživatele se zadaným ID. Zkontrolujte
prosím, zda je zadané ID správné.
Data Settings:
Data Settings: 'Nastavení dat'
Select Import Type: 'Vybrat typ importu'
@ -606,6 +612,8 @@ Profile:
Profile Filter: Filtr profilu
Profile Settings: Nastavení profilu
Toggle Profile List: Přepnout seznam profilů
Open Profile Dropdown: Otevřít rozbalovací nabídku profilu
Close Profile Dropdown: Zavřít rozbalovací nabídku profilu
Channel:
Subscribe: 'Odebírat'
Unsubscribe: 'Zrušit odběr'
@ -800,6 +808,8 @@ Video:
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Živý
chat není pro tento stream k dispozici. Je možné, že byl vypnut nahrávajícím.
Pause on Current Video: Pozastavit na současném videu
Unhide Channel: Zobrazit kanál
Hide Channel: Skrýt kanál
Videos:
#& Sort By
Sort By:
@ -946,10 +956,9 @@ Tooltips:
Replace HTTP Cache: Zakáže mezipaměť HTTP Electronu a povolí vlastní paměťovou
mezipaměť pro obrázky v paměti. Povede to ke zvýšenému využití RAM.
Distraction Free Settings:
Hide Channels: Zadejte název nebo ID kanálu pro skrytí všech videí, playlistů
a samotného kanálu před zobrazením ve vyhledávání, trendech, nejpopulárnějších
a doporučených. Zadaný název kanálu se musí zcela shodovat a rozlišují se v
něm velká a malá písmena.
Hide Channels: Zadejte ID kanálu pro skrytí všech videí, playlistů a samotného
kanálu před zobrazením ve vyhledávání, trendech, nejpopulárnějších a doporučených.
Zadané ID kanálu se musí zcela shodovat a rozlišují se v něm velká a malá písmena.
Hide Subscriptions Live: Toto nastavení je nadřazeno nastavením celé aplikace
„{appWideSetting}“ v části „{subsection}“ v části „{settingsSection}“
SponsorBlock Settings:
@ -1031,3 +1040,6 @@ Playlist will pause when current video is finished: Po přehrání aktuálního
playlist pozastaven
Playlist will not pause when current video is finished: Po přehrání aktuálního videa
nebude playlist pozastaven
Channel Hidden: Kanál {channel} přidán do filtru kanálů
Go to page: Přejít na {page}
Channel Unhidden: Kanál {channel} odebrán z filtrů kanálů

View File

@ -427,7 +427,7 @@ Settings:
Hide Chapters: Kapitel ausblenden
Hide Upcoming Premieres: Anstehende Premieren ausblenden
Hide Channels: Videos aus Kanälen ausblenden
Hide Channels Placeholder: Kanalname oder Kanal-ID
Hide Channels Placeholder: Kanal-ID
Display Titles Without Excessive Capitalisation: Titel ohne übermäßige Großschreibung
anzeigen
Hide Channel Playlists: Kanal-Wiedergabelisten ausblenden
@ -448,6 +448,12 @@ Settings:
Blur Thumbnails: Vorschaubilder unscharf machen
Hide Profile Pictures in Comments: Profilbilder in den Kommentaren ausblenden
Hide Subscriptions Community: Abo-Gemeinschaft ausblenden
Hide Channels Invalid: Kanal-ID war ungültig
Hide Channels Disabled Message: Einige Kanäle wurden mit ID gesperrt und nicht
verarbeitet. Die Funktion wird blockiert, während diese IDs aktualisieren
Hide Channels Already Exists: Kanal-ID bereits vorhanden
Hide Channels API Error: Fehler beim Abrufen von Benutzern mit der bereitgestellten
ID. Bitte überprüfen Sie erneut, ob der Ausweis korrekt ist.
The app needs to restart for changes to take effect. Restart and apply change?: Die
App muss neu gestartet werden, damit die Änderungen wirksam werden. Neu starten
und Änderung übernehmen?
@ -1021,11 +1027,10 @@ Tooltips:
und aktiviert einen benutzerdefinierten In-Memory-Image-Cache. Dies führt zu
einer erhöhten RAM-Auslastung.
Distraction Free Settings:
Hide Channels: Gebe einen Kanalnamen oder eine Kanal-ID ein, um zu verhindern,
dass alle Videos, Wiedergabelisten und der Kanal selbst in der Suche, den Trends,
den beliebtesten und den empfohlenen Videos angezeigt werden. Der eingegebene
Kanalname muss vollständig übereinstimmen und es wird zwischen Groß- und Kleinschreibung
unterschieden.
Hide Channels: Gebe eine Kanal-ID ein, um zu verhindern, dass alle Videos, Wiedergabelisten
und der Kanal selbst in der Suche, den Trends, den beliebtesten und den empfohlenen
Videos angezeigt werden. Die eingegebene Kanal-ID muss vollständig übereinstimmen
und es wird zwischen Groß- und Kleinschreibung unterschieden.
Hide Subscriptions Live: Diese Einstellung wird durch die App-weite Einstellung
„{appWideSetting}“ im Abschnitt „{subsection}“ der „{settingsSection}“ außer
Kraft gesetzt

View File

@ -161,6 +161,7 @@ Settings:
Middle: 'Μέση'
End: 'Τέλος'
Hidden: Κρυμμένο
Blur: ''
'Invidious Instance (Default is https://invidious.snopyta.org)': 'Διακομιστής
Invidious (προεπιλογή https://invidious.snopyta.org)'
Region for Trending: 'Περιοχή που καθορίζει την καρτέλα των τάσεων'

View File

@ -31,6 +31,7 @@ Close: Close
Back: Back
Forward: Forward
Open New Window: Open New Window
Go to page: Go to {page}
Version {versionNumber} is now available! Click for more details: Version {versionNumber} is now available! Click
for more details
@ -149,6 +150,7 @@ History:
Settings:
# On Settings Page
Settings: Settings
Expand All Settings Sections: Expand All Settings Sections
The app needs to restart for changes to take effect. Restart and apply change?: The
app needs to restart for changes to take effect. Restart and apply change?
General Settings:
@ -341,6 +343,7 @@ Settings:
Fetch Feeds from RSS: Fetch Feeds from RSS
Manage Subscriptions: Manage Subscriptions
Fetch Automatically: Fetch Feed Automatically
Only Show Latest Video for Each Channel: Only Show Latest Video for Each Channel
Distraction Free Settings:
Distraction Free Settings: Distraction Free Settings
Sections:
@ -368,7 +371,11 @@ Settings:
Hide Sharing Actions: Hide Sharing Actions
Hide Chapters: Hide Chapters
Hide Channels: Hide Videos From Channels
Hide Channels Placeholder: Channel Name or ID
Hide Channels Disabled Message: Some channels were blocked using ID and weren't processed. Feature is blocked while those IDs are updating
Hide Channels Placeholder: Channel ID
Hide Channels Invalid: Channel ID provided was invalid
Hide Channels API Error: Error retrieving user with the ID provided. Please check again if the ID is correct.
Hide Channels Already Exists: Channel ID already exists
Hide Featured Channels: Hide Featured Channels
Hide Channel Playlists: Hide Channel Playlists
Hide Channel Community: Hide Channel Community
@ -516,6 +523,9 @@ Profile:
Profile Manager: Profile Manager
Create New Profile: Create New Profile
Edit Profile: Edit Profile
Edit Profile Name: Edit Profile Name
Create Profile Name: Create Profile Name
Profile Name: Profile Name
Color Picker: Color Picker
Custom Color: Custom Color
Profile Preview: Profile Preview
@ -550,6 +560,8 @@ Profile:
Are you sure you want to delete the selected channels? This will not delete the channel from any other profile.: Are
you sure you want to delete the selected channels? This will not delete the channel
from any other profile.
Close Profile Dropdown: Close Profile Dropdown
Open Profile Dropdown: Open Profile Dropdown
#On Channel Page
Channel:
Subscribe: Subscribe
@ -627,6 +639,8 @@ Video:
Copy YouTube Channel Link: Copy YouTube Channel Link
Open Channel in Invidious: Open Channel in Invidious
Copy Invidious Channel Link: Copy Invidious Channel Link
Hide Channel: Hide Channel
Unhide Channel: Show Channel
Views: Views
Loop Playlist: Loop Playlist
Shuffle Playlist: Shuffle Playlist
@ -872,8 +886,8 @@ Tooltips:
you want to be passed to the external player.
DefaultCustomArgumentsTemplate: "(Default: '{defaultCustomArguments}')"
Distraction Free Settings:
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists and the channel itself from appearing in search, trending, most popular and recommended.
The channel name entered must be a complete match and is case sensitive.
Hide Channels: Enter a channel ID to hide all videos, playlists and the channel itself from appearing in search, trending, most popular and recommended.
The channel ID entered must be a complete match and is case sensitive.
Hide Subscriptions Live: 'This setting is overridden by the app-wide "{appWideSetting}" setting, in the "{subsection}" section of the "{settingsSection}"'
Subscription Settings:
Fetch Feeds from RSS: When enabled, FreeTube will use RSS instead of its default
@ -930,6 +944,8 @@ Starting download: 'Starting download of "{videoTitle}"'
Downloading failed: 'There was an issue downloading "{videoTitle}"'
Screenshot Success: Saved screenshot as "{filePath}"
Screenshot Error: Screenshot failed. {error}
Channel Hidden: '{channel} added to channel filter'
Channel Unhidden: '{channel} removed from channel filter'
Hashtag:
Hashtag: Hashtag

View File

@ -427,7 +427,7 @@ Settings:
Hide Sharing Actions: Hide sharing actions
Hide Chapters: Hide chapters
Hide Channels: Hide videos from channels
Hide Channels Placeholder: Channel name or ID
Hide Channels Placeholder: Channel ID
Display Titles Without Excessive Capitalisation: Display Titles Without Excessive
Capitalisation
Hide Featured Channels: Hide featured channels
@ -447,6 +447,12 @@ Settings:
Hide Subscriptions Videos: Hide subscriptions videos
Hide Subscriptions Community: Hide subscriptions community
Hide Profile Pictures in Comments: Hide profile pictures in comments
Hide Channels Invalid: Channel ID provided was invalid
Hide Channels Disabled Message: Some channels were blocked using ID and weren't
processed. Feature is blocked while those IDs are updating
Hide Channels Already Exists: Channel ID already exists
Hide Channels API Error: Error retrieving user with the ID provided. Please check
again if the ID is correct.
The app needs to restart for changes to take effect. Restart and apply change?: The
app needs to restart for changes to take effect. Do you want to restart and apply
the changes?
@ -957,9 +963,9 @@ Tooltips:
Replace HTTP Cache: Disables Electron's disk-based HTTP cache and enables a custom
in-memory image cache. Will lead to increased RAM usage.
Distraction Free Settings:
Hide Channels: Enter a channel name or channel ID to hide all videos, playlists
and the channel itself from appearing in search, trending, most popular and
recommended. The channel name entered must be a complete match and is case sensitive.
Hide Channels: Enter a channel ID to hide all videos, playlists and the channel
itself from appearing in search, trending, most popular and recommended. The
channel ID entered must be a complete match and is case sensitive.
Hide Subscriptions Live: This setting is overridden by the app-wide {appWideSetting}
setting, in the {subsection} section of the {settingsSection}
SponsorBlock Settings:

View File

@ -324,6 +324,8 @@ Settings:
Fetch Feeds from RSS: 'Recuperar suministros desde RSS'
Manage Subscriptions: 'Gestionar suscripciones'
Fetch Automatically: Obtener los feed automáticamente
Only Show Latest Video for Each Channel: Mostrar solo los últimos vídeos de cada
canal
Data Settings:
Data Settings: 'Datos'
Select Import Type: 'Seleccionar tipo de importación'
@ -420,7 +422,7 @@ Settings:
Hide Chapters: Ocultar los capítulos
Hide Upcoming Premieres: Ocultar los próximos estrenos
Hide Channels: Ocultar los vídeos de los canales
Hide Channels Placeholder: Nombre o ID del canal
Hide Channels Placeholder: ID del canal
Display Titles Without Excessive Capitalisation: Mostrar títulos sin demasiadas
mayúsculas
Hide Featured Channels: Ocultar los canales recomendados
@ -441,6 +443,12 @@ Settings:
Hide Profile Pictures in Comments: Ocultar las fotos del perfil en los comentarios
Blur Thumbnails: Difuminar las miniaturas
Hide Subscriptions Community: Ocultar las suscripciones a la comunidad
Hide Channels Invalid: El ID del canal proporcionado no es válido
Hide Channels Disabled Message: Algunos canales se bloquearon por ID y no se procesaron.
La función está bloqueada mientras se actualizan esos ID
Hide Channels Already Exists: El ID del canal ya existe
Hide Channels API Error: Error al recuperar el usuario con el ID proporcionado.
Por favor, compruebe de nuevo si el ID es correcto.
The app needs to restart for changes to take effect. Restart and apply change?: ¿Quieres
reiniciar FreeTube ahora para aplicar los cambios?
Proxy Settings:
@ -512,6 +520,7 @@ Settings:
Enter Password To Unlock: Introduce la contraseña para desbloquear los ajustes
Password Incorrect: Contraseña incorrecta
Unlock: Desbloquear
Expand All Settings Sections: Expandir todas las secciones de los ajustes
About:
#On About page
About: 'Acerca de'
@ -614,6 +623,11 @@ Profile:
Profile Filter: Filtro de perfil
Profile Settings: Ajustes del perfil
Toggle Profile List: Alternar la lista de los perfiles
Open Profile Dropdown: Abrir el desplegable del Perfil
Close Profile Dropdown: Cerrar el desplegable del Perfil
Profile Name: Nombre del perfil
Edit Profile Name: Editar el nombre del perfil
Create Profile Name: Crear un nombre para el perfil
Channel:
Subscribe: 'Suscribirse'
Unsubscribe: 'Anular suscripción'
@ -821,6 +835,8 @@ Video:
chat en vivo no está disponible para esta transmisión. Tal vez estaba deshabilitado
antes de la retransmisión.
Pause on Current Video: Pausa en el vídeo actual
Unhide Channel: Mostrar el canal
Hide Channel: Ocultar el canal
Videos:
#& Sort By
Sort By:
@ -996,10 +1012,10 @@ Tooltips:
una caché para la imagen en la memoria personalizada. Esto aumentará el uso
de la memoria RAM.
Distraction Free Settings:
Hide Channels: Ingresa un nombre del canal o un ID del canal para ocultar todos
los videos, listas de reproducción y el propio canal para que no aparezcan en
la búsqueda, tendencias, más populares y recomendados. El nombre del canal ingresado
debe ser una coincidencia completa y distinguir entre mayúsculas y minúsculas.
Hide Channels: Introduzca un ID del canal para ocultar todos los vídeos, listas
de reproducción y el propio canal para que no aparezcan en las búsquedas, tendencias,
más populares y recomendados. El ID del canal introducido debe coincidir completamente
y se debe distinguir entre mayúsculas y minúsculas.
Hide Subscriptions Live: Esta configuración se reemplaza por la configuración
«{appWideSetting}» de toda la aplicación, en la sección «{subsection}» de «{settingsSection}»
SponsorBlock Settings:
@ -1066,3 +1082,6 @@ Playlist will pause when current video is finished: La lista de reproducción se
cuando termine el vídeo actual
Playlist will not pause when current video is finished: La lista de reproducción no
se detendrá cuando termine el vídeo actual
Go to page: Ir a la {page}
Channel Hidden: '{channel} añadido al filtro de canales'
Channel Unhidden: '{channel} eliminado del filtro de canales'

View File

@ -391,7 +391,7 @@ Settings:
Hide Upcoming Premieres: Peida tulevased esilinastused
Hide Chapters: Peida peatükid
Hide Channels: Peida kanalites leiduvad videod
Hide Channels Placeholder: Kanali nimi või tunnus
Hide Channels Placeholder: Kanali tunnus
Display Titles Without Excessive Capitalisation: Näita pealkirju ilma liigsete
suurtähtedeta
Sections:
@ -412,6 +412,13 @@ Settings:
Hide Profile Pictures in Comments: Peida kommentaaride profiilipildid
Blur Thumbnails: Hägusta pisipildid
Hide Subscriptions Community: Peida tellijate loend
Hide Channels Invalid: Sisestatud kanali tunnus on vigane
Hide Channels Disabled Message: Mõned kanalid on blokeeritud nende tunnuste alusel
ja nende andmeid ei töödelda. See funktsionaalsus pole kasutusel, kui nendes
kanalites toimub muutusi
Hide Channels Already Exists: Selline kanali tunnus juba on sul kirjas
Hide Channels API Error: Selle kanali tunnus alusel andmete või kasutaja andmete
laadimine ei õnnestunud. Palun kontrolli, et kanali tunnus oleks õige.
Proxy Settings:
Error getting network information. Is your proxy configured properly?: Võrguteavet
ei õnnestu leida. Kas sa oled puhverserveri ikka korralikult seadistanud?
@ -757,6 +764,8 @@ Video:
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Otsevestlus
pole selle videovoo puhul saadaval. Võib-olla on üleslaadija vestluse keelanud.
Pause on Current Video: Peata hetkel esitatav video
Unhide Channel: Näita kanalit
Hide Channel: Peida kanal
Videos:
#& Sort By
Sort By:
@ -919,10 +928,10 @@ Tooltips:
Skip by Scrolling Over Video Player: MPV-stiilis video läbilappamiseks kasuta
hiire ratast.
Distraction Free Settings:
Hide Channels: Sisesta kanali nimi või kanali ID, et kõik videod, esitusloendid
ja kanal ise ei oleks nähtav otsingus, soovitatavate videote, populaarsete videote
ja populaarsust koguvate videote vaates. Sisestatud kanali nimi peab otsingule
vastama täielikult ja on tõstutundlik.
Hide Channels: Sisesta kanali tunnus, et kõik videod, esitusloendid ja kanal ise
ei oleks nähtav otsingus, soovitatavate videote, populaarsete videote ja populaarsust
koguvate videote vaates. Sisestatud kanali tunnus peab otsingule vastama täpselt
ja on tõstutundlik.
Hide Subscriptions Live: Selle seadistuse tühistab rakenduseülene „{appWideSetting}“
seadistus „{subsection}“/„{settingsSection}“
Experimental Settings:
@ -984,3 +993,6 @@ Playlist will pause when current video is finished: Hetkel mängiva video lõppe
esitusloendi esitamine peatub
Playlist will not pause when current video is finished: Hetkel mängiva video lõppemisel
esitusloendi esitamine jätkub
Channel Hidden: '{channel} on lisatud kanalite filtrisse'
Go to page: 'Ava leht: {page}'
Channel Unhidden: '{channel} on eemaldatud kanalite filtrist'

View File

@ -439,7 +439,7 @@ Settings:
Hide Chapters: Masquer les chapitres
Hide Upcoming Premieres: Cacher les avant-premières à venir
Hide Channels: Masquer les vidéos des chaînes
Hide Channels Placeholder: Nom ou identifiant de la chaîne
Hide Channels Placeholder: Identifiant de la chaîne
Display Titles Without Excessive Capitalisation: Afficher les titres sans majuscules
excessives
Hide Channel Playlists: Masquer les listes de lecture des chaînes
@ -460,6 +460,13 @@ Settings:
Blur Thumbnails: Flouter les miniatures
Hide Profile Pictures in Comments: Masquer les photos de profil dans les commentaires
Hide Subscriptions Community: Masquer les communautés abonnées
Hide Channels Invalid: L'identifiant de la chaîne fourni n'est pas valide
Hide Channels Disabled Message: Certaines chaînes ont été bloquées à l'aide d'un
identifiant et n'ont pas été traitées. La fonctionnalité est bloquée tant que
ces identifiants sont mis à jour
Hide Channels Already Exists: L'identifiant de la chaîne existe déjà
Hide Channels API Error: Erreur de récupération de l'utilisateur portant l'identifiant
fourni. Veuillez vérifier à nouveau si l'ID est correct.
The app needs to restart for changes to take effect. Restart and apply change?: L'application
doit être redémarrée pour que les changements prennent effet. Redémarrer et appliquer
les changements ?
@ -811,6 +818,8 @@ Video:
chat en direct n'est pas disponible pour ce flux. Il a peut-être été désactivé
par le téléchargeur.
Pause on Current Video: Pause sur la vidéo en cours
Hide Channel: Cacher la chaîne
Unhide Channel: Rétablir la chaîne
Videos:
#& Sort By
Sort By:
@ -956,6 +965,8 @@ Profile:
Profile Filter: Filtre de profil
Profile Settings: Paramètres du profil
Toggle Profile List: Afficher la liste des profils
Open Profile Dropdown: Ouvrir la liste déroulante du profil
Close Profile Dropdown: Fermer la liste déroulante du profil
The playlist has been reversed: La liste de lecture a été inversée
A new blog is now available, {blogTitle}. Click to view more: Un nouveau billet est
maintenant disponible, {blogTitle}. Cliquez pour en savoir plus
@ -1038,11 +1049,10 @@ Tooltips:
un cache d'image personnalisé en mémoire. Ceci entraînera une augmentation de
l'utilisation de la mémoire vive.
Distraction Free Settings:
Hide Channels: Entrez un nom de chaîne ou un identifiant de chaîne pour empêcher
toutes les vidéos, les listes de lecture et la chaîne elle-même d'apparaître
dans les recherches, dans les catégories Tendances, Plus populaires et Recommandés.
Le nom de la chaîne entré doit correspondre exactement et est sensible à la
casse.
Hide Channels: Entrez un identifiant de chaîne pour empêcher toutes les vidéos,
les listes de lecture et la chaîne elle-même d'apparaître dans les recherches,
dans les catégories Tendances, Plus populaires et Recommandés. L'identifiant
de la chaîne entré doit correspondre exactement et est sensible aux majuscules.
Hide Subscriptions Live: Ce paramètre est remplacé par le paramètre « {appWideSetting} »
applicable à l'ensemble de l'application, dans la section « {subsection} » de
la section « {settingsSection} »
@ -1112,3 +1122,6 @@ Playlist will pause when current video is finished: La liste de lecture se met e
pause lorsque la vidéo en cours est terminée
Playlist will not pause when current video is finished: La liste de lecture ne se
met pas en pause lorsque la vidéo en cours est terminée
Go to page: Aller à {page}
Channel Hidden: '{channel} ajouté au filtre de chaîne'
Channel Unhidden: '{channel} retiré du filtre de chaîne'

View File

@ -155,6 +155,7 @@ Settings:
Middle: 'Sredina'
End: 'Kraj'
Hidden: Skriveno
Blur: Neoštro
'Invidious Instance (Default is https://invidious.snopyta.org)': 'Invidious primjerak
(standardno se koristi https://invidious.snopyta.org)'
Region for Trending: 'Regija za videa u trendu'
@ -418,7 +419,7 @@ Settings:
Hide Chapters: Sakrij poglavlja
Hide Upcoming Premieres: Sakrij nadolazeće premijere
Hide Channels: Sakrij videa iz kanala
Hide Channels Placeholder: Ime kanala ili ID
Hide Channels Placeholder: ID kanala
Display Titles Without Excessive Capitalisation: Prikaži naslove bez pretjeranog
korištenja velikih slova
Hide Featured Channels: Sakrij istaknute kanale
@ -439,6 +440,12 @@ Settings:
Blur Thumbnails: Zamuti minijature
Hide Profile Pictures in Comments: Sakrij slike profila u komentarima
Hide Subscriptions Community: Sakrij pretplate zajednice
Hide Channels Invalid: Navedeni ID kanala nije bio ispravan
Hide Channels Disabled Message: Neki su kanali blokirani upotrebom ID-a i nisu
obrađeni. Funkcija je blokirana tijekom aktualiziranje tih ID-ova
Hide Channels Already Exists: ID kanala već postoji
Hide Channels API Error: Greška pri dohvaćanju korisnika s navedenim ID-om. Ponovo
provjeri točnost ID-a.
The app needs to restart for changes to take effect. Restart and apply change?: Promjene
će se primijeniti nakon ponovnog pokeretanja programa. Ponovo pokrenuti program?
Proxy Settings:
@ -987,9 +994,9 @@ Tooltips:
Replace HTTP Cache: Deaktivira Electronovu HTTP predmemoriju temeljenu na disku
i aktivira prilagođenu predmemoriju slika u memoriji. Povećava korištenje RAM-a.
Distraction Free Settings:
Hide Channels: Upiši ime kanala ili ID kanala za skrivanje svih videa, zbirki
kao i samog kanala u pretrazi, trendovima popularnim i preporučenim. Upisano
ime kanala se mora potpuno poklapati i razlikuje velika i mala slova.
Hide Channels: Upiši ID kanala za skrivanje svih videa, zbirki kao i samog kanala
u prikazu pretrage, trendova, najpopularniji i preporučeni. Upisani ID kanala
se mora potpuno poklapati i razlikuje velika i mala slova.
Hide Subscriptions Live: Ovu postavku nadjačava aplikacijska postavka „{appWideSetting}”,
u odjeljku „{subsection}” u „{settingsSection}”
SponsorBlock Settings:

View File

@ -168,6 +168,7 @@ Settings:
Middle: 'Középső'
End: 'Vég'
Hidden: Rejtett
Blur: Elhomályosítás
'Invidious Instance (Default is https://invidious.snopyta.org)': 'Invidious példány
(Alapértelmezés: https://invidious.snopyta.org)'
Region for Trending: 'Népszerű területe'
@ -427,7 +428,7 @@ Settings:
Hide Chapters: Fejezetek elrejtése
Hide Upcoming Premieres: Közelgő első előadások elrejtése
Hide Channels: Videók elrejtése a csatornákból
Hide Channels Placeholder: Csatorna neve vagy azonosítója
Hide Channels Placeholder: Csatornaazonosító
Display Titles Without Excessive Capitalisation: Címek megjelenítése túlzott nagybetűk
nélkül
Hide Featured Channels: Kiemelt csatornák elrejtése
@ -448,6 +449,13 @@ Settings:
Blur Thumbnails: Indexkép elhomályosítása
Hide Profile Pictures in Comments: Profilképek elrejtése a megjegyzésekben
Hide Subscriptions Community: Közösségi feliratkozások elrejtése
Hide Channels Invalid: Érvénytelen a megadott csatornaazonosító
Hide Channels Disabled Message: Egyes csatornákat letiltottak az azonosító használatával,
és nem dolgozták fel őket. A jellemző le van tiltva az azonosítók frissítése
közben
Hide Channels Already Exists: Már létezik a csatornaazonosító
Hide Channels API Error: Hiba történt a megadott azonosítóval rendelkező felhasználó
lekérésekor. Kérjük, ellenőrizze még egyszer, hogy helyes-e az azonosító.
The app needs to restart for changes to take effect. Restart and apply change?: Az
alkalmazásnak újra kell indulnia, hogy a változtatások életbe lépjenek. Indítsa
újra és alkalmazza a módosítást?
@ -989,10 +997,10 @@ Tooltips:
Replace HTTP Cache: Letiltja az Electron lemez alapú HTTP-gyorsítótárát, és engedélyezi
az egyéni memórián belüli képgyorsítótárat. Megnövekedett RAM-használathoz vezet.
Distraction Free Settings:
Hide Channels: Adja meg a csatorna nevét vagy csatornaazonosítóját, hogy elrejtse
az összes videót, lejátszási listát és magát a csatornát, hogy ne jelenjen meg
a keresésben, illetve a felkapott, legnépszerűbb és legajánlottabb. A megadott
csatornanévnek teljes egyezésnek kell lennie, és megkülönbözteti a kis- és nagybetűket.
Hide Channels: Csatornaazonosító megadása, hogy elrejtse az összes videót, lejátszási
listát és magát a csatornát, nehogy megjelenjen a keresésben, a felkapott, legnépszerűbb
és legnépszerűbb. A megadott csatornaazonosítónak teljes egyezésnek kell lennie,
és megkülönbözteti a kis- és nagybetűket.
Hide Subscriptions Live: Ezt a beállítást felülírja az alkalmazásszintű „{appWideSetting}”
beállítás a(z) „{settingsSection}” „{subsection}” szakaszában
SponsorBlock Settings:

View File

@ -331,6 +331,8 @@ Settings:
How do I import my subscriptions?: 'Come importo le mie iscrizioni?'
Fetch Feeds from RSS: Scarica gli aggiornamenti dai flussi RSS
Fetch Automatically: Recupera i feed automaticamente
Only Show Latest Video for Each Channel: Mostra solo il video più recente per
ciascun canale
Advanced Settings:
Advanced Settings: 'Impostazioni Avanzate'
Enable Debug Mode (Prints data to the console): 'Abilità modalità Sviluppatore
@ -428,7 +430,7 @@ Settings:
Hide Chapters: Nascondi i capitoli
Hide Upcoming Premieres: Nascondi le prossime Première
Hide Channels: Nascondi i video dai canali
Hide Channels Placeholder: Nome o ID del canale
Hide Channels Placeholder: ID del canale
Display Titles Without Excessive Capitalisation: Visualizza i titoli senza un
uso eccessivo di maiuscole
Hide Featured Channels: Nascondi i canali in evidenza
@ -449,6 +451,13 @@ Settings:
Hide Profile Pictures in Comments: Nascondi le immagini del profilo nei commenti
Blur Thumbnails: Miniature sfocate
Hide Subscriptions Community: Nascondi la comunità di iscritti
Hide Channels Invalid: L'ID canale fornito non è valido
Hide Channels Disabled Message: Alcuni canali sono stati bloccati utilizzando
l'ID e non sono stati elaborati. La funzionalità è bloccata durante l'aggiornamento
di tali ID
Hide Channels Already Exists: L'ID canale esiste già
Hide Channels API Error: Errore durante il recupero dell'utente con l'ID fornito.
Controlla di nuovo se l'ID è corretto.
The app needs to restart for changes to take effect. Restart and apply change?: L'app
deve essere riavviata affinché le modifiche abbiano effetto. Riavviare e applicare
la modifica?
@ -508,10 +517,10 @@ Settings:
Experimental Settings:
Replace HTTP Cache: Sostituisci la cache HTTP
Experimental Settings: Impostazioni sperimentali
Warning: Queste impostazioni sono sperimentali, causano arresti anomali se abilitate.
Warning: Queste Impostazioni sono sperimentali, causano arresti anomali se abilitate.
Si consiglia prima di fare un backup. Utilizzare a proprio rischio!
Password Dialog:
Enter Password To Unlock: Inserisci la password per sbloccare le impostazioni
Enter Password To Unlock: Inserisci la password per sbloccare le Impostazioni
Password Incorrect: Password non corretta
Password: Password
Unlock: Sblocca
@ -519,8 +528,9 @@ Settings:
Set Password: Imposta password
Password Settings: Impostazioni password
Set Password To Prevent Access: Imposta una password per impedire l'accesso alle
impostazioni
Impostazioni
Remove Password: Rimuovi password
Expand All Settings Sections: Espandi tutte le sezioni delle Impostazioni
About:
#On About page
About: 'Informazioni'
@ -787,6 +797,8 @@ Video:
chat dal vivo non è disponibile per questo video. Potrebbe essere stata disattivata
dall'autore del caricamento.
Pause on Current Video: Pausa sul video attuale
Unhide Channel: Mostra canale
Hide Channel: Nascondi canale
Videos:
#& Sort By
Sort By:
@ -935,6 +947,11 @@ Profile:
Profile Filter: Filtro del profilo
Profile Settings: Impostazioni dei profili
Toggle Profile List: Attiva/disattiva elenco profili
Open Profile Dropdown: Apri il menu a discesa del profilo
Close Profile Dropdown: Chiudi il menu a discesa del profilo
Profile Name: Nome del profilo
Edit Profile Name: Modifica il nome del profilo
Create Profile Name: Crea un nome per il profilo
This video is unavailable because of missing formats. This can happen due to country unavailability.: Questo
video non è disponibile a causa di alcuni formati mancanti. Questo può succedere
in caso di mancata disponibilità della nazione.
@ -955,7 +972,7 @@ Tooltips:
o indietro per controllare la velocità di riproduzione. Tieni premuto il tasto
CTRL (Command su Mac) e clicca con il tasto sinistro del mouse per tornare rapidamente
alla velocità di riproduzione predefinita (1x a meno che non sia stata cambiata
nelle impostazioni).
nelle Impostazioni).
Skip by Scrolling Over Video Player: Usa la rotella di scorrimento per saltare
il video, in stile MPV.
Allow DASH AV1 formats: I formati DASH AV1 possono avere un aspetto migliore dei
@ -994,7 +1011,7 @@ Tooltips:
un percorso personalizzato può essere impostato qui.
External Player: Scegliendo un lettore esterno sarà visualizzata sulla miniatura
un'icona per aprire il video nel lettore esterno (se la playlist lo supporta).
Attenzione, le impostazioni Invidious non influiscono sui lettori esterni.
Attenzione, le Impostazioni Invidious non influiscono sui lettori esterni.
DefaultCustomArgumentsTemplate: '(Predefinito: {defaultCustomArguments})'
Privacy Settings:
Remove Video Meta Files: Se abilitato, quando chiuderai la pagina di riproduzione
@ -1005,9 +1022,9 @@ Tooltips:
una cache di immagini in memoria personalizzata. Comporta un aumento dell'utilizzo
della RAM.
Distraction Free Settings:
Hide Channels: Inserisci il nome o l'ID di un canale per impedire che tutti i
video, le playlist e il canale stesso vengano visualizzati nelle ricerche, tendenze,
più popolari e consigliati. Il nome del canale inserito deve avere una corrispondenza
Hide Channels: Inserisci l'ID di un canale per impedire che tutti i video, le
playlist e il canale stesso vengano visualizzati nelle ricerche, tendenze, più
popolari e consigliati. Il nome del canale inserito deve avere una corrispondenza
completa e fa distinzione tra maiuscole e minuscole.
Hide Subscriptions Live: Questa impostazione è sovrascritta dall'impostazione
"{appWideSetting}" a livello di app, nella sezione "{subsection}" di "{settingsSection}"
@ -1031,7 +1048,7 @@ Unknown YouTube url type, cannot be opened in app: Tipo di URL di YouTube sconos
Search Bar:
Clear Input: Pulisci ricerca
External link opening has been disabled in the general settings: L'apertura dei link
esterni è stata disabilitata nelle impostazioni generali
esterni è stata disabilitata nelle Impostazioni generali
Are you sure you want to open this link?: Sei sicuro di voler aprire questo link?
Downloading has completed: 'Il download di "{videoTitle}" è terminato'
Starting download: Avvio del download di "{videoTitle}"
@ -1075,3 +1092,6 @@ Playlist will pause when current video is finished: La playlist verrà messa in
al termine del video attuale
Playlist will not pause when current video is finished: La playlist non verrà messa
in pausa al termine del video attuale
Channel Hidden: '{channel} aggiunto al filtro canali'
Go to page: Vai a {page}
Channel Unhidden: '{channel} rimosso dal filtro canali'

View File

@ -39,7 +39,7 @@ Global:
Counts:
Video Count: 1 件の動画 | {count} 件の動画
Subscriber Count: 1 登録者 | {count} 登録者
View Count: 1 表示 | {count} 回表示
View Count: 1 視聴 | {count} 回視聴
Watching Count: 1 人が視聴中 | {count} 人が視聴中
Channel Count: 1 チャンネル | {count} チャンネル
Search / Go to URL: '検索 / URL の表示'
@ -145,6 +145,7 @@ Settings:
Middle: '中間'
End: '終了'
Hidden: 非表示
Blur: ぼかし
'Invidious Instance (Default is https://invidious.snopyta.org)': '接続先の Invidious
サーバー(初期値は https://invidious.snopyta.org'
Region for Trending: '急上昇の地域設定'
@ -387,7 +388,7 @@ Settings:
Hide Chapters: チャプターの非表示
Hide Upcoming Premieres: 今後のプレミア公開を非表示
Hide Channels: チャンネルから動画を非表示
Hide Channels Placeholder: チャネル名または ID
Hide Channels Placeholder: チャネル ID
Display Titles Without Excessive Capitalisation: 英語の大文字化を控えたタイトル表示
Hide Channel Playlists: チャンネル再生リストの非表示
Hide Channel Community: チャンネル コミュニティの非表示
@ -407,6 +408,10 @@ Settings:
Hide Profile Pictures in Comments: コメントでプロフィール写真を隠す
Blur Thumbnails: サムネイルをぼかす
Hide Subscriptions Community: チャンネルの購読者リストの非公開
Hide Channels Invalid: 提供チャンネル ID 無効
Hide Channels Disabled Message: 一部のチャンネルが ID を使用してブロックされ、処理されませんでした。これらの ID が更新されている間、機能はブロックされます
Hide Channels Already Exists: 既に存在するチャンネル ID
Hide Channels API Error: 提供された ID のユーザーを取得できませんでした。ID が正しいかどうかをもう一度確認してください。
The app needs to restart for changes to take effect. Restart and apply change?: 変更の反映には、アプリの再起動が必要です。再起動して変更を適用しますか?
Proxy Settings:
Error getting network information. Is your proxy configured properly?: ネットワーク情報の取得中にエラーが発生しました。プロキシーを正しく設定してますか?
@ -460,7 +465,8 @@ Settings:
Experimental Settings:
Replace HTTP Cache: HTTP キャッシュの置換
Experimental Settings: 実験中の設定
Warning: この設定項目は実験中であるため、有効にするとクラッシュすることがあります。必ずバックアップを作成してください。使用は自己責任でお願いします!
Warning:
これらの設定は実験的なものであり、有効にするとアプリのクラッシュを引き起こす恐れがあります。バックアップをとっておくことを強くお勧めします。自己責任で使用してください!
Password Settings:
Password Settings: パスワード設定
Set Password To Prevent Access: 設定の変更にはパスワードを設定とする
@ -886,8 +892,8 @@ Tooltips:
Replace HTTP Cache: Electron のディスクに基づく HTTP キャッシュを無効化し、メモリ内で独自の画像キャッシュを使用します。このことにより
RAM の使用率は増加します。
Distraction Free Settings:
Hide Channels: チャンネル名またはチャンネル ID
を入力すると、すべてのビデオ、再生リスト、チャンネル自体が検索や人気、およびおすすめに表示されなくなります。入力するチャンネル名は、大文字と小文字を区別するので完全に一致させてください。
Hide Channels: チャンネル ID を入力すると、すべてのビデオ、再生リスト、チャンネル自体が検索や人気、およびおすすめに表示されなくなります。入力するチャンネル
ID は、大文字と小文字を区別するので完全に一致させてください。
Hide Subscriptions Live: この設定は、アプリ全体の "{appWideSetting}" 設定により上書きされます。"{settingsSection}"
項目の "{subsection}" にあります
SponsorBlock Settings:

View File

@ -1,5 +1,5 @@
# Put the name of your locale in the same language
Locale Name: 'kur-ckb'
Locale Name: 'کوردی ناوەڕاست'
FreeTube: 'فریتیوب'
# Currently on Subscriptions, Playlists, and History
'This part of the app is not ready yet. Come back later when progress has been made.': >-
@ -7,20 +7,20 @@ FreeTube: 'فریتیوب'
رویداوە.
# Webkit Menu Bar
File: 'فایل'
Quit: 'چونەدەرەوە'
Edit: 'دەستکاریکردن'
Undo: 'گەڕانەوە'
File: 'پەڕگە'
Quit: 'دەرچوون'
Edit: 'دەستکاری'
Undo: 'پووچکردنەوە'
Redo: 'هێنانەوە'
Cut: 'بڕین'
Copy: 'کۆپی'
Paste: 'پەیست'
Copy: 'لەبەرگرتنەوە'
Paste: 'لکاندن'
Delete: 'سڕینەوە'
Select all: 'دیاریکردنی هەمووی'
Reload: 'دوبارە دابەزاندن'
Force Reload: 'دوباری دابەزاندی بەهێز'
Toggle Developer Tools: 'ئەدەواتەکانی دیڤیڵۆپەر بەردەست بخە'
Actual size: 'گەورەیی راستی'
Reload: 'بارکردنەوە'
Force Reload: 'بارکردنەوەی بەزۆر'
Toggle Developer Tools: 'زامنی ئامرازەکانی گەشەپێدەر'
Actual size: 'قەبارەی ڕاستەقینە'
Zoom in: 'زووم کردنە ناوەوە'
Zoom out: 'زووم کردنە دەرەوە'
Toggle fullscreen: 'شاشەکەت پرکەرەوە'
@ -30,9 +30,9 @@ Close: 'داخستن'
Back: 'گەڕانەوە'
Forward: 'چونەپێشەوە'
Version {versionNumber} is now available! Click for more details: 'ڤێرژنی {versionNumber}
ئێستا بەردەستە! کلیک بکە بۆ زانیاری زیاتر'
Download From Site: 'دایبەزێنە لە سایتەکەوە'
Version {versionNumber} is now available! Click for more details: 'ئێستا وەشانی {versionNumber}
بەردەستە..بۆ زانیاری زۆرتر کرتە بکە'
Download From Site: 'لە وێبگەوە دایگرە'
A new blog is now available, {blogTitle}. Click to view more: 'بڵۆگێکی نوێ بەردەستە،
{blogTitle}. کلیک بکە بۆ بینینی زیاتر'
@ -190,3 +190,8 @@ Profile:
Removed {profile} from your profiles: 'سڕاوەتەوە لە پرۆفایلەکانت {profile}'
Channel:
Playlists: {}
New Window: پەنجەرەی نوێ
Go to page: بڕۆ بۆ {page}
Preferences: هەڵبژاردەکان
Are you sure you want to open this link?: دڵنیایت دەتەوێت ئەم بەستەرە بکەیتەوە؟
Open New Window: کردنەوەی پەنجەرەیەکی نوێ

View File

@ -426,7 +426,7 @@ Settings:
Hide Chapters: Schowaj rozdziały
Hide Upcoming Premieres: Schowaj nadchodzące premiery
Hide Channels: Schowaj filmy z kanałów
Hide Channels Placeholder: Nazwa albo ID kanału
Hide Channels Placeholder: ID kanału
Display Titles Without Excessive Capitalisation: Wyświetlaj tytuły bez nadmiernych
wielkich liter
Hide Channel Community: Schowaj społeczność kanału
@ -447,6 +447,13 @@ Settings:
Hide Profile Pictures in Comments: Nie pokazuj zdjęć profilowych w komentarzach
Blur Thumbnails: Rozmazuj miniaturki
Hide Subscriptions Community: Schowaj „Społeczność” kanałów
Hide Channels Invalid: Podane ID kanału jest niepoprawne
Hide Channels Disabled Message: Niektóre z kanałów nie zostały przetworzone, ponieważ
zostały zablokowane po swoim ID. Funkcja jest zablokowana, aż do momentu, w
którym zostaną one zaktualizowane.
Hide Channels Already Exists: To ID kanału już jest
Hide Channels API Error: Nie udało się wyszukać użytkownika po podanym ID. Proszę
sprawdzić, czy wpisane ID jest poprawne.
The app needs to restart for changes to take effect. Restart and apply change?: Aplikacja
musi zostać ponownie uruchomiona, aby zmiany zostały wprowadzone. Uruchomić ponownie
i zastosować zmiany?
@ -791,6 +798,8 @@ Video:
na żywo jest nie dostępny dla tej transmisji. Być może został on wyłączony przez
osobę wstawiającą.
Pause on Current Video: Zatrzymaj po tym filmie
Unhide Channel: Pokaż kanał
Hide Channel: Ukryj kanał
Videos:
#& Sort By
Sort By:
@ -932,6 +941,8 @@ Profile:
Profile Filter: Filtr profilu
Profile Settings: Ustawienia profilu
Toggle Profile List: Włącz/wyłącz listę profili
Open Profile Dropdown: Otwórz rozwijane menu profilu
Close Profile Dropdown: Zamknij rozwijane menu profilu
The playlist has been reversed: Playlista została odwrócona
A new blog is now available, {blogTitle}. Click to view more: 'Nowy wpis na blogu
jest dostępny, {blogTitle}. Kliknij, aby zobaczyć więcej'
@ -1006,10 +1017,10 @@ Tooltips:
Electrona i włącza własny obraz pamięci podręcznej wewnątrz pamięci RAM. Spowoduje
to większe użycie pamięci RAM.
Distraction Free Settings:
Hide Channels: Wprowadź nazwę albo ID kanału, aby schować wszystkie filmy i playlisty
tego kanału, oraz sam kanał z wyszukiwań, z zakładek „Na czasie” i „Popularne”
oraz z polecanych. Nazwa kanału musi być dokładnym dopasowaniem, z uwzględnieniem
wielkości liter.
Hide Channels: Wprowadź ID kanału, aby schować wszystkie filmy i playlisty tego
kanału, oraz sam kanał z wyszukiwań, z zakładek „Na czasie” i „Popularne” oraz
z polecanych. ID kanału musi być dokładnym dopasowaniem, z uwzględnieniem wielkości
liter.
Hide Subscriptions Live: Ta opcja została nadpisana opcją ogólną „{appWideSetting}”
z podgrupy „{subsection}” grupy „{settingsSection}”
SponsorBlock Settings:
@ -1079,3 +1090,6 @@ Playlist will pause when current video is finished: Playlista zatrzyma się, gdy
film się zakończy
Playlist will not pause when current video is finished: Playlista nie zatrzyma się,
gdy obecny film się zakończy
Channel Hidden: '{channel} dodany do filtra kanałów'
Go to page: Idź do {page}
Channel Unhidden: '{channel} usunięty z filtra kanału'

View File

@ -452,7 +452,7 @@ Settings:
Hide Chapters: Ocultar capítulos
Hide Upcoming Premieres: Ocultar próximas estreias
Hide Channels: Ocultar vídeos dos canais
Hide Channels Placeholder: Nome ou ID do canal
Hide Channels Placeholder: ID do canal
Display Titles Without Excessive Capitalisation: Mostrar títulos sem maiúsculas
em excesso
Hide Featured Channels: Ocultar canais em destaque
@ -473,6 +473,13 @@ Settings:
Hide Profile Pictures in Comments: Ocultar imagens de perfil nos comentários
Blur Thumbnails: Desfocar miniaturas
Hide Subscriptions Community: Ocultar subscrições de comunidades
Hide Channels Invalid: O ID do canal não é válido
Hide Channels Disabled Message: Alguns canais foram bloqueados e não foram processados.
A funcionalidade está bloqueada enquanto estiver a ocorrer a atualização das
ID.
Hide Channels Already Exists: Esta ID já existe
Hide Channels API Error: Não foi possível obter o utilizador através do ID. Verifique
se a ID indicada é válida.
External Player Settings:
Custom External Player Arguments: Argumentos do reprodutor externo
Custom External Player Executable: Executável do reprodutor externo
@ -1002,9 +1009,9 @@ Tooltips:
Replace HTTP Cache: Desativa a cache HTTP Electron e ativa uma cache de imagem
na memória personalizada. Implica o aumento da utilização de memória RAM.
Distraction Free Settings:
Hide Channels: Introduza o nome ou ID de um canal para impedir que os vídeos,
listas de reprodução e o próprio canal apareçam na pesquisa, tendências, mais
populares e recomendados. O nome do canal introduzido tem que ser uma correspondência
Hide Channels: Introduza o ID de um canal para impedir que os vídeos, listas de
reprodução e o próprio canal apareçam na pesquisa, tendências, mais populares
e recomendados. O nome do canal introduzido tem que ser uma correspondência
exata e diferencia maiúsculas de minúsculas.
Hide Subscriptions Live: Esta definição é substituída pela definição global "{appWideSetting}",
existente na secção "{subsection}" de "{settingsSection}"

View File

@ -419,7 +419,7 @@ Settings:
Hide Video Description: Скрыть описание видео
Hide Chapters: Скрыть разделы
Hide Upcoming Premieres: Скрыть предстоящие премьеры
Hide Channels Placeholder: Название или идентификатор канала
Hide Channels Placeholder: Идентификатор канала
Hide Channels: Скрыть видео с каналов
Display Titles Without Excessive Capitalisation: Отображать заголовки без чрезмерного
использования заглавных букв
@ -439,6 +439,13 @@ Settings:
Hide Channel Podcasts: Скрыть звукопередачи канала
Hide Subscriptions Shorts: Скрыть короткие видео из подписок
Hide Profile Pictures in Comments: Скрыть изображения профилей в комментариях
Hide Channels Invalid: Указанный идентификатор канала недействителен
Hide Subscriptions Community: Скрыть сообщество подписок
Hide Channels Disabled Message: Некоторые каналы были заблокированы по идентификатору
и не были обработаны. Функция заблокирована, пока эти идентификаторы обновляются
Hide Channels Already Exists: Идентификатор канала уже существует
Hide Channels API Error: Ошибка при получении пользователя с предоставленным идентификатором.
Пожалуйста, проверьте еще раз, правильно ли указан идентификатор.
The app needs to restart for changes to take effect. Restart and apply change?: Чтобы
изменения вступили в силу, необходимо перезапустить приложение. Перезапустить
и применить изменения?
@ -994,10 +1001,9 @@ Tooltips:
Replace HTTP Cache: Отключает дисковый HTTP-кэш Electron и включает пользовательский
кэш изображений в памяти. Приведёт к увеличению использования оперативной памяти.
Distraction Free Settings:
Hide Channels: Введите название канала или его идентификатор, чтобы скрыть все
видео, подборки и сам канал от показа в поиске, трендах, наиболее просматриваемых
и желательных. Введённое название канала должно полностью совпадать и учитывать
регистр.
Hide Channels: Введите идентификатор канала, чтобы скрыть все видео, плейлисты
и сам канал от появления в результатах поиска, трендах, самых популярных и рекомендуемых.
Введенный идентификатор канала должен полностью совпадать и чувствителен к регистру.
Hide Subscriptions Live: Эта настройка переопределена общей настройкой «{appWideSetting}»,
в подразделе «{subsection}» раздела «{settingsSection}»
SponsorBlock Settings:

View File

@ -7,17 +7,17 @@ FreeTube: 'FreeTube'
# Webkit Menu Bar
File: 'Súbor'
Quit: 'Ukončiť'
Edit: 'Upravovať'
Edit: 'Upraviť'
Undo: 'Vrátiť späť'
Redo: 'Znovu vykonať'
Cut: 'Strih'
Cut: 'Vystrihnúť'
Copy: 'Kopírovať'
Paste: 'Vložiť'
Delete: 'Vymazať'
Select all: 'Vybrať všetko'
Reload: 'Obnoviť'
Force Reload: 'Vynútiť obnovenie'
Toggle Developer Tools: 'Spínať nástroje pre vývojárov'
Toggle Developer Tools: 'Prepnúť vývojárske nástroje'
Actual size: 'Skutočná veľkosť'
Zoom in: 'Priblížiť'
Zoom out: 'Oddialiť'
@ -25,8 +25,8 @@ Toggle fullscreen: 'Prepnúť na celú obrazovku'
Window: 'Okno'
Minimize: 'Minimalizovať'
Close: 'Zavrieť'
Back: 'Späť'
Forward: 'Vpred'
Back: 'Dozadu'
Forward: 'Dopredu'
# Global
# Anything shared among components / views should be put here
@ -34,16 +34,23 @@ Global:
Videos: 'Videá'
# Search Bar
Search / Go to URL: 'Hľadať / Ísť na adresu URL'
Counts:
Video Count: 1 video | {count} videí
Subscriber Count: 1 odberateľ | {count} odberateľov
Channel Count: 1 kanál | {count} kanálov
Live: Naživo
Community: Komunita
Shorts: Shorts
Search / Go to URL: 'Hľadať / Ísť na URL adresu'
# In Filter Button
Search Filters:
Search Filters: 'Vyhľadávacie filtre'
Sort By:
Sort By: 'Triediť podľa'
Most Relevant: 'Najrelevantnejšie'
Most Relevant: 'Najvhodnejšie'
Rating: 'Hodnotenie'
Upload Date: 'Dátum nahratia'
View Count: 'Počet zhliadnutí'
View Count: 'Počet zobrazení'
Time:
Time: 'Čas'
Any Time: 'Kedykoľvek'
@ -53,37 +60,36 @@ Search Filters:
This Month: 'Tento mesiac'
This Year: 'Tento rok'
Type:
Type: 'Typ'
All Types: 'Všetky druhy'
Type: 'Druh'
All Types: 'Všetky'
Videos: 'Videá'
Channels: 'Kanály'
#& Playlists
Movies: Filmy
Duration:
Duration: 'Trvanie'
All Durations: 'Všetky trvania'
All Durations: 'Všetky'
Short (< 4 minutes): 'Krátke (menej ako 4 minúty)'
Long (> 20 minutes): 'Dlhé (viac ako 20 minút)'
# On Search Page
Medium (4 - 20 minutes): Stredné (4 až 20 minút)
Search Results: 'Výsledky vyhľadávania'
Fetching results. Please wait: 'Načítavajú sa výsledky. Prosím čakajte'
Fetching results. Please wait: 'Načítavam výsledky. Prosím čakajte'
Fetch more results: 'Načítať viac výsledkov'
# Sidebar
There are no more results for this search: Pre toto hľadanie nie sú k dispozícii
žiadne ďalšie výsledky
There are no more results for this search: Pre toto hľadanie nie sú ďalšie výsledky
Subscriptions:
# On Subscriptions Page
Subscriptions: 'Odbery'
Latest Subscriptions: 'Najnovšie Odbery'
Latest Subscriptions: 'Najnovšie odbery'
'Your Subscription list is currently empty. Start adding subscriptions to see them here.': 'Váš
zoznam odberov je momentálne prázdny. Začnite pridávať odbery aby sa tu zobrazili.'
zoznam odberov je prázdny. Začnite pridávať odbery aby sa tu zobrazili.'
'Getting Subscriptions. Please wait.': 'Získavanie odberov. Prosím čakajte.'
Load More Videos: Načítať viac videí
Refresh Subscriptions: Načítať odbery znovu
'Getting Subscriptions. Please wait.': Nahrávam odbery, prosím počkajte.
Refresh Subscriptions: Obnoviť odbery
'Getting Subscriptions. Please wait.': Sťahujem odbery, prosím čakajte.
This profile has a large number of subscriptions. Forcing RSS to avoid rate limiting: Tento
profil má mnoho odberateľov. Vždy použite RSS, aby ste obišli limit
profil má priveľa odberov. Vynucujem RSS na obídenie limitov
Trending:
Trending: 'Trendy'
Default: Predvolené
@ -91,30 +97,30 @@ Trending:
Movies: Filmy
Trending Tabs: Karty trendov
Gaming: Hry
Most Popular: 'Najpopulárnejšie'
Playlists: 'Zoznamy'
Most Popular: 'Populárne'
Playlists: 'Playlist'
User Playlists:
Your Playlists: 'Vaše zoznamy'
Your Playlists: 'Tvoj playlist'
Your saved videos are empty. Click on the save button on the corner of a video to have it listed here: Vaše
uložené videá sú prázdne. Kliknutím na tlačidlo uložiť v rohu videa ho tu zobrazíte
Playlist Message: Táto stránka neodráža plne funkčné zoznamy videí. Uvádza iba zoznam
videí, ktoré ste uložili alebo zaradili medzi obľúbené. Po dokončení práce sa
všetky videá, ktoré sa tu nachádzajú, migrujú do zoznamu „Obľúbené“.
Playlist Message: Zatiaľ tu nie sú plne funkčné playlisty. Sú tu len videá, ktoré
ste uložili medzi obľúbené. Keď playlisty plne implementujeme do aplikácie, presunú
sa do „Obľúbené“.
History:
# On History Page
History: 'História'
Watch History: 'História pozeraných videí'
Watch History: 'Pozreté videá'
Your history list is currently empty.: 'Váša história je momentálne prázdna.'
Settings:
# On Settings Page
Settings: 'Nastavenia'
General Settings:
General Settings: 'Všeobecné nastavenia'
Fallback to Non-Preferred Backend on Failure: 'Záloha k nepreferovanému backendu
pri poruche'
Enable Search Suggestions: 'Povoliť návrhy na vyhľadávanie'
Fallback to Non-Preferred Backend on Failure: 'Pri chybe prepnúť na alternatívny
backend'
Enable Search Suggestions: 'Zapnúť návrhy vyhľadávania'
Default Landing Page: 'Predvolená vstupná stránka'
Locale Preference: 'Predvoľba miestneho nastavenia'
Locale Preference: 'Nastaviť jazyk'
Preferred API Backend:
Preferred API Backend: 'Preferované API Backend'
Local API: 'Lokálne API'
@ -133,10 +139,10 @@ Settings:
(Predvolená je https://invidious.snopyta.org)'
Region for Trending: 'Región pre trendy'
#! List countries
Check for Latest Blog Posts: Skontrolovať najnovšie príspevky na blogu
Check for Updates: Skontrolovať aktualizácie
Check for Latest Blog Posts: Kontrolovať najnovšie príspevky na blogu
Check for Updates: Kontrolovať aktualizácie
View all Invidious instance information: Zobraziť všetko info o Invidious inštancií
System Default: Určené Systémom
System Default: Podľa systému
Set Current Instance as Default: Nastaviť Aktuálne vybranú inštanciu ako predvolenú
External Link Handling:
External Link Handling: Spracovanie externých odkazov
@ -325,7 +331,7 @@ Settings:
Search cache has been cleared: Vyrovnávacia pamäť vyhľadávania bola vymazaná
Automatically Remove Video Meta Files: Automaticky Odstrániť Metasúbory Videa
The app needs to restart for changes to take effect. Restart and apply change?: Aplikácia
požaduje reštartovanie aby sa prejavili zmeny. Reštartovať a aplikovať zmeny?
požaduje reštart, aby sa zmeny prejavili. Reštartovať a aplikovať zmeny?
Proxy Settings:
Error getting network information. Is your proxy configured properly?: Chyba pri
získavaní informácií o sieti. Je váš server proxy správne nakonfigurovaný?
@ -465,8 +471,8 @@ Video:
momentálne podporovaný.'
'Chat is disabled or the Live Stream has ended.': 'Chat je zakázaný alebo sa priamy
prenos skončil.'
Live chat is enabled. Chat messages will appear here once sent.: 'Live chat je
povolený. Po odoslaní sa tu zobrazia četové správy.'
Live chat is enabled. Chat messages will appear here once sent.: 'Live chat je povolený.
Po odoslaní sa tu zobrazia četové správy.'
'Live Chat is currently not supported with the Invidious API. A direct connection to YouTube is required.': 'Live
Chat momentálne nie je podporovaný pomocou API Invidious. Vyžaduje sa priame pripojenie
k službe YouTube.'
@ -528,7 +534,7 @@ Video:
Video has been saved: Video uložené
Save Video: Uložiť Video
Sponsor Block category:
music offtopic: hudba ostatné
music offtopic: Hudba offtopic
interaction: Interakcia
self-promotion: Samopropagácia
outro: Záver
@ -692,9 +698,9 @@ Tooltips:
Preferred API Backend: Vyberte backend, ktorý FreeTube používa na získavanie údajov.
Lokálne API je zabudovaný extraktor. Invidious API vyžaduje na pripojenie server
Invidious.
External Link Handling: "Vyberte predvolené správanie pri kliknutí na odkaz, ktorý\
\ nemožno otvoriť vo FreeTube.\nV predvolenom nastavení FreeTube otvorí odkaz,\
\ na ktorý ste klikli, vo vašom predvolenom prehliadači.\n"
External Link Handling: "Vyberte predvolené správanie pri kliknutí na odkaz, ktorý
nemožno otvoriť vo FreeTube.\nV predvolenom nastavení FreeTube otvorí odkaz,
na ktorý ste klikli, vo vašom predvolenom prehliadači.\n"
Privacy Settings:
Remove Video Meta Files: Ak je povolené, FreeTube po zatvorení stránky prezerania
automaticky odstráni metasúbory vytvorené počas prehrávania videa.
@ -751,12 +757,12 @@ Profile:
Profile Select: Vyberte profil
Profile Filter: Filter Profilov
Profile Settings: Nastavenia profilu
A new blog is now available, {blogTitle}. Click to view more: 'Nový príspevok na blogu
je k dispozícií, {blogTitle}. Klikni pre viac informácií'
Download From Site: Stiahnuť zo stránky
Version {versionNumber} is now available! Click for more details: Je k dispozícií
verzia {versionNumber} ! Klikni pre viac informácií
Locale Name: slovenčina
A new blog is now available, {blogTitle}. Click to view more: 'Dostupný nový článok
na blogu: {blogTitle}. Klikni pre viac informácií'
Download From Site: Stiahnuť z webu
Version {versionNumber} is now available! Click for more details: Verzia {versionNumber}
je k dispozícii! Klikni pre viac informácií
Locale Name: Slovenčina
Playing Next Video Interval: Prehrávanie ďalšieho videa za chvíľu. Kliknutím zrušíte.
| Prehráva sa ďalšie video o {nextVideoInterval} sekundu. Kliknutím zrušíte. | Prehráva
sa ďalšie video o {nextVideoInterval} sekúnd. Kliknutím zrušíte.
@ -765,11 +771,11 @@ Hashtags have not yet been implemented, try again later: Neznámy typ adresy URL
v aplikácii sa nedá otvoriť
Unknown YouTube url type, cannot be opened in app: Neznámy typ adresy URL YouTube,
v aplikácii sa nedá otvoriť
Open New Window: Otvoriť Nové Okno
Open New Window: Otvoriť nové okno
Default Invidious instance has been set to {instance}: Predvolená Invidious inštancia
bola nastavená na {instance}
Search Bar:
Clear Input: Čistý vstup
Clear Input: Vymazať
Default Invidious instance has been cleared: Predvolená Invidious inštancia bola vymazaná
External link opening has been disabled in the general settings: V nastaveniach bolo
vypnuté otváranie odkazov v externých aplikáciach
@ -777,7 +783,7 @@ Are you sure you want to open this link?: Naozaj chcete otvoriť tento odkaz?
New Window: Nové okno
Channels:
Channels: Kanály
Title: List kanálov
Count: '{číslo} kanál(e) sa nenašli.'
Search bar placeholder: Vyhľadávať kanále
Preferences: Preferencie
Title: Zoznam kanálov
Count: 'Nájdených {number} kanálov.'
Search bar placeholder: Vyhľadať kanály
Preferences: Predvoľby

View File

@ -333,6 +333,8 @@ Settings:
Fetch Feeds from RSS: 'Прикупи фидове из RSS-а'
Manage Subscriptions: 'Управљање праћењима'
Fetch Automatically: Аутоматски прикупи фид
Only Show Latest Video for Each Channel: Прикажи само најновији видео снимак за
сваки канал
Distraction Free Settings:
Distraction Free Settings: 'Подешавања „Без ометања“'
Hide Video Views: 'Сакриј прегледе видео снимка'
@ -346,7 +348,7 @@ Settings:
Hide Live Chat: 'Сакриј ћаскање уживо'
Hide Active Subscriptions: 'Сакриј активна праћења'
Blur Thumbnails: Замагли сличице
Hide Channels Placeholder: Назив канала или ID канала
Hide Channels Placeholder: ID канала
Hide Video Description: Сакриј опис видео снимка
Hide Chapters: Сакриј поглавља
Sections:
@ -373,6 +375,12 @@ Settings:
Hide Profile Pictures in Comments: Сакриј слике профила у коментарима
Hide Upcoming Premieres: Сакриј предстојеће премијере
Hide Channel Releases: Сакриј издања канала
Hide Channels Invalid: Наведени ID канала је неважећи
Hide Channels Disabled Message: Неки канали су блокирани помоћу ID-а и нису обрађени.
Функција је блокирана док се ти ID-ови ажурирају
Hide Channels Already Exists: ID канала већ постоји
Hide Channels API Error: Грешка при преузимању корисника са наведеним ID-ом. Проверите
поново да ли је ID тачан.
Data Settings:
Data Settings: 'Подешавања података'
Select Import Type: 'Избор врсте увоза'
@ -486,6 +494,7 @@ Settings:
Password: Лозинка
Enter Password To Unlock: Унесите лозинку да бисте откључали подешавања
Unlock: Откључај
Expand All Settings Sections: Прошири све одељке подешавања
About:
#On About page
About: 'О апликацији'
@ -560,6 +569,11 @@ Profile:
#On Channel Page
Profile Settings: Подешавања профила
Toggle Profile List: Укључи листу профила
Open Profile Dropdown: Отвори падајући мени профила
Close Profile Dropdown: Затвори падајући мени профила
Profile Name: Име профила
Edit Profile Name: Измени име профила
Create Profile Name: Направи име профила
Channel:
Subscriber: 'Пратилац'
Subscribers: 'Пратиоци'
@ -755,6 +769,8 @@ Video:
YouTube-ом.
'Live Chat is unavailable for this stream. It may have been disabled by the uploader.': Ћаскање
уживо није доступно за овај стрим. Можда га је онемогућио аутор.
Unhide Channel: Прикажи канал
Hide Channel: Сакриј канал
Tooltips:
Subscription Settings:
Fetch Feeds from RSS: 'Када је омогућено, FreeTube ће користити RSS уместо свог
@ -796,7 +812,7 @@ Tooltips:
Distraction Free Settings:
Hide Channels: Унесите назив канала или ID канала да бисте сакрили све видео снимке,
плејлисте и сам канал, да се не појављују у претрази, у тренду, најпопуларнијима
и препорученима. Назив канала који сте унели мора се потпуно подударати и разликовати
и препорученима. ID канала који сте унели мора се потпуно подударати и разликовати
велика и мала слова.
Hide Subscriptions Live: Ово подешавање је замењено подешавањем за целу апликацију
„{appWideSetting}“ у одељку „{subsection}“ у „{settingsSection}“
@ -976,3 +992,6 @@ Screenshot Error: Снимак екрана није успео. {error}
Downloading has completed: „{videoTitle}“ је завршио преузимање
Loop is now enabled: Понављање је сада омогућено
Downloading failed: Дошло је до проблема при преузимању „{videoTitle}“
Channel Hidden: '{channel} је додат на филтер канала'
Go to page: Иди на {page}
Channel Unhidden: '{channel} је уклоњен из филтера канала'

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