Merge branch 'development' of github.com:FreeTubeApp/FreeTube into feat/add-select-mode

This commit is contained in:
Jason Henriquez 2023-11-22 11:43:44 -06:00
commit 74f14ea273
30 changed files with 455 additions and 123 deletions

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

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

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

@ -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

@ -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

@ -35,6 +35,10 @@ export default defineComponent({
}
},
methods: {
isActiveProfile: function (profile) {
return profile._id === this.activeProfile._id
},
toggleProfileList: function () {
this.profileListShown = !this.profileListShown

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)"

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,54 @@ export default defineComponent({
type: String,
required: true
},
hideProfileDropdownToggle: {
type: Boolean,
default: false
},
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 +80,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 +108,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 +136,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 +151,34 @@ export default defineComponent({
}
}
}
if (this.isProfileDropdownEnabled && !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

@ -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

@ -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: {
@ -74,6 +78,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 () {
@ -181,6 +193,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

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

View File

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

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,7 +53,6 @@
class="unsubscribeContainer"
>
<ft-subscribe-button
class="btn"
:channel-id="channel.id"
:channel-name="channel.name"
:channel-thumbnail="channel.thumbnail"

View File

@ -811,6 +811,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:
@ -1037,3 +1039,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} من مرشح القناة

View File

@ -3,7 +3,7 @@ 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: 'پەڕگە'
@ -32,9 +32,9 @@ Back: 'دواوە'
Forward: 'پێشەوە'
Open New Window: 'کردنەوەی پەنجەرەیەکی نوێ'
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: ''
Are you sure you want to open this link?: 'دڵنیایت دەتەوێت ئەم بەستەرە بکەیتەوە؟'
@ -44,13 +44,13 @@ Global:
Videos: 'ڤیدیۆکان'
Shorts: ''
Live: 'ڕاستەوخۆ'
Community: ''
Community: 'کۆمەڵگە'
Counts:
Video Count: '١ ڤیدیۆ | {count} ڤیدیۆ'
Channel Count: '١ کەناڵ | {count} کەناڵ'
Subscriber Count: '١ بەشداربوو | {count} بەشداربوو'
View Count: 'بینینەک | {count} بینین'
Watching Count: ''
Watching Count: '١ تەمەشاکردن | {count} تەمەشاکردن'
# Search Bar
Search / Go to URL: ''
@ -58,7 +58,7 @@ Search Bar:
Clear Input: ''
# In Filter Button
Search Filters:
Search Filters: ''
Search Filters: 'پاڵفتەکردنی گەڕان'
Sort By:
Sort By: 'ڕیزکردن بە'
Most Relevant: ''
@ -87,10 +87,10 @@ Search Filters:
Medium (4 - 20 minutes): 'ناوەند (٤ - ٢٠ خولەک)'
Long (> 20 minutes): 'درێژ (> ٢٠ خولەک)'
# On Search Page
Search Results: ''
Search Results: 'ئەنجامەکانی گەڕان'
Fetching results. Please wait: ''
Fetch more results: ''
There are no more results for this search: ''
There are no more results for this search: 'ئەنجامەکی تر نییە بۆ ئەم گەڕانە'
# Sidebar
Subscriptions:
# On Subscriptions Page
@ -121,26 +121,27 @@ Channels:
Unsubscribe Prompt: ''
Trending:
Trending: ''
Default: ''
Default: 'بنەڕەت'
Music: 'مۆسیقا'
Gaming: 'یاری'
Movies: 'فیلم'
Trending Tabs: ''
Most Popular: 'باوترین'
Playlists: ''
Playlists: 'پێڕستی لێدانەکان'
User Playlists:
Your 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: ''
Search bar placeholder: 'لەناو پێڕستی لێدان بگەڕێ'
History:
# On History Page
History: 'مێژوو'
Watch History: 'مێژووی تەمەشاکردن'
Your history list is currently empty.: ''
Empty Search Message: ''
Search bar placeholder: ""
Your history list is currently empty.: 'ئێستا لیستەی مێژووت بەتاڵە.'
Empty Search Message: 'هیچ ڤیدیۆیەک لە مێژووت نەدۆزرایەوە کە بەرانبەری گەڕانەکەت
بێت'
Search bar placeholder: "لەناو مێژوو بگەڕێ"
Settings:
# On Settings Page
Settings: 'ڕێکخستنەکان'
@ -153,7 +154,7 @@ Settings:
Enable Search Suggestions: ''
Default Landing Page: ''
Locale Preference: ''
System Default: ''
System Default: 'بنەڕەتی سیستەم'
Preferred API Backend:
Preferred API Backend: ''
Local API: ''
@ -164,8 +165,8 @@ Settings:
List: 'پێڕست'
Thumbnail Preference:
Thumbnail Preference: ''
Default: ''
Beginning: ''
Default: 'بنەڕەت'
Beginning: 'سەرەتا'
Middle: 'ناوەڕاست'
End: 'کۆتایی'
Hidden: 'شاراوە'
@ -193,10 +194,10 @@ Settings:
Hide Side Bar Labels: ''
Hide FreeTube Header Logo: ''
Base Theme:
Base Theme: ''
Base Theme: 'ڕووکاری بنچینە'
Black: 'ڕەش'
Dark: 'تاریک'
System Default: ''
System Default: 'بنەڕەتی سیستەم'
Light: 'ڕووناک'
Dracula: ''
Catppuccin Mocha: ''
@ -208,11 +209,11 @@ Settings:
Pink: 'پەمبە'
Purple: 'وەنەوشەیی'
Deep Purple: 'وەنەوشەیی تۆخ'
Indigo: ''
Indigo: 'نیلی'
Blue: 'شین'
Light Blue: 'شینی ئاڵ'
Cyan: ''
Teal: ''
Cyan: 'شینی تۆخ'
Teal: 'شەدری'
Green: 'کەسک'
Light Green: 'کەسکی ئاڵ'
Lime: ''
@ -856,3 +857,4 @@ Hashtag:
Yes: ''
No: ''
Ok: ''
Go to page: بڕۆ بۆ {page}

View File

@ -806,6 +806,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:
@ -1036,3 +1038,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

@ -554,6 +554,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

View File

@ -827,6 +827,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:
@ -1073,3 +1075,5 @@ Playlist will pause when current video is finished: La lista de reproducción se
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

@ -764,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:
@ -991,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

@ -794,6 +794,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:
@ -1082,3 +1084,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

@ -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

@ -761,6 +761,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 уместо свог
@ -982,3 +984,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} је уклоњен из филтера канала'

View File

@ -713,6 +713,8 @@ Video:
Upcoming: 即将到来
'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:
@ -936,3 +938,5 @@ Hashtag:
Playlist will pause when current video is finished: 当前视频播完后播放列表会暂停
Playlist will not pause when current video is finished: 当前视频播完后播放列表不会暂停
Go to page: 转到页{page}
Channel Hidden: '{channel} 频道已添加到频道过滤器'
Channel Unhidden: 从频道过滤器删除了{channel} 频道

View File

@ -131,7 +131,7 @@ Settings:
Preferred API Backend:
Preferred API Backend: '偏好API伺服器'
Local API: '本機 API'
Invidious API: 'Invidious API(應用程式介面)'
Invidious API: 'Invidious API'
Video View Type:
Video View Type: '影片觀看類別'
Grid: '網格'
@ -141,7 +141,7 @@ Settings:
Default: '預設'
Beginning: '片頭'
Middle: '中間'
End: '尾'
End: '尾'
Hidden: 隱藏
Blur: 模糊
'Invidious Instance (Default is https://invidious.snopyta.org)': 'Invidious實例(預設為
@ -410,7 +410,7 @@ Settings:
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?: 此變更需要重啟讓修改生效。重啟並且套用變更?
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?: 取得網路資訊時發生錯誤。您的代理伺服器設定正確嗎?
City: 城市
@ -590,12 +590,12 @@ Channel:
Releases: 發布
This channel does not currently have any releases: 此頻道目前沒有任何發布
Video:
Open in YouTube: '在YouTube中開啟'
Copy YouTube Link: '複製YouTube連結'
Open YouTube Embedded Player: '開啟YouTube內嵌播放器'
Copy YouTube Embedded Player Link: '複製YouTube內嵌播放器連結'
Open in Invidious: '在Invidious中開啟'
Copy Invidious Link: '複製Invidious連結'
Open in YouTube: '在 YouTube 中開啟'
Copy YouTube Link: '複製 YouTube 連結'
Open YouTube Embedded Player: '開啟 YouTube 內嵌播放器'
Copy YouTube Embedded Player Link: '複製 YouTube 內嵌播放器連結'
Open in Invidious: '在 Invidious 中開啟'
Copy Invidious Link: '複製 Invidious 連結'
Views: '觀看'
Watched: '已觀看'
# As in a Live Video
@ -661,10 +661,10 @@ Video:
audio only: 僅音訊
video only: 僅影片
Download Video: 下載影片
Copy Invidious Channel Link: 複製Invidious頻道連結
Open Channel in Invidious: Invidious開啟頻道
Copy YouTube Channel Link: 複製YouTube頻道連結
Open Channel in YouTube: YouTube開啟頻道
Copy Invidious Channel Link: 複製 Invidious 頻道連結
Open Channel in Invidious: Invidious 開啟頻道
Copy YouTube Channel Link: 複製 YouTube 頻道連結
Open Channel in YouTube: YouTube 開啟頻道
Started streaming on: '開始直播時間'
Streamed on: 直播於
Video has been removed from your saved list: 影片已從您的播放清單移除
@ -722,6 +722,8 @@ Video:
Upcoming: 即將到來
'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:
@ -944,3 +946,6 @@ Hashtag:
This hashtag does not currently have any videos: 此標籤目前沒有任何影片
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} 已從頻道過濾條件移除'