mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2024-08-14 23:57:27 +00:00 
			
		
		
		
	Merge branch 'Master' into 'efy', Update efy submodule
This commit is contained in:
		
						commit
						fbe23772cb
					
				
					 111 changed files with 11620 additions and 6205 deletions
				
			
		
							
								
								
									
										85
									
								
								src/App.vue
									
										
									
									
									
								
							
							
						
						
									
										85
									
								
								src/App.vue
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -34,7 +34,7 @@ video {
 | 
			
		|||
 | 
			
		||||
/*Radius 0*/
 | 
			
		||||
.video-grid img {
 | 
			
		||||
    border-radius: var(--efy_radius0);
 | 
			
		||||
    border-radius: var(--efy_radius) var(--efy_radius) 0 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*Radius 2*/
 | 
			
		||||
| 
						 | 
				
			
			@ -68,7 +68,7 @@ video {
 | 
			
		|||
    display: flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    gap: var(--efy_gap0);
 | 
			
		||||
    margin: 5rem 0 0;
 | 
			
		||||
    margin: 5rem 15rem 15rem 15rem;
 | 
			
		||||
}
 | 
			
		||||
.pp-video-card-buttons :is(a, button) {
 | 
			
		||||
    padding: 4rem 8rem;
 | 
			
		||||
| 
						 | 
				
			
			@ -141,38 +141,46 @@ export default {
 | 
			
		|||
            theme: "dark",
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        setTheme() {
 | 
			
		||||
            let themePref = this.getPreferenceString("theme", "dark");
 | 
			
		||||
            if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
 | 
			
		||||
            else this.theme = themePref;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.setTheme();
 | 
			
		||||
        darkModePreference.addEventListener("change", () => {
 | 
			
		||||
            this.setTheme();
 | 
			
		||||
        });
 | 
			
		||||
        if (this.getPreferenceBoolean("watchHistory", false))
 | 
			
		||||
            if ("indexedDB" in window) {
 | 
			
		||||
                const request = indexedDB.open("piped-db", 2);
 | 
			
		||||
                request.onupgradeneeded = ev => {
 | 
			
		||||
                    const db = request.result;
 | 
			
		||||
                    console.log("Upgrading object store.");
 | 
			
		||||
                    if (!db.objectStoreNames.contains("watch_history")) {
 | 
			
		||||
                        const store = db.createObjectStore("watch_history", { keyPath: "videoId" });
 | 
			
		||||
                        store.createIndex("video_id_idx", "videoId", { unique: true });
 | 
			
		||||
                        store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
 | 
			
		||||
                    }
 | 
			
		||||
                    if (ev.oldVersion < 2) {
 | 
			
		||||
                        const store = request.transaction.objectStore("watch_history");
 | 
			
		||||
                        store.createIndex("watchedAt", "watchedAt", { unique: false });
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
                request.onsuccess = e => {
 | 
			
		||||
                    window.db = e.target.result;
 | 
			
		||||
                };
 | 
			
		||||
            } else console.log("This browser doesn't support IndexedDB");
 | 
			
		||||
 | 
			
		||||
        if ("indexedDB" in window) {
 | 
			
		||||
            const request = indexedDB.open("piped-db", 5);
 | 
			
		||||
            request.onupgradeneeded = ev => {
 | 
			
		||||
                const db = request.result;
 | 
			
		||||
                console.log("Upgrading object store.");
 | 
			
		||||
                if (!db.objectStoreNames.contains("watch_history")) {
 | 
			
		||||
                    const store = db.createObjectStore("watch_history", { keyPath: "videoId" });
 | 
			
		||||
                    store.createIndex("video_id_idx", "videoId", { unique: true });
 | 
			
		||||
                    store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
 | 
			
		||||
                }
 | 
			
		||||
                if (ev.oldVersion < 2) {
 | 
			
		||||
                    const store = request.transaction.objectStore("watch_history");
 | 
			
		||||
                    store.createIndex("watchedAt", "watchedAt", { unique: false });
 | 
			
		||||
                }
 | 
			
		||||
                if (!db.objectStoreNames.contains("playlist_bookmarks")) {
 | 
			
		||||
                    const store = db.createObjectStore("playlist_bookmarks", { keyPath: "playlistId" });
 | 
			
		||||
                    store.createIndex("playlist_id_idx", "playlistId", { unique: true });
 | 
			
		||||
                    store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
 | 
			
		||||
                }
 | 
			
		||||
                if (!db.objectStoreNames.contains("channel_groups")) {
 | 
			
		||||
                    const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
 | 
			
		||||
                    store.createIndex("groupName", "groupName", { unique: true });
 | 
			
		||||
                }
 | 
			
		||||
                if (!db.objectStoreNames.contains("playlists")) {
 | 
			
		||||
                    const playlistStore = db.createObjectStore("playlists", { keyPath: "playlistId" });
 | 
			
		||||
                    playlistStore.createIndex("playlistId", "playlistId", { unique: true });
 | 
			
		||||
                    const playlistVideosStore = db.createObjectStore("playlist_videos", { keyPath: "videoId" });
 | 
			
		||||
                    playlistVideosStore.createIndex("videoId", "videoId", { unique: true });
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
            request.onsuccess = e => {
 | 
			
		||||
                window.db = e.target.result;
 | 
			
		||||
            };
 | 
			
		||||
        } else console.log("This browser doesn't support IndexedDB");
 | 
			
		||||
 | 
			
		||||
        const App = this;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -197,5 +205,24 @@ export default {
 | 
			
		|||
            }
 | 
			
		||||
        })();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        setTheme() {
 | 
			
		||||
            let themePref = this.getPreferenceString("theme", "dark");
 | 
			
		||||
            if (themePref == "auto") this.theme = darkModePreference.matches ? "dark" : "light";
 | 
			
		||||
            else this.theme = themePref;
 | 
			
		||||
 | 
			
		||||
            // Change title bar color based on user's theme
 | 
			
		||||
            const themeColor = document.querySelector("meta[name='theme-color']");
 | 
			
		||||
            if (this.theme === "light") {
 | 
			
		||||
                themeColor.setAttribute("content", "#FFF");
 | 
			
		||||
            } else {
 | 
			
		||||
                themeColor.setAttribute("content", "#0F0F0F");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Used for the scrollbar
 | 
			
		||||
            const root = document.querySelector(":root");
 | 
			
		||||
            this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark");
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,19 +1,19 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
    <div class="flex flex-col flex-justify-between">
 | 
			
		||||
        <router-link :to="props.item.url">
 | 
			
		||||
            <div class="relative">
 | 
			
		||||
                <img class="w-full" :src="props.item.thumbnail" loading="lazy" />
 | 
			
		||||
            <div class="my-4 flex justify-center">
 | 
			
		||||
                <img class="aspect-square w-[50%] rounded-full" :src="props.item.thumbnail" loading="lazy" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <p>
 | 
			
		||||
                <span v-text="props.item.name" />
 | 
			
		||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
 | 
			
		||||
                <font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
 | 
			
		||||
            </p>
 | 
			
		||||
        </router-link>
 | 
			
		||||
        <p v-if="props.item.description" v-text="props.item.description" />
 | 
			
		||||
        <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
 | 
			
		||||
            <p>
 | 
			
		||||
                <span v-text="props.item.uploader" />
 | 
			
		||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
 | 
			
		||||
                <font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
 | 
			
		||||
            </p>
 | 
			
		||||
        </router-link>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +29,9 @@
 | 
			
		|||
 | 
			
		||||
<script setup>
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    item: Object,
 | 
			
		||||
    item: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,88 +1,93 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <ErrorHandler v-if="channel && channel.error" :message="channel.message" :error="channel.error" />
 | 
			
		||||
 | 
			
		||||
    <div v-if="channel" v-show="!channel.error">
 | 
			
		||||
        <img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full pb-1.5" loading="lazy" />
 | 
			
		||||
        <div class="pp-channel-page-author flex place-items-center">
 | 
			
		||||
            <img height="48" width="48" class="m-1" :src="channel.avatarUrl" />
 | 
			
		||||
            <h5 v-text="channel.name" />
 | 
			
		||||
            <font-awesome-icon class="ml-1.5" v-if="channel.verified" icon="check" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
        <p class="whitespace-pre-wrap mt-2">
 | 
			
		||||
            <span v-html="purifyHTML(urlify(channel.description))" />
 | 
			
		||||
        </p>
 | 
			
		||||
        <LoadingIndicatorPage :show-content="channel != null && !channel.error">
 | 
			
		||||
            <img v-if="channel.bannerUrl" :src="channel.bannerUrl" class="w-full pb-1.5" loading="lazy" />
 | 
			
		||||
            <div class="pp-channel-page-author flex">
 | 
			
		||||
                <img height="48" width="48" class="m-1" :src="channel.avatarUrl" />
 | 
			
		||||
                <h5 v-text="channel.name" />
 | 
			
		||||
                <font-awesome-icon v-if="channel.verified" class="ml-1.5" icon="check" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <p v-text="channel.description" style="margin: 10rem 0 0 0" />
 | 
			
		||||
 | 
			
		||||
        <div class="flex mt-4 mb-2 pp-channel-tabs">
 | 
			
		||||
            <button
 | 
			
		||||
                class="btn pp-subscribe"
 | 
			
		||||
                @click="subscribeHandler"
 | 
			
		||||
                v-t="{
 | 
			
		||||
                    path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
 | 
			
		||||
                    args: { count: numberFormat(channel.subscriberCount) },
 | 
			
		||||
                }"
 | 
			
		||||
            ></button>
 | 
			
		||||
            <div class="pp-channel-tabs">
 | 
			
		||||
                <button
 | 
			
		||||
                    v-t="{
 | 
			
		||||
                        path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
 | 
			
		||||
                        args: { count: numberFormat(channel.subscriberCount) },
 | 
			
		||||
                    }"
 | 
			
		||||
                    class="pp-subscribe"
 | 
			
		||||
                    @click="subscribeHandler"
 | 
			
		||||
                ></button>
 | 
			
		||||
 | 
			
		||||
            <!-- RSS Feed button -->
 | 
			
		||||
            <a
 | 
			
		||||
                aria-label="RSS feed"
 | 
			
		||||
                title="RSS feed"
 | 
			
		||||
                role="button"
 | 
			
		||||
                v-if="channel.id"
 | 
			
		||||
                :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
 | 
			
		||||
                target="_blank"
 | 
			
		||||
                class="btn"
 | 
			
		||||
                style="display: inline; float: unset; margin-left: var(--efy_gap0)"
 | 
			
		||||
            >
 | 
			
		||||
                <font-awesome-icon icon="rss" />
 | 
			
		||||
            </a>
 | 
			
		||||
            <WatchOnYouTubeButton :link="`https://youtube.com/channel/${this.channel.id}`" />
 | 
			
		||||
            <p>|</p>
 | 
			
		||||
            <button
 | 
			
		||||
                v-for="(tab, index) in tabs"
 | 
			
		||||
                :key="tab.name"
 | 
			
		||||
                style="margin-right: var(--efy_gap0)"
 | 
			
		||||
                @click="loadTab(index)"
 | 
			
		||||
                :class="{ active: selectedTab == index }"
 | 
			
		||||
            >
 | 
			
		||||
                <span v-text="tab.translatedName"></span>
 | 
			
		||||
            </button>
 | 
			
		||||
        </div>
 | 
			
		||||
                <!-- RSS Feed button -->
 | 
			
		||||
                <a
 | 
			
		||||
                    v-if="channel.id"
 | 
			
		||||
                    aria-label="RSS feed"
 | 
			
		||||
                    title="RSS feed"
 | 
			
		||||
                    role="button"
 | 
			
		||||
                    :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${channel.id}`"
 | 
			
		||||
                    target="_blank"
 | 
			
		||||
                    class="pp-square"
 | 
			
		||||
                    style="display: inline; float: unset"
 | 
			
		||||
                >
 | 
			
		||||
                    <font-awesome-icon icon="rss" />
 | 
			
		||||
                </a>
 | 
			
		||||
                <WatchOnButton :link="`https://youtube.com/channel/${channel.id}`" />
 | 
			
		||||
                <p style="place-self: center">|</p>
 | 
			
		||||
                <button
 | 
			
		||||
                    v-for="(tab, index) in tabs"
 | 
			
		||||
                    :key="tab.name"
 | 
			
		||||
                    :class="{ active: selectedTab == index }"
 | 
			
		||||
                    @click="loadTab(index)"
 | 
			
		||||
                >
 | 
			
		||||
                    <span v-text="tab.translatedName"></span>
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        <hr />
 | 
			
		||||
            <hr />
 | 
			
		||||
 | 
			
		||||
        <div class="video-grid">
 | 
			
		||||
            <ContentItem
 | 
			
		||||
                v-for="item in contentItems"
 | 
			
		||||
                :key="item.url"
 | 
			
		||||
                :item="item"
 | 
			
		||||
                height="94"
 | 
			
		||||
                width="168"
 | 
			
		||||
                hide-channel
 | 
			
		||||
                class="efy_trans_filter"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
            <div class="video-grid">
 | 
			
		||||
                <ContentItem
 | 
			
		||||
                    v-for="item in contentItems"
 | 
			
		||||
                    :key="item.url"
 | 
			
		||||
                    :item="item"
 | 
			
		||||
                    height="94"
 | 
			
		||||
                    width="168"
 | 
			
		||||
                    hide-channel
 | 
			
		||||
                    class="efy_trans_filter"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        </LoadingIndicatorPage>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.pp-channel-tabs > p {
 | 
			
		||||
    place-self: center;
 | 
			
		||||
    padding: 0 10rem;
 | 
			
		||||
    -webkit-text-fill-color: var(--efy_text_trans2);
 | 
			
		||||
.pp-channel-tabs {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    margin: 15rem 0;
 | 
			
		||||
    gap: var(--efy_gap0);
 | 
			
		||||
}
 | 
			
		||||
.pp-channel-tabs :is(button, [role="button"]) {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ErrorHandler from "./ErrorHandler.vue";
 | 
			
		||||
import ContentItem from "./ContentItem.vue";
 | 
			
		||||
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
 | 
			
		||||
import WatchOnButton from "./WatchOnButton.vue";
 | 
			
		||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
			
		||||
// import CollapsableText from "./CollapsableText.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ErrorHandler,
 | 
			
		||||
        ContentItem,
 | 
			
		||||
        WatchOnYouTubeButton,
 | 
			
		||||
        WatchOnButton,
 | 
			
		||||
        LoadingIndicatorPage,
 | 
			
		||||
        // CollapsableText,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +135,9 @@ export default {
 | 
			
		|||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async fetchChannel() {
 | 
			
		||||
            const url = this.apiUrl() + "/" + this.$route.params.path + "/" + this.$route.params.channelId;
 | 
			
		||||
            const url = this.$route.path.includes("@")
 | 
			
		||||
                ? this.apiUrl() + "/@/" + this.$route.params.channelId
 | 
			
		||||
                : this.apiUrl() + "/" + this.$route.params.path + "/" + this.$route.params.channelId;
 | 
			
		||||
            return await this.fetchJson(url);
 | 
			
		||||
        },
 | 
			
		||||
        async getChannelData() {
 | 
			
		||||
| 
						 | 
				
			
			@ -142,13 +149,16 @@ export default {
 | 
			
		|||
                        this.contentItems = this.channel.relatedStreams;
 | 
			
		||||
                        this.fetchSubscribedStatus();
 | 
			
		||||
                        this.updateWatched(this.channel.relatedStreams);
 | 
			
		||||
                        this.fetchDeArrowContent(this.channel.relatedStreams);
 | 
			
		||||
                        this.tabs.push({
 | 
			
		||||
                            translatedName: this.$t("video.videos"),
 | 
			
		||||
                        });
 | 
			
		||||
                        const tabQuery = this.$route.query.tab;
 | 
			
		||||
                        for (let i = 0; i < this.channel.tabs.length; i++) {
 | 
			
		||||
                            let tab = this.channel.tabs[i];
 | 
			
		||||
                            tab.translatedName = this.getTranslatedTabName(tab.name);
 | 
			
		||||
                            this.tabs.push(tab);
 | 
			
		||||
                            if (tab.name === tabQuery) this.loadTab(i + 1);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			@ -178,6 +188,7 @@ export default {
 | 
			
		|||
                this.loading = false;
 | 
			
		||||
                this.updateWatched(json.relatedStreams);
 | 
			
		||||
                json.relatedStreams.map(stream => this.contentItems.push(stream));
 | 
			
		||||
                this.fetchDeArrowContent(this.contentItems);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        fetchChannelTabNextPage() {
 | 
			
		||||
| 
						 | 
				
			
			@ -188,6 +199,7 @@ export default {
 | 
			
		|||
                this.tabs[this.selectedTab].tabNextPage = json.nextpage;
 | 
			
		||||
                this.loading = false;
 | 
			
		||||
                json.content.map(item => this.contentItems.push(item));
 | 
			
		||||
                this.fetchDeArrowContent(this.contentItems);
 | 
			
		||||
                this.tabs[this.selectedTab].content = this.contentItems;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -231,10 +243,17 @@ export default {
 | 
			
		|||
        },
 | 
			
		||||
        loadTab(index) {
 | 
			
		||||
            this.selectedTab = index;
 | 
			
		||||
 | 
			
		||||
            // update the tab query in the url path
 | 
			
		||||
            const url = new URL(window.location);
 | 
			
		||||
            url.searchParams.set("tab", this.tabs[index].name ?? "videos");
 | 
			
		||||
            window.history.replaceState(window.history.state, "", url);
 | 
			
		||||
 | 
			
		||||
            if (index == 0) {
 | 
			
		||||
                this.contentItems = this.channel.relatedStreams;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.tabs[index].content) {
 | 
			
		||||
                this.contentItems = this.tabs[index].content;
 | 
			
		||||
                return;
 | 
			
		||||
| 
						 | 
				
			
			@ -243,6 +262,7 @@ export default {
 | 
			
		|||
                data: this.tabs[index].data,
 | 
			
		||||
            }).then(tab => {
 | 
			
		||||
                this.contentItems = this.tabs[index].content = tab.content;
 | 
			
		||||
                this.fetchDeArrowContent(this.contentItems);
 | 
			
		||||
                this.tabs[this.selectedTab].tabNextPage = tab.nextpage;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,11 @@
 | 
			
		|||
            {{ $t("video.chapters") }} - {{ chapters.length }}
 | 
			
		||||
        </h6>
 | 
			
		||||
        <div
 | 
			
		||||
            :key="chapter.start"
 | 
			
		||||
            v-for="(chapter, index) in chapters"
 | 
			
		||||
            @click="$emit('seek', chapter.start)"
 | 
			
		||||
            :key="chapter.start"
 | 
			
		||||
            class="chapter efy_anim_pulse efy_trans_filter"
 | 
			
		||||
            :class="{ 'pp-chapter-active': isCurrentChapter(index) }"
 | 
			
		||||
            @click="$emit('seek', chapter.start)"
 | 
			
		||||
        >
 | 
			
		||||
            <div class="flex">
 | 
			
		||||
                <img :src="chapter.image" :alt="chapter.title" />
 | 
			
		||||
| 
						 | 
				
			
			@ -23,11 +23,11 @@
 | 
			
		|||
    <!-- mobile view -->
 | 
			
		||||
    <div v-else class="pp-chapters pp-mobile flex overflow-x-auto">
 | 
			
		||||
        <div
 | 
			
		||||
            :key="chapter.start"
 | 
			
		||||
            v-for="(chapter, index) in chapters"
 | 
			
		||||
            @click="$emit('seek', chapter.start)"
 | 
			
		||||
            :key="chapter.start"
 | 
			
		||||
            class="chapter efy_anim_pulse efy_trans_filter"
 | 
			
		||||
            :class="{ 'pp-chapter-active': isCurrentChapter(index) }"
 | 
			
		||||
            @click="$emit('seek', chapter.start)"
 | 
			
		||||
        >
 | 
			
		||||
            <img :src="chapter.image" :alt="chapter.title" />
 | 
			
		||||
            <div class="m-1 flex">
 | 
			
		||||
| 
						 | 
				
			
			@ -38,24 +38,12 @@
 | 
			
		|||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.chapter {
 | 
			
		||||
    @apply cursor-pointer self-center p-2.5;
 | 
			
		||||
}
 | 
			
		||||
.pp-mobile .chapter img {
 | 
			
		||||
    @apply w-full h-full;
 | 
			
		||||
}
 | 
			
		||||
.chapter img {
 | 
			
		||||
    @apply w-3/10 h-3/10;
 | 
			
		||||
}
 | 
			
		||||
.text-truncate {
 | 
			
		||||
    @apply truncate overflow-hidden inline-block w-10em;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    chapters: Object,
 | 
			
		||||
    chapters: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        default: () => null,
 | 
			
		||||
    },
 | 
			
		||||
    mobileLayout: {
 | 
			
		||||
        type: Boolean,
 | 
			
		||||
        default: () => true,
 | 
			
		||||
| 
						 | 
				
			
			@ -75,3 +63,24 @@ const isCurrentChapter = index => {
 | 
			
		|||
 | 
			
		||||
defineEmits(["seek"]);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.chapter {
 | 
			
		||||
    @apply cursor-pointer self-center p-2.5;
 | 
			
		||||
}
 | 
			
		||||
.pp-mobile .chapter img {
 | 
			
		||||
    @apply w-full h-full;
 | 
			
		||||
}
 | 
			
		||||
.chapter img {
 | 
			
		||||
    @apply w-3/10 h-3/10;
 | 
			
		||||
}
 | 
			
		||||
.text-truncate {
 | 
			
		||||
    @apply truncate overflow-hidden inline-block w-10em;
 | 
			
		||||
}
 | 
			
		||||
.pp-chapter-active,
 | 
			
		||||
.pp-chapters .chapter:hover {
 | 
			
		||||
    background: var(--efy_color);
 | 
			
		||||
    background-clip: padding-box;
 | 
			
		||||
    color: var(--efy_text2);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										42
									
								
								src/components/CollapsableText.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/components/CollapsableText.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
<template v-if="text">
 | 
			
		||||
    <div class="mx-1 whitespace-pre-wrap py-2">
 | 
			
		||||
        <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
        <span v-if="showFullText" v-html="fullText()" />
 | 
			
		||||
        <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
        <span v-else v-html="colapsedText()" />
 | 
			
		||||
        <span v-if="text.length > 100 && !showFullText">...</span>
 | 
			
		||||
        <button
 | 
			
		||||
            v-if="text.length > 100"
 | 
			
		||||
            class="block whitespace-normal font-semibold text-neutral-500 hover:underline"
 | 
			
		||||
            @click="showFullText = !showFullText"
 | 
			
		||||
        >
 | 
			
		||||
            [{{ showFullText ? $t("actions.show_less") : $t("actions.show_more") }}]
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { purifyHTML, rewriteDescription } from "@/utils/HtmlUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        text: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            default: null,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            showFullText: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        fullText() {
 | 
			
		||||
            return purifyHTML(rewriteDescription(this.text));
 | 
			
		||||
        },
 | 
			
		||||
        colapsedText() {
 | 
			
		||||
            return purifyHTML(rewriteDescription(this.text.slice(0, 100)));
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,38 +1,43 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div class="comment flex mt-1.5">
 | 
			
		||||
        <img :src="comment.thumbnail" class="comment-avatar" height="48" width="48" loading="lazy" alt="Avatar" />
 | 
			
		||||
        <router-link style="height: fit-content" :to="comment.commentorUrl">
 | 
			
		||||
            <img :src="comment.thumbnail" class="comment-avatar" height="48" width="48" loading="lazy" alt="Avatar" />
 | 
			
		||||
        </router-link>
 | 
			
		||||
 | 
			
		||||
        <div class="comment-content pl-2">
 | 
			
		||||
            <div class="comment-header">
 | 
			
		||||
                <div v-if="comment.pinned" class="comment-pinned">
 | 
			
		||||
                    <font-awesome-icon icon="thumbtack" />
 | 
			
		||||
                    <span
 | 
			
		||||
                        class="ml-1.5"
 | 
			
		||||
                        v-t="{
 | 
			
		||||
                            path: 'comment.pinned_by',
 | 
			
		||||
                            args: { author: uploader },
 | 
			
		||||
                        }"
 | 
			
		||||
                        class="ml-1.5"
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="comment-author mt-1 flex">
 | 
			
		||||
                    <router-link class="font-bold link" :to="comment.commentorUrl">{{ comment.author }}</router-link>
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" v-if="comment.verified" icon="check" />
 | 
			
		||||
                <div class="comment-author flex align-center">
 | 
			
		||||
                    <router-link class="link font-bold" :to="comment.commentorUrl">{{ comment.author }}</router-link>
 | 
			
		||||
                    <font-awesome-icon v-if="comment.verified" class="ml-1.5" icon="check" />
 | 
			
		||||
                    <div class="comment-meta mb-1.5" v-text="' • ' + comment.commentedTime + ' •'" />
 | 
			
		||||
                    <div class="i-fa-solid:thumbs-up" />
 | 
			
		||||
                    <span class="ml-1" v-text="numberFormat(comment.likeCount)" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1" v-if="comment.hearted" icon="heart" />
 | 
			
		||||
                    <div class="comment-footer mt-1 flex items-center">
 | 
			
		||||
                        <div class="i-fa6-solid:thumbs-up" />
 | 
			
		||||
                        <span class="ml-1" v-text="numberFormat(comment.likeCount)" />
 | 
			
		||||
                        <font-awesome-icon v-if="comment.hearted" class="ml-1" icon="heart" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="whitespace-pre-wrap" v-html="purifyHTML(comment.commentText)" />
 | 
			
		||||
            <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
            <div class="whitespace-pre-wrap" v-html="purifiedText" />
 | 
			
		||||
            <template v-if="comment.repliesPage && (!loadingReplies || !showingReplies)">
 | 
			
		||||
                <div @click="loadReplies" class="cursor-pointer">
 | 
			
		||||
                <div class="cursor-pointer" @click="loadReplies">
 | 
			
		||||
                    <a v-text="`${$t('actions.reply_count', comment.replyCount)}`" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-if="showingReplies">
 | 
			
		||||
                <div @click="hideReplies" class="cursor-pointer">
 | 
			
		||||
                <div class="cursor-pointer" @click="hideReplies">
 | 
			
		||||
                    <a v-t="'actions.hide_replies'" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" icon="level-up-alt" />
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +46,7 @@
 | 
			
		|||
                <div v-for="reply in replies" :key="reply.commentId" class="w-full">
 | 
			
		||||
                    <CommentItem :comment="reply" :uploader="uploader" :video-id="videoId" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-if="nextpage" @click="loadReplies" class="cursor-pointer">
 | 
			
		||||
                <div v-if="nextpage" class="cursor-pointer" @click="loadReplies">
 | 
			
		||||
                    <a v-t="'actions.load_more_replies'" />
 | 
			
		||||
                    <font-awesome-icon class="ml-1.5" icon="level-down-alt" />
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +56,8 @@
 | 
			
		|||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { purifyHTML } from "@/utils/HtmlUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        comment: {
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +77,11 @@ export default {
 | 
			
		|||
            nextpage: null,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        purifiedText() {
 | 
			
		||||
            return purifyHTML(this.comment.commentText);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        async loadReplies() {
 | 
			
		||||
            if (!this.showingReplies && this.loadingReplies) {
 | 
			
		||||
| 
						 | 
				
			
			@ -97,4 +109,7 @@ export default {
 | 
			
		|||
.comment-content {
 | 
			
		||||
    overflow-wrap: anywhere;
 | 
			
		||||
}
 | 
			
		||||
.comment-avatar {
 | 
			
		||||
    max-width: unset;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										28
									
								
								src/components/ConfirmModal.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/components/ConfirmModal.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <ModalComponent @close="$emit('close')">
 | 
			
		||||
        <div>
 | 
			
		||||
            <h3 class="text-xl" v-text="message" />
 | 
			
		||||
            <div class="ml-auto mt-8 w-min flex gap-2">
 | 
			
		||||
                <button v-t="'actions.cancel'" class="btn" @click="$emit('close')" />
 | 
			
		||||
                <button v-t="'actions.okay'" class="btn" @click="$emit('confirm')" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </ModalComponent>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ModalComponent from "./ModalComponent.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ModalComponent,
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
        message: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    emits: ["close", "confirm"],
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -6,7 +6,10 @@
 | 
			
		|||
import { defineAsyncComponent } from "vue";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    item: Object,
 | 
			
		||||
    item: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue"));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <p v-text="message" />
 | 
			
		||||
    <button @click="toggleTrace" class="btn" v-t="'actions.show_more'" />
 | 
			
		||||
    <button v-t="'actions.show_more'" class="btn" @click="toggleTrace" />
 | 
			
		||||
    <p ref="stacktrace" class="whitespace-pre-wrap" hidden v-text="error" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,40 +1,97 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <button class="btn mr-2" @click="exportHandler">
 | 
			
		||||
        <router-link to="/subscriptions">Subscriptions</router-link>
 | 
			
		||||
    </button>
 | 
			
		||||
 | 
			
		||||
    <span>
 | 
			
		||||
        <a :href="getRssUrl" class="btn">
 | 
			
		||||
            <font-awesome-icon icon="rss" />
 | 
			
		||||
        </a>
 | 
			
		||||
    </span>
 | 
			
		||||
 | 
			
		||||
    <span class="md:float-right flex pp-sortby-feed">
 | 
			
		||||
        <SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
 | 
			
		||||
    </span>
 | 
			
		||||
 | 
			
		||||
    <hr />
 | 
			
		||||
    <div class="flex flex-wrap align-center" style="place-content: space-between">
 | 
			
		||||
        <span class="buttons flex" style="gap: var(--efy_gap0)">
 | 
			
		||||
            <router-link role="button" to="/subscriptions">Subscriptions</router-link>
 | 
			
		||||
            <a :href="getRssUrl" role="button" class="pp-square">
 | 
			
		||||
                <font-awesome-icon icon="rss" />
 | 
			
		||||
            </a>
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
    <div class="video-grid">
 | 
			
		||||
        <VideoItem :is-feed="true" v-for="video in videos" :key="video.url" :item="video" />
 | 
			
		||||
        <div class="filters flex align-center">
 | 
			
		||||
            <span class="flex">
 | 
			
		||||
                <label for="filters" v-text="`${$t('actions.filter')}:`" />
 | 
			
		||||
                <select
 | 
			
		||||
                    id="filters"
 | 
			
		||||
                    v-model="selectedFilter"
 | 
			
		||||
                    default="all"
 | 
			
		||||
                    class="select flex-grow"
 | 
			
		||||
                    @change="onFilterChange()"
 | 
			
		||||
                >
 | 
			
		||||
                    <option v-for="filter in availableFilters" :key="filter" v-t="`video.${filter}`" :value="filter" />
 | 
			
		||||
                </select>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
            <span class="flex">
 | 
			
		||||
                <label for="group-selector" v-text="`${$t('titles.channel_groups')}:`" />
 | 
			
		||||
                <select id="group-selector" v-model="selectedGroupName" default="" class="select flex-grow">
 | 
			
		||||
                    <option v-t="`video.all`" value="" />
 | 
			
		||||
                    <option
 | 
			
		||||
                        v-for="group in channelGroups"
 | 
			
		||||
                        :key="group.groupName"
 | 
			
		||||
                        :value="group.groupName"
 | 
			
		||||
                        v-text="group.groupName"
 | 
			
		||||
                    />
 | 
			
		||||
                </select>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
            <span class="pp-sortby-feed flex">
 | 
			
		||||
                <SortingSelector by-key="uploaded" @apply="order => videos.sort(order)" />
 | 
			
		||||
            </span>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr />
 | 
			
		||||
    <LoadingIndicatorPage :show-content="videosStore != null" class="video-grid">
 | 
			
		||||
        <template v-for="video in filteredVideos" :key="video.url">
 | 
			
		||||
            <VideoItem v-if="shouldShowVideo(video)" :is-feed="true" :item="video" />
 | 
			
		||||
        </template>
 | 
			
		||||
    </LoadingIndicatorPage>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.filters {
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
.filters,
 | 
			
		||||
.filters span {
 | 
			
		||||
    gap: var(--efy_gap0);
 | 
			
		||||
}
 | 
			
		||||
.filters :is(select, label),
 | 
			
		||||
.buttons a[role="button"] {
 | 
			
		||||
    margin: 0 !important;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    place-content: center;
 | 
			
		||||
}
 | 
			
		||||
.filters span {
 | 
			
		||||
    align-items: center;
 | 
			
		||||
}
 | 
			
		||||
.buttons a[role="button"] {
 | 
			
		||||
    height: var(--efy_ratio_width);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VideoItem from "./VideoItem.vue";
 | 
			
		||||
import SortingSelector from "./SortingSelector.vue";
 | 
			
		||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        VideoItem,
 | 
			
		||||
        SortingSelector,
 | 
			
		||||
        LoadingIndicatorPage,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            currentVideoCount: 0,
 | 
			
		||||
            videoStep: 100,
 | 
			
		||||
            videosStore: [],
 | 
			
		||||
            videosStore: null,
 | 
			
		||||
            videos: [],
 | 
			
		||||
            availableFilters: ["all", "shorts", "videos"],
 | 
			
		||||
            selectedFilter: "all",
 | 
			
		||||
            selectedGroupName: "",
 | 
			
		||||
            channelGroups: [],
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
| 
						 | 
				
			
			@ -42,6 +99,12 @@ export default {
 | 
			
		|||
            if (_this.authenticated) return _this.authApiUrl() + "/feed/rss?authToken=" + _this.getAuthToken();
 | 
			
		||||
            else return _this.authApiUrl() + "/feed/unauthenticated/rss?channels=" + _this.getUnauthenticatedChannels();
 | 
			
		||||
        },
 | 
			
		||||
        filteredVideos(_this) {
 | 
			
		||||
            const selectedGroup = _this.channelGroups.filter(group => group.groupName == _this.selectedGroupName);
 | 
			
		||||
            return _this.selectedGroupName == ""
 | 
			
		||||
                ? _this.videos
 | 
			
		||||
                : _this.videos.filter(video => selectedGroup[0].channels.includes(video.uploaderUrl.substr(-11)));
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.fetchFeed().then(videos => {
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +112,23 @@ export default {
 | 
			
		|||
            this.loadMoreVideos();
 | 
			
		||||
            this.updateWatched(this.videos);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.selectedFilter = this.getPreferenceString("feedFilter") ?? "all";
 | 
			
		||||
 | 
			
		||||
        if (!window.db) return;
 | 
			
		||||
 | 
			
		||||
        const cursor = this.getChannelGroupsCursor();
 | 
			
		||||
        cursor.onsuccess = e => {
 | 
			
		||||
            const cursor = e.target.result;
 | 
			
		||||
            if (cursor) {
 | 
			
		||||
                const group = cursor.value;
 | 
			
		||||
                this.channelGroups.push({
 | 
			
		||||
                    groupName: group.groupName,
 | 
			
		||||
                    channels: JSON.parse(group.channels),
 | 
			
		||||
                });
 | 
			
		||||
                cursor.continue();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        document.title = this.$t("titles.feed") + " - Piped";
 | 
			
		||||
| 
						 | 
				
			
			@ -74,15 +154,31 @@ export default {
 | 
			
		|||
            }
 | 
			
		||||
        },
 | 
			
		||||
        loadMoreVideos() {
 | 
			
		||||
            if (!this.videosStore) return;
 | 
			
		||||
            this.currentVideoCount = Math.min(this.currentVideoCount + this.videoStep, this.videosStore.length);
 | 
			
		||||
            if (this.videos.length != this.videosStore.length)
 | 
			
		||||
            if (this.videos.length != this.videosStore.length) {
 | 
			
		||||
                this.videos = this.videosStore.slice(0, this.currentVideoCount);
 | 
			
		||||
                this.fetchDeArrowContent(this.videos);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        handleScroll() {
 | 
			
		||||
            if (window.innerHeight + window.scrollY >= document.body.offsetHeight - window.innerHeight) {
 | 
			
		||||
                this.loadMoreVideos();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        shouldShowVideo(video) {
 | 
			
		||||
            switch (this.selectedFilter.toLowerCase()) {
 | 
			
		||||
                case "shorts":
 | 
			
		||||
                    return video.isShort;
 | 
			
		||||
                case "videos":
 | 
			
		||||
                    return !video.isShort;
 | 
			
		||||
                default:
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        onFilterChange() {
 | 
			
		||||
            this.setPreference("feedFilter", this.selectedFilter);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,13 +1,30 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <h1 class="font-bold text-center" v-t="'titles.history'" />
 | 
			
		||||
 | 
			
		||||
    <div class="flex place-items-center">
 | 
			
		||||
        <div class="mr-2">
 | 
			
		||||
            <button class="btn" v-t="'actions.clear_history'" @click="clearHistory" />
 | 
			
		||||
        <div class="flex flex-col gap-2 md:flex-row md:items-center">
 | 
			
		||||
            <button v-t="'actions.clear_history'" class="btn" @click="clearHistory" />
 | 
			
		||||
 | 
			
		||||
            <button v-t="'actions.export_to_json'" class="btn" @click="exportHistory" />
 | 
			
		||||
 | 
			
		||||
            <div class="ml-auto flex items-center gap-1">
 | 
			
		||||
                <SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="mr-2">
 | 
			
		||||
            <SortingSelector by-key="watchedAt" @apply="order => videos.sort(order)" />
 | 
			
		||||
        <div class="ml-4 flex items-center">
 | 
			
		||||
            <input id="autoDelete" v-model="autoDeleteHistory" type="checkbox" @change="onChange" />
 | 
			
		||||
            <label v-t="'actions.delete_automatically'" class="ml-2" for="autoDelete" />
 | 
			
		||||
            <select v-model="autoDeleteDelayHours" class="select ml-3 pl-3" @change="onChange">
 | 
			
		||||
                <option v-t="{ path: 'info.hours', args: { amount: '1' } }" value="1" />
 | 
			
		||||
                <option v-t="{ path: 'info.hours', args: { amount: '3' } }" value="3" />
 | 
			
		||||
                <option v-t="{ path: 'info.hours', args: { amount: '6' } }" value="6" />
 | 
			
		||||
                <option v-t="{ path: 'info.hours', args: { amount: '12' } }" value="12" />
 | 
			
		||||
                <option v-t="{ path: 'info.days', args: { amount: '1' } }" value="24" />
 | 
			
		||||
                <option v-t="{ path: 'info.days', args: { amount: '3' } }" value="72" />
 | 
			
		||||
                <option v-t="{ path: 'info.weeks', args: { amount: '1' } }" value="168" />
 | 
			
		||||
                <option v-t="{ path: 'info.weeks', args: { amount: '3' } }" value="336" />
 | 
			
		||||
                <option v-t="{ path: 'info.months', args: { amount: '1' } }" value="672" />
 | 
			
		||||
                <option v-t="{ path: 'info.months', args: { amount: '2' } }" value="1344" />
 | 
			
		||||
            </select>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -32,28 +49,39 @@ export default {
 | 
			
		|||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            videos: [],
 | 
			
		||||
            autoDeleteHistory: false,
 | 
			
		||||
            autoDeleteDelayHours: "24",
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.autoDeleteHistory = this.getPreferenceBoolean("autoDeleteWatchHistory", false);
 | 
			
		||||
        this.autoDeleteDelayHours = this.getPreferenceString("autoDeleteWatchHistoryDelayHours", "24");
 | 
			
		||||
 | 
			
		||||
        (async () => {
 | 
			
		||||
            if (window.db) {
 | 
			
		||||
                var tx = window.db.transaction("watch_history", "readonly");
 | 
			
		||||
            if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
 | 
			
		||||
                var tx = window.db.transaction("watch_history", "readwrite");
 | 
			
		||||
                var store = tx.objectStore("watch_history");
 | 
			
		||||
                const cursorRequest = store.index("watchedAt").openCursor(null, "prev");
 | 
			
		||||
                cursorRequest.onsuccess = e => {
 | 
			
		||||
                    const cursor = e.target.result;
 | 
			
		||||
                    if (cursor) {
 | 
			
		||||
                        const video = cursor.value;
 | 
			
		||||
                        this.videos.push({
 | 
			
		||||
                            url: "/watch?v=" + video.videoId,
 | 
			
		||||
                            title: video.title,
 | 
			
		||||
                            uploaderName: video.uploaderName,
 | 
			
		||||
                            uploaderUrl: video.uploaderUrl,
 | 
			
		||||
                            duration: video.duration,
 | 
			
		||||
                            thumbnail: video.thumbnail,
 | 
			
		||||
                            watchedAt: video.watchedAt,
 | 
			
		||||
                        });
 | 
			
		||||
                        if (this.videos.length < 1000) cursor.continue();
 | 
			
		||||
                        if (!this.shouldRemoveVideo(video)) {
 | 
			
		||||
                            this.videos.push({
 | 
			
		||||
                                url: "/watch?v=" + video.videoId,
 | 
			
		||||
                                title: video.title,
 | 
			
		||||
                                uploaderName: video.uploaderName,
 | 
			
		||||
                                uploaderUrl: video.uploaderUrl,
 | 
			
		||||
                                duration: video.duration,
 | 
			
		||||
                                thumbnail: video.thumbnail,
 | 
			
		||||
                                watchedAt: video.watchedAt,
 | 
			
		||||
                                watched: true,
 | 
			
		||||
                                currentTime: video.currentTime,
 | 
			
		||||
                            });
 | 
			
		||||
                        } else {
 | 
			
		||||
                            store.delete(video.videoId);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +99,32 @@ export default {
 | 
			
		|||
            }
 | 
			
		||||
            this.videos = [];
 | 
			
		||||
        },
 | 
			
		||||
        exportHistory() {
 | 
			
		||||
            const dateStr = new Date().toISOString().split(".")[0];
 | 
			
		||||
            let json = {
 | 
			
		||||
                format: "Piped",
 | 
			
		||||
                version: 1,
 | 
			
		||||
                playlists: [
 | 
			
		||||
                    {
 | 
			
		||||
                        name: `Piped History ${dateStr}`,
 | 
			
		||||
                        type: "history",
 | 
			
		||||
                        visibility: "private",
 | 
			
		||||
                        videos: this.videos.map(video => "https://youtube.com" + video.url),
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            };
 | 
			
		||||
            this.download(JSON.stringify(json), `piped_history_${dateStr}.json`, "application/json");
 | 
			
		||||
        },
 | 
			
		||||
        onChange() {
 | 
			
		||||
            this.setPreference("autoDeleteWatchHistory", this.autoDeleteHistory);
 | 
			
		||||
            this.setPreference("autoDeleteWatchHistoryDelayHours", this.autoDeleteDelayHours);
 | 
			
		||||
        },
 | 
			
		||||
        shouldRemoveVideo(video) {
 | 
			
		||||
            if (!this.autoDeleteHistory) return false;
 | 
			
		||||
            // convert from hours to milliseconds
 | 
			
		||||
            let maximumTimeDiff = Number(this.autoDeleteDelayHours) * 60 * 60 * 1000;
 | 
			
		||||
            return Date.now() - video.watchedAt > maximumTimeDiff;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -106,10 +106,14 @@ export default {
 | 
			
		|||
                }
 | 
			
		||||
                // FreeTube DB
 | 
			
		||||
                else if (text.indexOf("allChannels") != -1) {
 | 
			
		||||
                    const json = JSON.parse(text);
 | 
			
		||||
                    json.subscriptions.forEach(item => {
 | 
			
		||||
                        this.subscriptions.push(item.id);
 | 
			
		||||
                    });
 | 
			
		||||
                    const lines = text.split("\n");
 | 
			
		||||
                    for (let line of lines) {
 | 
			
		||||
                        if (line === "") continue;
 | 
			
		||||
                        const json = JSON.parse(line);
 | 
			
		||||
                        json.subscriptions.forEach(item => {
 | 
			
		||||
                            this.subscriptions.push(item.id);
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // Google Takeout JSON
 | 
			
		||||
                else if (text.indexOf("contentDetails") != -1) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										47
									
								
								src/components/LoadingIndicatorPage.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/components/LoadingIndicatorPage.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div v-if="!showContent" class="min-h-[75vh] w-full flex items-center justify-center">
 | 
			
		||||
        <span id="spinner" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else>
 | 
			
		||||
        <slot />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        showContent: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
#spinner {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    width: 70rem;
 | 
			
		||||
    height: 70rem;
 | 
			
		||||
}
 | 
			
		||||
#spinner:after {
 | 
			
		||||
    content: " ";
 | 
			
		||||
    display: block;
 | 
			
		||||
    width: 54rem;
 | 
			
		||||
    height: 54rem;
 | 
			
		||||
    margin: 8rem;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
    border: 4rem solid var(--efy_text);
 | 
			
		||||
    border-color: var(--efy_text) transparent var(--efy_text) transparent;
 | 
			
		||||
    animation: spinner 1.2s linear infinite;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes spinner {
 | 
			
		||||
    0% {
 | 
			
		||||
        transform: rotate(0deg);
 | 
			
		||||
    }
 | 
			
		||||
    100% {
 | 
			
		||||
        transform: rotate(360deg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										70
									
								
								src/components/LoginPage.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/components/LoginPage.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <h1 v-t="'titles.login'" class="my-4 text-center font-bold" />
 | 
			
		||||
    <hr />
 | 
			
		||||
    <div class="text-center">
 | 
			
		||||
        <form class="children:pb-3">
 | 
			
		||||
            <div>
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="username"
 | 
			
		||||
                    class="input"
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    autocomplete="username"
 | 
			
		||||
                    :placeholder="$t('login.username')"
 | 
			
		||||
                    :aria-label="$t('login.username')"
 | 
			
		||||
                    @keyup.enter="login"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="password"
 | 
			
		||||
                    class="input"
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    autocomplete="password"
 | 
			
		||||
                    :placeholder="$t('login.password')"
 | 
			
		||||
                    :aria-label="$t('login.password')"
 | 
			
		||||
                    @keyup.enter="login"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <a v-t="'titles.login'" class="btn w-auto" @click="login" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            username: null,
 | 
			
		||||
            password: null,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        //TODO: Add Server Side check
 | 
			
		||||
        if (this.getAuthToken()) {
 | 
			
		||||
            this.$router.push("/");
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        document.title = this.$t("titles.login") + " - Piped";
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        login() {
 | 
			
		||||
            if (!this.username || !this.password) return;
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/login", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    username: this.username,
 | 
			
		||||
                    password: this.password,
 | 
			
		||||
                }),
 | 
			
		||||
            }).then(resp => {
 | 
			
		||||
                if (resp.token) {
 | 
			
		||||
                    this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token);
 | 
			
		||||
                    window.location = "/"; // done to bypass cache
 | 
			
		||||
                } else alert(resp.error);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +11,7 @@
 | 
			
		|||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    emits: ["close"],
 | 
			
		||||
    mounted() {
 | 
			
		||||
        window.addEventListener("keydown", this.handleKeyDown);
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +35,10 @@ export default {
 | 
			
		|||
 | 
			
		||||
<style>
 | 
			
		||||
.modal {
 | 
			
		||||
    @apply fixed z-50 top-0 left-0 w-full h-full bg-dark-900 bg-opacity-80 transition-opacity table;
 | 
			
		||||
    @apply fixed z-50 top-0 left-0 w-full h-full bg-gray bg-opacity-80 transition-opacity table;
 | 
			
		||||
}
 | 
			
		||||
.dark .modal {
 | 
			
		||||
    @apply bg-dark-900 bg-opacity-80;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modal > div {
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +46,7 @@ export default {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
.modal-container {
 | 
			
		||||
    @apply w-300rem m-auto max-w-[100vw] relative;
 | 
			
		||||
    @apply w-100% m-auto max-w-[410rem] relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modal-container > button {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,10 +56,10 @@
 | 
			
		|||
        </div>
 | 
			
		||||
        <div class="lt-md:hidden flex flex-1 justify-start" style="position: relative">
 | 
			
		||||
            <input
 | 
			
		||||
                ref="videoSearch"
 | 
			
		||||
                v-model="searchText"
 | 
			
		||||
                type="text"
 | 
			
		||||
                role="search"
 | 
			
		||||
                ref="videoSearch"
 | 
			
		||||
                :title="$t('actions.search')"
 | 
			
		||||
                :placeholder="$t('actions.search')"
 | 
			
		||||
                @keyup="onKeyUp"
 | 
			
		||||
| 
						 | 
				
			
			@ -78,8 +78,8 @@
 | 
			
		|||
            <router-link v-t="'titles.preferences'" to="/preferences" />
 | 
			
		||||
            <p
 | 
			
		||||
                v-if="shouldShowLogin"
 | 
			
		||||
                class="cursor-pointer font-bold"
 | 
			
		||||
                v-t="'titles.account'"
 | 
			
		||||
                class="cursor-pointer font-bold"
 | 
			
		||||
                @click="showLoginModal = !showLoginModal"
 | 
			
		||||
            />
 | 
			
		||||
            <router-link v-if="shouldShowHistory" v-t="'titles.history'" to="/history" />
 | 
			
		||||
| 
						 | 
				
			
			@ -87,15 +87,8 @@
 | 
			
		|||
            <router-link v-if="!shouldShowTrending" v-t="'titles.feed'" to="/feed" />
 | 
			
		||||
            <button
 | 
			
		||||
                efy_sidebar_btn="relative, pp-desktop"
 | 
			
		||||
                style="
 | 
			
		||||
                    background: transparent;
 | 
			
		||||
                    -webkit-text-fill-color: var(--efy_text);
 | 
			
		||||
                    padding: 0;
 | 
			
		||||
                    margin: -5rem 0 0 0;
 | 
			
		||||
                    border: 0;
 | 
			
		||||
                    backdrop-filter: none !important;
 | 
			
		||||
                    -webkit-backdrop-filter: none !important;
 | 
			
		||||
                "
 | 
			
		||||
                style="background: transparent; padding: 0; margin: -5rem 0 0 0; border: 0"
 | 
			
		||||
                class="efy_trans_filter_off efy_shadow_button_off"
 | 
			
		||||
            >
 | 
			
		||||
                <i efy_icon="menu" style="margin: 0" />
 | 
			
		||||
            </button>
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +108,7 @@
 | 
			
		|||
            @focus="onInputFocus"
 | 
			
		||||
            @blur="onInputBlur"
 | 
			
		||||
        />
 | 
			
		||||
        <span v-if="searchText" class="delete-search" @click="searchText = ''">x</span>
 | 
			
		||||
        <span v-if="searchText" class="delete-search" @click="searchText = ''">⨉</span>
 | 
			
		||||
    </div>
 | 
			
		||||
    <SearchSuggestions
 | 
			
		||||
        v-show="(searchText || showSearchHistory) && suggestionsVisible"
 | 
			
		||||
| 
						 | 
				
			
			@ -204,17 +197,17 @@ export default {
 | 
			
		|||
            suggestionsVisible: false,
 | 
			
		||||
            showLoginModal: false,
 | 
			
		||||
            showTopNav: false,
 | 
			
		||||
            homePagePath: "/",
 | 
			
		||||
            registrationDisabled: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        const query = new URLSearchParams(window.location.search).get("search_query");
 | 
			
		||||
        if (query) this.onSearchTextChange(query);
 | 
			
		||||
        this.focusOnSearchBar();
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        shouldShowLogin(_this) {
 | 
			
		||||
            return _this.getAuthToken() == null;
 | 
			
		||||
        },
 | 
			
		||||
        shouldShowRegister(_this) {
 | 
			
		||||
            return _this.registrationDisabled == false ? _this.shouldShowLogin : false;
 | 
			
		||||
        },
 | 
			
		||||
        shouldShowHistory(_this) {
 | 
			
		||||
            return _this.getPreferenceBoolean("watchHistory", false);
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -225,6 +218,13 @@ export default {
 | 
			
		|||
            return _this.getPreferenceBoolean("searchHistory", false) && localStorage.getItem("search_history");
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.fetchAuthConfig();
 | 
			
		||||
        const query = new URLSearchParams(window.location.search).get("search_query");
 | 
			
		||||
        if (query) this.onSearchTextChange(query);
 | 
			
		||||
        this.focusOnSearchBar();
 | 
			
		||||
        this.homePagePath = this.getHomePage(this);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        // focus on search bar when Ctrl+k is pressed
 | 
			
		||||
        focusOnSearchBar() {
 | 
			
		||||
| 
						 | 
				
			
			@ -241,12 +241,7 @@ export default {
 | 
			
		|||
        },
 | 
			
		||||
        onKeyPress(e) {
 | 
			
		||||
            if (e.key === "Enter") {
 | 
			
		||||
                e.target.blur();
 | 
			
		||||
                this.$router.push({
 | 
			
		||||
                    name: "SearchResults",
 | 
			
		||||
                    query: { search_query: this.searchText },
 | 
			
		||||
                });
 | 
			
		||||
                return;
 | 
			
		||||
                this.submitSearch(e);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        onInputFocus() {
 | 
			
		||||
| 
						 | 
				
			
			@ -259,6 +254,22 @@ export default {
 | 
			
		|||
        onSearchTextChange(searchText) {
 | 
			
		||||
            this.searchText = searchText;
 | 
			
		||||
        },
 | 
			
		||||
        async fetchAuthConfig() {
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/config").then(config => {
 | 
			
		||||
                this.registrationDisabled = config?.registrationDisabled === true;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onSearchClick(e) {
 | 
			
		||||
            this.submitSearch(e);
 | 
			
		||||
        },
 | 
			
		||||
        submitSearch(e) {
 | 
			
		||||
            e.target.blur();
 | 
			
		||||
            this.$router.push({
 | 
			
		||||
                name: "SearchResults",
 | 
			
		||||
                query: { search_query: this.searchText },
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div class="flex flex-col justify-center items-center min-h-[88vh]">
 | 
			
		||||
        <h1 class="font-bold !text-[170rem] mb-[-6vh]">404</h1>
 | 
			
		||||
        <h2 class="!text-[40rem]" v-t="'info.page_not_found'" />
 | 
			
		||||
        <a class="btn mt-128" href="/" v-t="'actions.back_to_home'" />
 | 
			
		||||
        <h2 v-t="'info.page_not_found'" class="!text-[40rem]" />
 | 
			
		||||
        <a v-t="'actions.back_to_home'" role="button" href="/" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,12 @@
 | 
			
		|||
            <option v-for="playlist in playlists" :value="playlist.id" :key="playlist.id" v-text="playlist.name" />
 | 
			
		||||
        </select>
 | 
			
		||||
        <div class="flex justify-end">
 | 
			
		||||
            <button ref="addButton" v-t="'actions.create_playlist'" class="btn" @click="onCreatePlaylist" />
 | 
			
		||||
            <button
 | 
			
		||||
                class="btn"
 | 
			
		||||
                @click="handleClick(selectedPlaylist)"
 | 
			
		||||
                ref="addButton"
 | 
			
		||||
                v-t="'actions.add_to_playlist'"
 | 
			
		||||
                class="btn"
 | 
			
		||||
                @click="handleClick(selectedPlaylist)"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </ModalComponent>
 | 
			
		||||
| 
						 | 
				
			
			@ -23,11 +24,16 @@ export default {
 | 
			
		|||
        ModalComponent,
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
        videoInfo: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
        videoId: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    emits: ["close"],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            playlists: [],
 | 
			
		||||
| 
						 | 
				
			
			@ -62,31 +68,25 @@ export default {
 | 
			
		|||
            this.$refs.addButton.disabled = true;
 | 
			
		||||
            this.processing = true;
 | 
			
		||||
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    videoId: this.videoId,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            }).then(json => {
 | 
			
		||||
            this.addVideosToPlaylist(playlistId, [this.videoId], [this.videoInfo]).then(json => {
 | 
			
		||||
                this.setPreference("selectedPlaylist" + this.hashCode(this.authApiUrl()), playlistId);
 | 
			
		||||
                this.$emit("close");
 | 
			
		||||
                if (json.error) alert(json.error);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async fetchPlaylists() {
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                },
 | 
			
		||||
            }).then(json => {
 | 
			
		||||
            this.getPlaylists().then(json => {
 | 
			
		||||
                this.playlists = json;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onCreatePlaylist() {
 | 
			
		||||
            const name = prompt(this.$t("actions.create_playlist"));
 | 
			
		||||
            if (!name) return;
 | 
			
		||||
            this.createPlaylist(name).then(json => {
 | 
			
		||||
                if (json.error) alert(json.error);
 | 
			
		||||
                else this.fetchPlaylists();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +1,24 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
    <div class="flex flex-col flex-justify-between">
 | 
			
		||||
        <router-link :to="props.item.url">
 | 
			
		||||
            <div class="relative">
 | 
			
		||||
                <img class="w-full" :src="props.item.thumbnail" loading="lazy" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <p>
 | 
			
		||||
                <span v-text="props.item.name" />
 | 
			
		||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.verified" icon="check" />
 | 
			
		||||
                <font-awesome-icon v-if="props.item.verified" class="ml-1.5" icon="check" />
 | 
			
		||||
            </p>
 | 
			
		||||
        </router-link>
 | 
			
		||||
        <p v-if="props.item.description" v-text="props.item.description" />
 | 
			
		||||
 | 
			
		||||
        <router-link v-if="props.item.uploaderUrl" class="link" :to="props.item.uploaderUrl">
 | 
			
		||||
            <p>
 | 
			
		||||
                <span v-text="props.item.uploader" />
 | 
			
		||||
                <font-awesome-icon class="ml-1.5" v-if="props.item.uploaderVerified" icon="check" />
 | 
			
		||||
                <span v-text="props.item.uploaderName" />
 | 
			
		||||
                <font-awesome-icon v-if="props.item.uploaderVerified" class="ml-1.5" icon="check" />
 | 
			
		||||
            </p>
 | 
			
		||||
        </router-link>
 | 
			
		||||
        <a v-else-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
 | 
			
		||||
 | 
			
		||||
        <a v-if="props.item.uploaderName" class="link" v-text="props.item.uploaderName" />
 | 
			
		||||
        <template v-if="props.item.videos >= 0">
 | 
			
		||||
            <br v-if="props.item.uploaderName" />
 | 
			
		||||
            <strong v-text="`${props.item.videos} ${$t('video.videos')}`" />
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +30,9 @@
 | 
			
		|||
 | 
			
		||||
<script setup>
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    item: Object,
 | 
			
		||||
    item: {
 | 
			
		||||
        type: Object,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,12 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <ErrorHandler v-if="playlist && playlist.error" :message="playlist.message" :error="playlist.error" />
 | 
			
		||||
 | 
			
		||||
    <div v-if="playlist" v-show="!playlist.error">
 | 
			
		||||
        <h1 class="text-center my-4" v-text="playlist.name" />
 | 
			
		||||
    <LoadingIndicatorPage v-show="!playlist?.error" :show-content="playlist">
 | 
			
		||||
        <h5 class="mb-1 ml-1" v-text="playlist.name" />
 | 
			
		||||
 | 
			
		||||
        <div class="flex justify-between items-center">
 | 
			
		||||
        <CollapsableText v-if="playlist?.description" :text="playlist.description" />
 | 
			
		||||
 | 
			
		||||
        <div class="mt-1 flex items-center justify-between">
 | 
			
		||||
            <div>
 | 
			
		||||
                <router-link class="link" :to="playlist.uploaderUrl || '/'">
 | 
			
		||||
                    <img :src="playlist.uploaderAvatar" loading="lazy" />
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +16,11 @@
 | 
			
		|||
            <div>
 | 
			
		||||
                <strong v-text="`${playlist.videos} ${$t('video.videos')}`" />
 | 
			
		||||
                <br />
 | 
			
		||||
                <button class="btn mr-1 ml-2" v-if="authenticated && !isPipedPlaylist" @click="clonePlaylist">
 | 
			
		||||
                <button v-if="!isPipedPlaylist" class="btn mx-1" @click="bookmarkPlaylist">
 | 
			
		||||
                    {{ $t(`actions.${isBookmarked ? "playlist_bookmarked" : "bookmark_playlist"}`)
 | 
			
		||||
                    }}<font-awesome-icon class="ml-3" icon="bookmark" />
 | 
			
		||||
                </button>
 | 
			
		||||
                <button v-if="authenticated && !isPipedPlaylist" class="btn mr-1 ml-2" @click="clonePlaylist">
 | 
			
		||||
                    {{ $t("actions.clone_playlist") }}<font-awesome-icon class="ml-3" icon="clone" />
 | 
			
		||||
                </button>
 | 
			
		||||
                <button class="btn mr-1" @click="downloadPlaylistAsTxt">
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +29,7 @@
 | 
			
		|||
                <a class="btn" :href="getRssUrl">
 | 
			
		||||
                    <font-awesome-icon icon="rss" />
 | 
			
		||||
                </a>
 | 
			
		||||
                <WatchOnYouTubeButton :link="`https://www.youtube.com/playlist?list=${this.$route.query.list}`" />
 | 
			
		||||
                <WatchOnButton :link="`https://www.youtube.com/playlist?list=${$route.query.list}`" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,29 +43,34 @@
 | 
			
		|||
                :index="index"
 | 
			
		||||
                :playlist-id="$route.query.list"
 | 
			
		||||
                :admin="admin"
 | 
			
		||||
                @remove="removeVideo(index)"
 | 
			
		||||
                height="94"
 | 
			
		||||
                width="168"
 | 
			
		||||
                @remove="removeVideo(index)"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    </LoadingIndicatorPage>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ErrorHandler from "./ErrorHandler.vue";
 | 
			
		||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
			
		||||
import CollapsableText from "./CollapsableText.vue";
 | 
			
		||||
import VideoItem from "./VideoItem.vue";
 | 
			
		||||
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
 | 
			
		||||
import WatchOnButton from "./WatchOnButton.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ErrorHandler,
 | 
			
		||||
        VideoItem,
 | 
			
		||||
        WatchOnYouTubeButton,
 | 
			
		||||
        WatchOnButton,
 | 
			
		||||
        LoadingIndicatorPage,
 | 
			
		||||
        CollapsableText,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            playlist: null,
 | 
			
		||||
            admin: false,
 | 
			
		||||
            isBookmarked: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
| 
						 | 
				
			
			@ -74,19 +85,17 @@ export default {
 | 
			
		|||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.getPlaylistData();
 | 
			
		||||
        const playlistId = this.$route.query.list;
 | 
			
		||||
        if (this.authenticated && playlistId?.length == 36)
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                },
 | 
			
		||||
            }).then(json => {
 | 
			
		||||
            this.getPlaylists().then(json => {
 | 
			
		||||
                if (json.error) alert(json.error);
 | 
			
		||||
                else if (json.filter(playlist => playlist.id === playlistId).length > 0) this.admin = true;
 | 
			
		||||
                else if (json.some(playlist => playlist.id === playlistId)) this.admin = true;
 | 
			
		||||
            });
 | 
			
		||||
        else if (playlistId.startsWith("local")) this.admin = true;
 | 
			
		||||
        this.isPlaylistBookmarked();
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        this.getPlaylistData();
 | 
			
		||||
        window.addEventListener("scroll", this.handleScroll);
 | 
			
		||||
        if (this.playlist) this.updateTitle();
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -95,12 +104,21 @@ export default {
 | 
			
		|||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        async fetchPlaylist() {
 | 
			
		||||
            const playlistId = this.$route.query.list;
 | 
			
		||||
            if (playlistId.startsWith("local")) {
 | 
			
		||||
                return this.getPlaylist(playlistId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await await this.fetchJson(this.authApiUrl() + "/playlists/" + this.$route.query.list);
 | 
			
		||||
        },
 | 
			
		||||
        async getPlaylistData() {
 | 
			
		||||
            this.fetchPlaylist()
 | 
			
		||||
                .then(data => (this.playlist = data))
 | 
			
		||||
                .then(() => this.updateTitle());
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    this.updateTitle();
 | 
			
		||||
                    this.updateWatched(this.playlist.relatedStreams);
 | 
			
		||||
                    this.fetchDeArrowContent(this.playlist.relatedStreams);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        async updateTitle() {
 | 
			
		||||
            document.title = this.playlist.name + " - Piped";
 | 
			
		||||
| 
						 | 
				
			
			@ -116,6 +134,7 @@ export default {
 | 
			
		|||
                    this.playlist.nextpage = json.nextpage;
 | 
			
		||||
                    this.loading = false;
 | 
			
		||||
                    json.relatedStreams.map(stream => this.playlist.relatedStreams.push(stream));
 | 
			
		||||
                    this.fetchDeArrowContent(this.playlist.relatedStreams);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +163,48 @@ export default {
 | 
			
		|||
            });
 | 
			
		||||
            this.download(data, this.playlist.name + ".txt", "text/plain");
 | 
			
		||||
        },
 | 
			
		||||
        async bookmarkPlaylist() {
 | 
			
		||||
            if (!this.playlist) return;
 | 
			
		||||
 | 
			
		||||
            if (this.isBookmarked) {
 | 
			
		||||
                this.removePlaylistBookmark();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (window.db) {
 | 
			
		||||
                const playlistId = this.$route.query.list;
 | 
			
		||||
                var tx = window.db.transaction("playlist_bookmarks", "readwrite");
 | 
			
		||||
                var store = tx.objectStore("playlist_bookmarks");
 | 
			
		||||
                store.put({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    name: this.playlist.name,
 | 
			
		||||
                    uploader: this.playlist.uploader,
 | 
			
		||||
                    uploaderUrl: this.playlist.uploaderUrl,
 | 
			
		||||
                    thumbnail: this.playlist.thumbnailUrl,
 | 
			
		||||
                    uploaderAvatar: this.playlist.uploaderAvatar,
 | 
			
		||||
                    videos: this.playlist.videos,
 | 
			
		||||
                });
 | 
			
		||||
                this.isBookmarked = true;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async removePlaylistBookmark() {
 | 
			
		||||
            var tx = window.db.transaction("playlist_bookmarks", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("playlist_bookmarks");
 | 
			
		||||
            store.delete(this.$route.query.list);
 | 
			
		||||
            this.isBookmarked = false;
 | 
			
		||||
        },
 | 
			
		||||
        async isPlaylistBookmarked() {
 | 
			
		||||
            // needed in order to change the is bookmarked var later
 | 
			
		||||
            const App = this;
 | 
			
		||||
            const playlistId = this.$route.query.list;
 | 
			
		||||
            var tx = window.db.transaction("playlist_bookmarks", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("playlist_bookmarks");
 | 
			
		||||
            var req = store.openCursor(playlistId);
 | 
			
		||||
            req.onsuccess = function (e) {
 | 
			
		||||
                var cursor = e.target.result;
 | 
			
		||||
                App.isBookmarked = cursor ? true : false;
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,18 +29,7 @@ export default {
 | 
			
		|||
        },
 | 
			
		||||
        selectedIndex: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.updateScroll();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        updateScroll() {
 | 
			
		||||
            const elems = Array.from(this.$refs.scrollable.children).filter(elm => elm.matches("div"));
 | 
			
		||||
            const index = this.selectedIndex - 1;
 | 
			
		||||
            if (index < elems.length)
 | 
			
		||||
                this.$refs.scrollable.scrollTop =
 | 
			
		||||
                    elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
| 
						 | 
				
			
			@ -54,5 +43,18 @@ export default {
 | 
			
		|||
            deep: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.updateScroll();
 | 
			
		||||
        this.updateWatched(this.playlist.relatedStreams);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        updateScroll() {
 | 
			
		||||
            const elems = Array.from(this.$refs.scrollable.children).filter(elm => elm.matches("div"));
 | 
			
		||||
            const index = this.selectedIndex - 1;
 | 
			
		||||
            if (index < elems.length)
 | 
			
		||||
                this.$refs.scrollable.scrollTop =
 | 
			
		||||
                    elems[this.selectedIndex - 1].offsetTop - this.$refs.scrollable.offsetTop;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,125 +1,153 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <h1 class="font-bold text-center my-4" v-t="'titles.playlists'" />
 | 
			
		||||
    <hr />
 | 
			
		||||
    <div class="flex justify-between">
 | 
			
		||||
        <button v-t="'actions.create_playlist'" class="btn mr-2" @click="onCreatePlaylist" />
 | 
			
		||||
        <div class="flex">
 | 
			
		||||
            <button v-if="playlists.length > 0" v-t="'actions.export_to_json'" @click="exportPlaylists" />
 | 
			
		||||
            <input id="fileSelector" ref="fileSelector" type="file" class="display-none" @change="importPlaylists" />
 | 
			
		||||
            <label for="fileSelector" v-t="'actions.import_from_json'" class="btn ml-2" role="button" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="video-grid">
 | 
			
		||||
        <div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
 | 
			
		||||
            <router-link :to="`/playlist?list=${playlist.id}`">
 | 
			
		||||
                <img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
 | 
			
		||||
                <p
 | 
			
		||||
                    style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 15rem"
 | 
			
		||||
                    class="flex link"
 | 
			
		||||
                    :title="playlist.name"
 | 
			
		||||
                    v-text="playlist.name"
 | 
			
		||||
                />
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <div class="pp-video-card-buttons flex gap-15rem children:m-0" style="flex-wrap: wrap">
 | 
			
		||||
                <button v-text="`${playlist.videos} ${$t('video.videos')}`" class="thumbnail-overlay thumbnail-right" />
 | 
			
		||||
                <button
 | 
			
		||||
                    v-t="'actions.edit_playlist'"
 | 
			
		||||
                    class="pp-color h-auto"
 | 
			
		||||
                    @click="showPlaylistEditModal(playlist)"
 | 
			
		||||
                />
 | 
			
		||||
                <button
 | 
			
		||||
                    v-t="'actions.delete_playlist'"
 | 
			
		||||
                    class="pp-color h-auto"
 | 
			
		||||
                    @click="playlistToDelete = playlist.id"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <ModalComponent v-if="playlist.id == playlistToEdit" @close="playlistToEdit = null">
 | 
			
		||||
                <div class="flex flex-col gap-2">
 | 
			
		||||
                    <h2 v-t="'actions.edit_playlist'" />
 | 
			
		||||
                    <input
 | 
			
		||||
                        v-model="newPlaylistName"
 | 
			
		||||
                        class="input"
 | 
			
		||||
                        type="text"
 | 
			
		||||
                        :placeholder="$t('actions.playlist_name')"
 | 
			
		||||
                    />
 | 
			
		||||
                    <input
 | 
			
		||||
                        v-model="newPlaylistDescription"
 | 
			
		||||
                        class="input"
 | 
			
		||||
                        type="text"
 | 
			
		||||
                        :placeholder="$t('actions.playlist_description')"
 | 
			
		||||
                    />
 | 
			
		||||
                    <button v-t="'actions.okay'" class="btn ml-auto" @click="editPlaylist(playlist)" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </ModalComponent>
 | 
			
		||||
            <ConfirmModal
 | 
			
		||||
                v-if="playlistToDelete == playlist.id"
 | 
			
		||||
                :message="$t('actions.delete_playlist_confirm')"
 | 
			
		||||
                @close="playlistToDelete = null"
 | 
			
		||||
                @confirm="onDeletePlaylist(playlist.id)"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr />
 | 
			
		||||
 | 
			
		||||
    <div>
 | 
			
		||||
        <div class="flex justify-between">
 | 
			
		||||
            <button v-t="'actions.create_playlist'" class="btn mr-2" @click="onCreatePlaylist" />
 | 
			
		||||
            <div class="flex">
 | 
			
		||||
                <button
 | 
			
		||||
                    v-if="this.playlists.length > 0"
 | 
			
		||||
                    v-t="'actions.export_to_json'"
 | 
			
		||||
                    class="btn"
 | 
			
		||||
                    @click="exportPlaylists"
 | 
			
		||||
                />
 | 
			
		||||
                <input
 | 
			
		||||
                    id="fileSelector"
 | 
			
		||||
                    ref="fileSelector"
 | 
			
		||||
                    type="file"
 | 
			
		||||
                    class="display-none"
 | 
			
		||||
                    @change="importPlaylists"
 | 
			
		||||
                />
 | 
			
		||||
                <label for="fileSelector" v-t="'actions.import_from_json'" class="btn ml-2" role="button" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    <h2 v-t="'titles.bookmarks'" class="my-4 font-bold" />
 | 
			
		||||
 | 
			
		||||
        <div class="video-grid">
 | 
			
		||||
            <div v-for="playlist in playlists" :key="playlist.id" class="efy_trans_filter">
 | 
			
		||||
                <router-link :to="`/playlist?list=${playlist.id}`">
 | 
			
		||||
                    <img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
 | 
			
		||||
                    <p
 | 
			
		||||
                        style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
			
		||||
                        class="flex link"
 | 
			
		||||
                        :title="playlist.name"
 | 
			
		||||
                        v-text="playlist.name"
 | 
			
		||||
                    />
 | 
			
		||||
                </router-link>
 | 
			
		||||
                <div class="pp-video-card-buttons flex gap-15rem children:m-0" style="flex-wrap: wrap">
 | 
			
		||||
                    <button
 | 
			
		||||
                        class="thumbnail-overlay thumbnail-right"
 | 
			
		||||
                        v-text="`${playlist.videos} ${$t('video.videos')}`"
 | 
			
		||||
                    />
 | 
			
		||||
                    <button
 | 
			
		||||
                        class="pp-color h-auto"
 | 
			
		||||
                        @click="renamePlaylist(playlist.id)"
 | 
			
		||||
                        v-t="'actions.rename_playlist'"
 | 
			
		||||
                    />
 | 
			
		||||
                    <button
 | 
			
		||||
                        class="pp-color h-auto"
 | 
			
		||||
                        @click="deletePlaylist(playlist.id)"
 | 
			
		||||
                        v-t="'actions.delete_playlist'"
 | 
			
		||||
                    />
 | 
			
		||||
    <div v-if="bookmarks" class="video-grid">
 | 
			
		||||
        <router-link
 | 
			
		||||
            v-for="(playlist, index) in bookmarks"
 | 
			
		||||
            :key="playlist.playlistId"
 | 
			
		||||
            :to="`/playlist?list=${playlist.playlistId}`"
 | 
			
		||||
        >
 | 
			
		||||
            <img class="w-full" :src="playlist.thumbnail" alt="thumbnail" />
 | 
			
		||||
            <div class="relative text-sm">
 | 
			
		||||
                <span class="thumbnail-overlay thumbnail-right" v-text="`${playlist.videos} ${$t('video.videos')}`" />
 | 
			
		||||
                <div class="absolute bottom-100px right-5px z-100 px-5px" @click.prevent="removeBookmark(index)">
 | 
			
		||||
                    <font-awesome-icon class="ml-3" icon="bookmark" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
            <p
 | 
			
		||||
                style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; margin: 0 0 0 10rem"
 | 
			
		||||
                class="link my-2 flex overflow-hidden"
 | 
			
		||||
                :title="playlist.name"
 | 
			
		||||
                v-text="playlist.name"
 | 
			
		||||
            />
 | 
			
		||||
            <a :href="playlist.uploaderUrl" class="flex items-center">
 | 
			
		||||
                <img class="h-32px w-32px rounded-full" :src="playlist.uploaderAvatar" />
 | 
			
		||||
                <span class="ml-3 hover:underline" v-text="playlist.uploader" />
 | 
			
		||||
            </a>
 | 
			
		||||
        </router-link>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
			
		||||
import ModalComponent from "./ModalComponent.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: { ConfirmModal, ModalComponent },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            playlists: [],
 | 
			
		||||
            bookmarks: [],
 | 
			
		||||
            playlistToDelete: null,
 | 
			
		||||
            playlistToEdit: null,
 | 
			
		||||
            newPlaylistName: "",
 | 
			
		||||
            newPlaylistDescription: "",
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        if (this.authenticated) this.fetchPlaylists();
 | 
			
		||||
        else this.$router.push("/login");
 | 
			
		||||
        this.fetchPlaylists();
 | 
			
		||||
        this.loadPlaylistBookmarks();
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        document.title = this.$t("titles.playlists") + " - Piped";
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        fetchPlaylists() {
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                },
 | 
			
		||||
            }).then(json => {
 | 
			
		||||
            this.getPlaylists().then(json => {
 | 
			
		||||
                this.playlists = json;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        renamePlaylist(id) {
 | 
			
		||||
            const newName = prompt(this.$t("actions.new_playlist_name"));
 | 
			
		||||
            if (!newName) return;
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: id,
 | 
			
		||||
                    newName: newName,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            }).then(json => {
 | 
			
		||||
                if (json.error) alert(json.error);
 | 
			
		||||
                else {
 | 
			
		||||
                    this.playlists.forEach((playlist, index) => {
 | 
			
		||||
                        if (playlist.id == id) {
 | 
			
		||||
                            this.playlists[index].name = newName;
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        showPlaylistEditModal(playlist) {
 | 
			
		||||
            this.newPlaylistName = playlist.name;
 | 
			
		||||
            this.newPlaylistDescription = playlist.description;
 | 
			
		||||
            this.playlistToEdit = playlist.id;
 | 
			
		||||
        },
 | 
			
		||||
        deletePlaylist(id) {
 | 
			
		||||
            if (confirm(this.$t("actions.delete_playlist_confirm")))
 | 
			
		||||
                this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
 | 
			
		||||
                    method: "POST",
 | 
			
		||||
                    body: JSON.stringify({
 | 
			
		||||
                        playlistId: id,
 | 
			
		||||
                    }),
 | 
			
		||||
                    headers: {
 | 
			
		||||
                        Authorization: this.getAuthToken(),
 | 
			
		||||
                        "Content-Type": "application/json",
 | 
			
		||||
                    },
 | 
			
		||||
                }).then(json => {
 | 
			
		||||
        editPlaylist(selectedPlaylist) {
 | 
			
		||||
            // save the new name and description since they could be overwritten during the http request
 | 
			
		||||
            const newName = this.newPlaylistName;
 | 
			
		||||
            const newDescription = this.newPlaylistDescription;
 | 
			
		||||
            if (newName != selectedPlaylist.name) {
 | 
			
		||||
                this.renamePlaylist(selectedPlaylist.id, newName).then(json => {
 | 
			
		||||
                    if (json.error) alert(json.error);
 | 
			
		||||
                    else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
 | 
			
		||||
                    else selectedPlaylist.name = newName;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            if (newDescription != selectedPlaylist.description) {
 | 
			
		||||
                this.changePlaylistDescription(selectedPlaylist.id, newDescription).then(json => {
 | 
			
		||||
                    if (json.error) alert(json.error);
 | 
			
		||||
                    else selectedPlaylist.description = newDescription;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            this.playlistToEdit = null;
 | 
			
		||||
        },
 | 
			
		||||
        onDeletePlaylist(id) {
 | 
			
		||||
            this.deletePlaylist(id).then(json => {
 | 
			
		||||
                if (json.error) alert(json.error);
 | 
			
		||||
                else this.playlists = this.playlists.filter(playlist => playlist.id !== id);
 | 
			
		||||
            });
 | 
			
		||||
            this.playlistToDelete = null;
 | 
			
		||||
        },
 | 
			
		||||
        onCreatePlaylist() {
 | 
			
		||||
            const name = prompt(this.$t("actions.create_playlist"));
 | 
			
		||||
| 
						 | 
				
			
			@ -129,19 +157,6 @@ export default {
 | 
			
		|||
                else this.fetchPlaylists();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async createPlaylist(name) {
 | 
			
		||||
            let json = await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    name: name,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            return json;
 | 
			
		||||
        },
 | 
			
		||||
        async exportPlaylists() {
 | 
			
		||||
            if (!this.playlists) return;
 | 
			
		||||
            let json = {
 | 
			
		||||
| 
						 | 
				
			
			@ -154,8 +169,8 @@ export default {
 | 
			
		|||
            this.download(JSON.stringify(json), "playlists.json", "application/json");
 | 
			
		||||
        },
 | 
			
		||||
        async fetchPlaylistJson(playlistId) {
 | 
			
		||||
            let playlist = await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
 | 
			
		||||
            let playlistJson = {
 | 
			
		||||
            let playlist = await this.getPlaylist(playlistId);
 | 
			
		||||
            return {
 | 
			
		||||
                name: playlist.name,
 | 
			
		||||
                // possible other types: history, watch later, ...
 | 
			
		||||
                type: "playlist",
 | 
			
		||||
| 
						 | 
				
			
			@ -164,57 +179,69 @@ export default {
 | 
			
		|||
                // list of the videos, starting with "https://youtube.com" to clarify that those are YT videos
 | 
			
		||||
                videos: playlist.relatedStreams.map(stream => "https://youtube.com" + stream.url),
 | 
			
		||||
            };
 | 
			
		||||
            return playlistJson;
 | 
			
		||||
        },
 | 
			
		||||
        async importPlaylists() {
 | 
			
		||||
            const file = this.$refs.fileSelector.files[0];
 | 
			
		||||
            const files = this.$refs.fileSelector.files;
 | 
			
		||||
            for (let file of files) {
 | 
			
		||||
                await this.importPlaylistFile(file);
 | 
			
		||||
            }
 | 
			
		||||
            window.location.reload();
 | 
			
		||||
        },
 | 
			
		||||
        async importPlaylistFile(file) {
 | 
			
		||||
            let text = await file.text();
 | 
			
		||||
            let tasks = [];
 | 
			
		||||
            // list of playlists exported from Piped
 | 
			
		||||
            if (text.includes("playlists")) {
 | 
			
		||||
            if (file.name.slice(-4).toLowerCase() == ".csv") {
 | 
			
		||||
                const lines = text.split("\n");
 | 
			
		||||
                const playlistName = lines[1].split(",")[4];
 | 
			
		||||
                const playlist = {
 | 
			
		||||
                    name: playlistName != "" ? playlistName : new Date().toJSON(),
 | 
			
		||||
                    videos: lines
 | 
			
		||||
                        .slice(4, lines.length)
 | 
			
		||||
                        .filter(line => line != "")
 | 
			
		||||
                        .slice(1)
 | 
			
		||||
                        .map(line => `https://youtube.com/watch?v=${line.split(",")[0]}`),
 | 
			
		||||
                };
 | 
			
		||||
                tasks.push(this.createPlaylistWithVideos(playlist));
 | 
			
		||||
            } else if (text.includes('"Piped"')) {
 | 
			
		||||
                // CSV from Google Takeout
 | 
			
		||||
                let playlists = JSON.parse(text).playlists;
 | 
			
		||||
                if (!playlists.length) {
 | 
			
		||||
                    alert(this.$t("actions.no_valid_playlists"));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                for (var i = 0; i < playlists.length; i++) {
 | 
			
		||||
                    tasks.push(this.createPlaylistWithVideos(playlists[i]));
 | 
			
		||||
                for (let playlist of playlists) {
 | 
			
		||||
                    tasks.push(this.createPlaylistWithVideos(playlist));
 | 
			
		||||
                }
 | 
			
		||||
                // CSV from Google Takeout
 | 
			
		||||
            } else if (file.name.slice(-4).toLowerCase() == ".csv") {
 | 
			
		||||
                const lines = text.split("\n");
 | 
			
		||||
                const playlist = {
 | 
			
		||||
                    name: lines[1].split(",")[4],
 | 
			
		||||
                    videos: lines
 | 
			
		||||
                        .slice(4, lines.length)
 | 
			
		||||
                        .filter(line => line != "")
 | 
			
		||||
                        .map(line => `https://youtube.com/watch?v=${line.split(",")[0]}`),
 | 
			
		||||
                };
 | 
			
		||||
                tasks.push(this.createPlaylistWithVideos(playlist));
 | 
			
		||||
            } else {
 | 
			
		||||
                alert(this.$t("actions.no_valid_playlists"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            await Promise.all(tasks);
 | 
			
		||||
            window.location.reload();
 | 
			
		||||
        },
 | 
			
		||||
        async createPlaylistWithVideos(playlist) {
 | 
			
		||||
            let newPlaylist = await this.createPlaylist(playlist.name);
 | 
			
		||||
            let videoIds = playlist.videos.map(url => url.substr(-11));
 | 
			
		||||
            await this.addVideosToPlaylist(newPlaylist.playlistId, videoIds);
 | 
			
		||||
        },
 | 
			
		||||
        async addVideosToPlaylist(playlistId, videoIds) {
 | 
			
		||||
            await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    videoIds: videoIds,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        async loadPlaylistBookmarks() {
 | 
			
		||||
            if (!window.db) return;
 | 
			
		||||
            var tx = window.db.transaction("playlist_bookmarks", "readonly");
 | 
			
		||||
            var store = tx.objectStore("playlist_bookmarks");
 | 
			
		||||
            const cursorRequest = store.openCursor();
 | 
			
		||||
            cursorRequest.onsuccess = e => {
 | 
			
		||||
                const cursor = e.target.result;
 | 
			
		||||
                if (cursor) {
 | 
			
		||||
                    this.bookmarks.push(cursor.value);
 | 
			
		||||
                    cursor.continue();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        async removeBookmark(index) {
 | 
			
		||||
            var tx = window.db.transaction("playlist_bookmarks", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("playlist_bookmarks");
 | 
			
		||||
            store.delete(this.bookmarks[index].playlistId);
 | 
			
		||||
            this.bookmarks.splice(index, 1);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <!--efy-->
 | 
			
		||||
    <div class="pp-pref-cards">
 | 
			
		||||
        <div efy_card="grid">
 | 
			
		||||
            <h2>Quick</h2>
 | 
			
		||||
| 
						 | 
				
			
			@ -96,6 +97,342 @@
 | 
			
		|||
                v-t="'actions.restore_preferences'"
 | 
			
		||||
                @click="restorePreferences()"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!--master-->
 | 
			
		||||
    <div class="flex">
 | 
			
		||||
        <button @click="$router.go(-1) || $router.push('/')">
 | 
			
		||||
            <font-awesome-icon icon="chevron-left" /><span v-t="'actions.back'" class="ml-1.5" />
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <h1 v-t="'titles.preferences'" class="text-center font-bold" />
 | 
			
		||||
    <hr />
 | 
			
		||||
    <label for="ddlTheme" class="pref">
 | 
			
		||||
        <strong v-t="'actions.theme'" />
 | 
			
		||||
        <select id="ddlTheme" v-model="selectedTheme" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
            <option v-t="'actions.auto'" value="auto" />
 | 
			
		||||
            <option v-t="'actions.dark'" value="dark" />
 | 
			
		||||
            <option v-t="'actions.light'" value="light" />
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="ddlLanguageSelection">
 | 
			
		||||
        <strong v-t="'actions.language_selection'" />
 | 
			
		||||
        <select id="ddlLanguageSelection" v-model="selectedLanguage" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
            <option v-for="language in languages" :key="language.code" :value="language.code" v-text="language.name" />
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="ddlCountrySelection">
 | 
			
		||||
        <strong v-t="'actions.country_selection'" />
 | 
			
		||||
        <select id="ddlCountrySelection" v-model="countrySelected" class="select w-50" @change="onChange($event)">
 | 
			
		||||
            <option v-for="country in countryMap" :key="country.code" :value="country.code" v-text="country.name" />
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="ddlDefaultHomepage">
 | 
			
		||||
        <strong v-t="'actions.default_homepage'" />
 | 
			
		||||
        <select id="ddlDefaultHomepage" v-model="defaultHomepage" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
            <option v-t="'titles.trending'" value="trending" />
 | 
			
		||||
            <option v-t="'titles.feed'" value="feed" />
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
 | 
			
		||||
    <h2 v-t="'titles.player'" class="text-center" />
 | 
			
		||||
    <label class="pref" for="chkAutoPlayVideo">
 | 
			
		||||
        <strong v-t="'actions.autoplay_video'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkAutoPlayVideo"
 | 
			
		||||
            v-model="autoPlayVideo"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkAutoDisplayCaptions">
 | 
			
		||||
        <strong v-t="'actions.auto_display_captions'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkAutoDisplayCaptions"
 | 
			
		||||
            v-model="autoDisplayCaptions"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkAutoPlayNextCountdown">
 | 
			
		||||
        <strong v-t="'actions.autoplay_next_countdown'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkAutoPlayNextCountdown"
 | 
			
		||||
            v-model="autoPlayNextCountdown"
 | 
			
		||||
            class="input w-24"
 | 
			
		||||
            type="number"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkAudioOnly">
 | 
			
		||||
        <strong v-t="'actions.audio_only'" />
 | 
			
		||||
        <input id="chkAudioOnly" v-model="listen" class="checkbox" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="ddlDefaultQuality">
 | 
			
		||||
        <strong v-t="'actions.default_quality'" />
 | 
			
		||||
        <select id="ddlDefaultQuality" v-model="defaultQuality" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
            <option v-t="'actions.auto'" value="0" />
 | 
			
		||||
            <option v-for="resolution in resolutions" :key="resolution" :value="resolution" v-text="`${resolution}p`" />
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="txtBufferingGoal">
 | 
			
		||||
        <strong v-t="'actions.buffering_goal'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="txtBufferingGoal"
 | 
			
		||||
            v-model="bufferingGoal"
 | 
			
		||||
            class="input w-24"
 | 
			
		||||
            type="text"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkMinimizeComments">
 | 
			
		||||
        <strong v-t="'actions.minimize_comments_default'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkMinimizeComments"
 | 
			
		||||
            v-model="minimizeComments"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkMinimizeDescription">
 | 
			
		||||
        <strong v-t="'actions.minimize_description_default'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkMinimizeDescription"
 | 
			
		||||
            v-model="minimizeDescription"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkMinimizeRecommendations">
 | 
			
		||||
        <strong v-t="'actions.minimize_recommendations_default'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkMinimizeRecommendations"
 | 
			
		||||
            v-model="minimizeRecommendations"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkMinimizeChapters">
 | 
			
		||||
        <strong v-t="'actions.minimize_chapters_default'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkMinimizeChapters"
 | 
			
		||||
            v-model="minimizeChapters"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <!-- chapters layout on mobile -->
 | 
			
		||||
    <label class="pref lg:invisible" for="chkMinimizeChapters">
 | 
			
		||||
        <strong v-t="'actions.chapters_layout_mobile'" />
 | 
			
		||||
 | 
			
		||||
        <select id="ddlDefaultHomepage" v-model="mobileChapterLayout" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
            <option v-t="'video.chapters_horizontal'" value="Horizontal" />
 | 
			
		||||
            <option v-t="'video.chapters_vertical'" value="Vertical" />
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkShowWatchOnYouTube">
 | 
			
		||||
        <strong v-t="'actions.show_watch_on_youtube'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkShowWatchOnYouTube"
 | 
			
		||||
            v-model="showWatchOnYouTube"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkShowSearchSuggestions">
 | 
			
		||||
        <strong v-t="'actions.show_search_suggestions'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkShowSearchSuggestions"
 | 
			
		||||
            v-model="searchSuggestions"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkStoreSearchHistory">
 | 
			
		||||
        <strong v-t="'actions.store_search_history'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkStoreSearchHistory"
 | 
			
		||||
            v-model="searchHistory"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkStoreWatchHistory">
 | 
			
		||||
        <strong v-t="'actions.store_watch_history'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkStoreWatchHistory"
 | 
			
		||||
            v-model="watchHistory"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label v-if="watchHistory" class="pref" for="chkHideWatched">
 | 
			
		||||
        <strong v-t="'actions.hide_watched'" />
 | 
			
		||||
        <input id="chkHideWatched" v-model="hideWatched" class="checkbox" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="ddlEnabledCodecs">
 | 
			
		||||
        <strong v-t="'actions.enabled_codecs'" />
 | 
			
		||||
        <select
 | 
			
		||||
            id="ddlEnabledCodecs"
 | 
			
		||||
            v-model="enabledCodecs"
 | 
			
		||||
            class="select h-auto w-auto"
 | 
			
		||||
            multiple
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        >
 | 
			
		||||
            <option value="av1">AV1</option>
 | 
			
		||||
            <option value="vp9">VP9</option>
 | 
			
		||||
            <option value="avc">AVC (h.264)</option>
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkDisableLBRY">
 | 
			
		||||
        <strong v-t="'actions.disable_lbry'" />
 | 
			
		||||
        <input id="chkDisableLBRY" v-model="disableLBRY" class="checkbox" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkEnableLBRYProxy">
 | 
			
		||||
        <strong v-t="'actions.enable_lbry_proxy'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkEnableLBRYProxy"
 | 
			
		||||
            v-model="proxyLBRY"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
 | 
			
		||||
    <h2 class="text-center">SponsorBlock</h2>
 | 
			
		||||
    <p class="text-center">
 | 
			
		||||
        <span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
 | 
			
		||||
    </p>
 | 
			
		||||
    <label class="pref" for="chkEnableSponsorblock">
 | 
			
		||||
        <strong v-t="'actions.enable_sponsorblock'" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkEnableSponsorblock"
 | 
			
		||||
            v-model="sponsorBlock"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <div v-if="sponsorBlock">
 | 
			
		||||
        <label v-for="[name, item] in skipOptions" :key="name" class="pref" :for="'ddlSkip_' + name">
 | 
			
		||||
            <strong v-t="item.label" />
 | 
			
		||||
            <select :id="'ddlSkip_' + name" v-model="item.value" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
                <option v-t="'actions.no'" value="no" />
 | 
			
		||||
                <option v-t="'actions.skip_button_only'" value="button" />
 | 
			
		||||
                <option v-t="'actions.skip_automatically'" value="auto" />
 | 
			
		||||
            </select>
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="pref" for="chkShowMarkers">
 | 
			
		||||
            <strong v-t="'actions.show_markers'" />
 | 
			
		||||
            <input
 | 
			
		||||
                id="chkShowMarkers"
 | 
			
		||||
                v-model="showMarkers"
 | 
			
		||||
                class="checkbox"
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                @change="onChange($event)"
 | 
			
		||||
            />
 | 
			
		||||
        </label>
 | 
			
		||||
        <label class="pref" for="txtMinSegmentLength">
 | 
			
		||||
            <strong v-t="'actions.min_segment_length'" />
 | 
			
		||||
            <input
 | 
			
		||||
                id="txtMinSegmentLength"
 | 
			
		||||
                v-model="minSegmentLength"
 | 
			
		||||
                class="input w-24"
 | 
			
		||||
                type="text"
 | 
			
		||||
                @change="onChange($event)"
 | 
			
		||||
            />
 | 
			
		||||
        </label>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <h2 v-t="'titles.dearrow'" class="text-center" />
 | 
			
		||||
    <p class="text-center">
 | 
			
		||||
        <span v-t="'actions.uses_api_from'" /><a class="link" href="https://sponsor.ajay.app/">sponsor.ajay.app</a>
 | 
			
		||||
    </p>
 | 
			
		||||
    <label class="pref" for="chkDeArrow">
 | 
			
		||||
        <strong v-t="'actions.enable_dearrow'" />
 | 
			
		||||
        <input id="chkDeArrow" v-model="dearrow" class="checkbox" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
    </label>
 | 
			
		||||
 | 
			
		||||
    <h2 v-t="'titles.instance'" class="text-center" />
 | 
			
		||||
    <label class="pref" for="ddlInstanceSelection">
 | 
			
		||||
        <strong v-text="`${$t('actions.instance_selection')}:`" />
 | 
			
		||||
        <select id="ddlInstanceSelection" v-model="selectedInstance" class="select w-auto" @change="onChange($event)">
 | 
			
		||||
            <option
 | 
			
		||||
                v-for="instance in instances"
 | 
			
		||||
                :key="instance.name"
 | 
			
		||||
                :value="instance.api_url"
 | 
			
		||||
                v-text="instance.name"
 | 
			
		||||
            />
 | 
			
		||||
        </select>
 | 
			
		||||
    </label>
 | 
			
		||||
    <label class="pref" for="chkAuthInstance">
 | 
			
		||||
        <strong v-text="`${$t('actions.different_auth_instance')}:`" />
 | 
			
		||||
        <input
 | 
			
		||||
            id="chkAuthInstance"
 | 
			
		||||
            v-model="authInstance"
 | 
			
		||||
            class="checkbox"
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            @change="onChange($event)"
 | 
			
		||||
        />
 | 
			
		||||
    </label>
 | 
			
		||||
    <template v-if="authInstance">
 | 
			
		||||
        <label class="pref" for="ddlAuthInstanceSelection">
 | 
			
		||||
            <strong v-text="`${$t('actions.instance_auth_selection')}:`" />
 | 
			
		||||
            <select
 | 
			
		||||
                id="ddlAuthInstanceSelection"
 | 
			
		||||
                v-model="selectedAuthInstance"
 | 
			
		||||
                class="select w-auto"
 | 
			
		||||
                @change="onChange($event)"
 | 
			
		||||
            >
 | 
			
		||||
                <option
 | 
			
		||||
                    v-for="instance in instances"
 | 
			
		||||
                    :key="instance.name"
 | 
			
		||||
                    :value="instance.api_url"
 | 
			
		||||
                    v-text="instance.name"
 | 
			
		||||
                />
 | 
			
		||||
            </select>
 | 
			
		||||
        </label>
 | 
			
		||||
    </template>
 | 
			
		||||
    <br />
 | 
			
		||||
 | 
			
		||||
    <!-- options that are visible only when logged in -->
 | 
			
		||||
    <div v-if="authenticated">
 | 
			
		||||
        <h2 v-t="'titles.account'" class="text-center"></h2>
 | 
			
		||||
        <label class="pref" for="txtDeleteAccountPassword">
 | 
			
		||||
            <strong v-t="'actions.delete_account'" />
 | 
			
		||||
            <div class="flex items-center">
 | 
			
		||||
                <input
 | 
			
		||||
                    id="txtDeleteAccountPassword"
 | 
			
		||||
                    ref="txtDeleteAccountPassword"
 | 
			
		||||
                    v-model="password"
 | 
			
		||||
                    :placeholder="$t('login.password')"
 | 
			
		||||
                    :aria-label="$t('login.password')"
 | 
			
		||||
                    class="input mr-2 w-auto"
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    @keyup.enter="deleteAccount"
 | 
			
		||||
                />
 | 
			
		||||
                <a v-t="'actions.delete_account'" class="btn w-auto" @click="deleteAccount" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </label>
 | 
			
		||||
        <div class="pref">
 | 
			
		||||
            <a v-t="'actions.logout'" class="btn w-auto" @click="logout" />
 | 
			
		||||
            <a
 | 
			
		||||
                v-t="'actions.invalidate_session'"
 | 
			
		||||
                class="btn w-auto"
 | 
			
		||||
                style="margin-left: 0.5em"
 | 
			
		||||
                @click="invalidateSession"
 | 
			
		||||
            />
 | 
			
		||||
            <!--masterend-->
 | 
			
		||||
            <input class="hidden" id="fileSelector" ref="fileSelector" type="file" @change="restorePreferences()" />
 | 
			
		||||
 | 
			
		||||
            <!-- options that are visible only when logged in -->
 | 
			
		||||
| 
						 | 
				
			
			@ -406,7 +743,7 @@
 | 
			
		|||
                <th v-t="'preferences.instance_locations'" />
 | 
			
		||||
                <th v-t="'preferences.has_cdn'" />
 | 
			
		||||
                <th v-t="'preferences.registered_users'" />
 | 
			
		||||
                <th class="lt-md:hidden" v-t="'preferences.version'" />
 | 
			
		||||
                <th v-t="'preferences.version'" class="lt-md:hidden" />
 | 
			
		||||
                <th v-t="'preferences.up_to_date'" />
 | 
			
		||||
                <th v-t="'preferences.ssl_score'" />
 | 
			
		||||
            </tr>
 | 
			
		||||
| 
						 | 
				
			
			@ -420,35 +757,61 @@
 | 
			
		|||
                <td class="lt-md:hidden" v-text="instance.version" />
 | 
			
		||||
                <td v-text="`${instance.up_to_date ? '✅' : '❌'}`" />
 | 
			
		||||
                <td>
 | 
			
		||||
                    <a :href="sslScore(instance.api_url)" target="_blank" v-t="'actions.view_ssl_score'" />
 | 
			
		||||
                    <a v-t="'actions.view_ssl_score'" :href="sslScore(instance.api_url)" target="_blank" />
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    <!--master-->
 | 
			
		||||
    <br />
 | 
			
		||||
    <p v-t="'info.preferences_note'" />
 | 
			
		||||
    <br />
 | 
			
		||||
    <button v-t="'actions.reset_preferences'" class="btn" @click="showConfirmResetPrefsDialog = true" />
 | 
			
		||||
    <button v-t="'actions.backup_preferences'" class="btn mx-4" @click="backupPreferences()" />
 | 
			
		||||
    <label v-t="'actions.restore_preferences'" for="fileSelector" class="btn" @click="restorePreferences()" />
 | 
			
		||||
    <input id="fileSelector" ref="fileSelector" class="hidden" type="file" @change="restorePreferences()" />
 | 
			
		||||
    <ConfirmModal
 | 
			
		||||
        v-if="showConfirmResetPrefsDialog"
 | 
			
		||||
        :message="$t('actions.confirm_reset_preferences')"
 | 
			
		||||
        @close="showConfirmResetPrefsDialog = false"
 | 
			
		||||
        @confirm="resetPreferences()"
 | 
			
		||||
    />
 | 
			
		||||
    <!--masterend-->
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import CountryMap from "@/utils/CountryMaps/en.json";
 | 
			
		||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ConfirmModal,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            mobileChapterLayout: "Vertical",
 | 
			
		||||
            selectedInstance: null,
 | 
			
		||||
            authInstance: false,
 | 
			
		||||
            selectedAuthInstance: null,
 | 
			
		||||
            instances: [],
 | 
			
		||||
            sponsorBlock: true,
 | 
			
		||||
            skipSponsor: true,
 | 
			
		||||
            skipIntro: false,
 | 
			
		||||
            skipOutro: false,
 | 
			
		||||
            skipPreview: false,
 | 
			
		||||
            skipInteraction: true,
 | 
			
		||||
            skipSelfPromo: true,
 | 
			
		||||
            skipMusicOffTopic: true,
 | 
			
		||||
            skipHighlight: false,
 | 
			
		||||
            skipFiller: false,
 | 
			
		||||
            skipOptions: new Map([
 | 
			
		||||
                ["sponsor", { value: "auto", label: "actions.skip_sponsors" }],
 | 
			
		||||
                ["intro", { value: "no", label: "actions.skip_intro" }],
 | 
			
		||||
                ["outro", { value: "no", label: "actions.skip_outro" }],
 | 
			
		||||
                ["preview", { value: "no", label: "actions.skip_preview" }],
 | 
			
		||||
                ["interaction", { value: "auto", label: "actions.skip_interaction" }],
 | 
			
		||||
                ["selfpromo", { value: "auto", label: "actions.skip_self_promo" }],
 | 
			
		||||
                ["music_offtopic", { value: "auto", label: "actions.skip_non_music" }],
 | 
			
		||||
                ["poi_highlight", { value: "no", label: "actions.skip_highlight" }],
 | 
			
		||||
                ["filler", { value: "no", label: "actions.skip_filler_tangent" }],
 | 
			
		||||
            ]),
 | 
			
		||||
            showMarkers: true,
 | 
			
		||||
            minSegmentLength: 0,
 | 
			
		||||
            dearrow: false,
 | 
			
		||||
            selectedTheme: "dark",
 | 
			
		||||
            autoPlayVideo: true,
 | 
			
		||||
            autoDisplayCaptions: false,
 | 
			
		||||
            autoPlayNextCountdown: 5,
 | 
			
		||||
            listen: false,
 | 
			
		||||
            resolutions: [144, 240, 360, 480, 720, 1080, 1440, 2160, 4320],
 | 
			
		||||
            defaultQuality: 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -461,6 +824,7 @@ export default {
 | 
			
		|||
            minimizeRecommendations: false,
 | 
			
		||||
            minimizeChapters: false,
 | 
			
		||||
            showWatchOnYouTube: false,
 | 
			
		||||
            searchSuggestions: true,
 | 
			
		||||
            watchHistory: false,
 | 
			
		||||
            searchHistory: false,
 | 
			
		||||
            hideWatched: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -468,6 +832,7 @@ export default {
 | 
			
		|||
            languages: [
 | 
			
		||||
                { code: "ar", name: "Arabic" },
 | 
			
		||||
                { code: "az", name: "Azərbaycan" },
 | 
			
		||||
                { code: "bg", name: "Български" },
 | 
			
		||||
                { code: "bn", name: "বাংলা" },
 | 
			
		||||
                { code: "bs", name: "Bosanski" },
 | 
			
		||||
                { code: "ca", name: "Català" },
 | 
			
		||||
| 
						 | 
				
			
			@ -495,6 +860,7 @@ export default {
 | 
			
		|||
                { code: "ml", name: "മലയാളം" },
 | 
			
		||||
                { code: "nb_NO", name: "Norwegian Bokmål" },
 | 
			
		||||
                { code: "nl", name: "Nederlands" },
 | 
			
		||||
                { code: "oc", name: "Occitan" },
 | 
			
		||||
                { code: "or", name: "ଓଡ଼ିଆ" },
 | 
			
		||||
                { code: "pl", name: "Polski" },
 | 
			
		||||
                { code: "pt", name: "Português" },
 | 
			
		||||
| 
						 | 
				
			
			@ -502,6 +868,7 @@ export default {
 | 
			
		|||
                { code: "pt_BR", name: "Português (Brasil)" },
 | 
			
		||||
                { code: "ro", name: "Română" },
 | 
			
		||||
                { code: "ru", name: "Русский" },
 | 
			
		||||
                { code: "si", name: "සිංහල" },
 | 
			
		||||
                { code: "sr", name: "Српски" },
 | 
			
		||||
                { code: "sv", name: "Svenska" },
 | 
			
		||||
                { code: "ta", name: "தமிழ்" },
 | 
			
		||||
| 
						 | 
				
			
			@ -516,6 +883,7 @@ export default {
 | 
			
		|||
            disableLBRY: false,
 | 
			
		||||
            proxyLBRY: false,
 | 
			
		||||
            password: null,
 | 
			
		||||
            showConfirmResetPrefsDialog: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
| 
						 | 
				
			
			@ -526,7 +894,7 @@ export default {
 | 
			
		|||
 | 
			
		||||
        this.fetchJson("https://piped-instances.kavin.rocks/").then(resp => {
 | 
			
		||||
            this.instances = resp;
 | 
			
		||||
            if (this.instances.filter(instance => instance.api_url == this.apiUrl()).length == 0)
 | 
			
		||||
            if (!this.instances.some(instance => instance.api_url == this.apiUrl()))
 | 
			
		||||
                this.instances.push({
 | 
			
		||||
                    name: "Custom Instance",
 | 
			
		||||
                    api_url: this.apiUrl(),
 | 
			
		||||
| 
						 | 
				
			
			@ -541,57 +909,30 @@ export default {
 | 
			
		|||
            this.selectedAuthInstance = this.getPreferenceString("auth_instance_url", this.selectedInstance);
 | 
			
		||||
 | 
			
		||||
            this.sponsorBlock = this.getPreferenceBoolean("sponsorblock", true);
 | 
			
		||||
            if (localStorage.getItem("selectedSkip") !== null) {
 | 
			
		||||
                var skipList = localStorage.getItem("selectedSkip").split(",");
 | 
			
		||||
                this.skipSponsor =
 | 
			
		||||
                    this.skipIntro =
 | 
			
		||||
                    this.skipOutro =
 | 
			
		||||
                    this.skipPreview =
 | 
			
		||||
                    this.skipInteraction =
 | 
			
		||||
                    this.skipSelfPromo =
 | 
			
		||||
                    this.skipMusicOffTopic =
 | 
			
		||||
                    this.skipHighlight =
 | 
			
		||||
                    this.skipFiller =
 | 
			
		||||
                        false;
 | 
			
		||||
            var skipOptions, skipList;
 | 
			
		||||
            if ((skipOptions = this.getPreferenceJSON("skipOptions")) !== undefined) {
 | 
			
		||||
                Object.entries(skipOptions).forEach(([key, value]) => {
 | 
			
		||||
                    var opt = this.skipOptions.get(key);
 | 
			
		||||
                    if (opt !== undefined) opt.value = value;
 | 
			
		||||
                    else console.log("Unknown sponsor type: " + key);
 | 
			
		||||
                });
 | 
			
		||||
            } else if ((skipList = this.getPreferenceString("selectedSkip")) !== undefined) {
 | 
			
		||||
                skipList = skipList.split(",");
 | 
			
		||||
                this.skipOptions.forEach(opt => (opt.value = "no"));
 | 
			
		||||
                skipList.forEach(skip => {
 | 
			
		||||
                    switch (skip) {
 | 
			
		||||
                        case "sponsor":
 | 
			
		||||
                            this.skipSponsor = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "intro":
 | 
			
		||||
                            this.skipIntro = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "outro":
 | 
			
		||||
                            this.skipOutro = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "preview":
 | 
			
		||||
                            this.skipPreview = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "interaction":
 | 
			
		||||
                            this.skipInteraction = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "selfpromo":
 | 
			
		||||
                            this.skipSelfPromo = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "music_offtopic":
 | 
			
		||||
                            this.skipMusicOffTopic = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "poi_highlight":
 | 
			
		||||
                            this.skipHighlight = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "filler":
 | 
			
		||||
                            this.skipFiller = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        default:
 | 
			
		||||
                            console.log("Unknown sponsor type: " + skip);
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                    var opt = this.skipOptions.get(skip);
 | 
			
		||||
                    if (opt !== undefined) opt.value = "auto";
 | 
			
		||||
                    else console.log("Unknown sponsor type: " + skip);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.showMarkers = this.getPreferenceBoolean("showMarkers", true);
 | 
			
		||||
            this.minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
 | 
			
		||||
            this.dearrow = this.getPreferenceBoolean("dearrow", false);
 | 
			
		||||
            this.selectedTheme = this.getPreferenceString("theme", "dark");
 | 
			
		||||
            this.autoPlayVideo = this.getPreferenceBoolean("playerAutoPlay", true);
 | 
			
		||||
            this.autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
 | 
			
		||||
            this.autoPlayNextCountdown = this.getPreferenceNumber("autoPlayNextCountdown", 5);
 | 
			
		||||
            this.listen = this.getPreferenceBoolean("listen", false);
 | 
			
		||||
            this.defaultQuality = Number(localStorage.getItem("quality"));
 | 
			
		||||
            this.bufferingGoal = Math.max(Number(localStorage.getItem("bufferGoal")), 10);
 | 
			
		||||
| 
						 | 
				
			
			@ -602,6 +943,7 @@ export default {
 | 
			
		|||
            this.minimizeRecommendations = this.getPreferenceBoolean("minimizeRecommendations", false);
 | 
			
		||||
            this.minimizeChapters = this.getPreferenceBoolean("minimizeChapters", false);
 | 
			
		||||
            this.showWatchOnYouTube = this.getPreferenceBoolean("showWatchOnYouTube", false);
 | 
			
		||||
            this.searchSuggestions = this.getPreferenceBoolean("searchSuggestions", true);
 | 
			
		||||
            this.watchHistory = this.getPreferenceBoolean("watchHistory", false);
 | 
			
		||||
            this.searchHistory = this.getPreferenceBoolean("searchHistory", false);
 | 
			
		||||
            this.selectedLanguage = this.getPreferenceString("hl", await this.defaultLanguage);
 | 
			
		||||
| 
						 | 
				
			
			@ -609,6 +951,7 @@ export default {
 | 
			
		|||
            this.disableLBRY = this.getPreferenceBoolean("disableLBRY", false);
 | 
			
		||||
            this.proxyLBRY = this.getPreferenceBoolean("proxyLBRY", false);
 | 
			
		||||
            this.hideWatched = this.getPreferenceBoolean("hideWatched", false);
 | 
			
		||||
            this.mobileChapterLayout = this.getPreferenceString("mobileChapterLayout", "Vertical");
 | 
			
		||||
            if (this.selectedLanguage != "en") {
 | 
			
		||||
                try {
 | 
			
		||||
                    this.CountryMap = await import(`../utils/CountryMaps/${this.selectedLanguage}.json`).then(
 | 
			
		||||
| 
						 | 
				
			
			@ -638,21 +981,19 @@ export default {
 | 
			
		|||
                localStorage.setItem("auth_instance_url", this.selectedAuthInstance);
 | 
			
		||||
                localStorage.setItem("sponsorblock", this.sponsorBlock);
 | 
			
		||||
 | 
			
		||||
                var sponsorSelected = [];
 | 
			
		||||
                if (this.skipSponsor) sponsorSelected.push("sponsor");
 | 
			
		||||
                if (this.skipIntro) sponsorSelected.push("intro");
 | 
			
		||||
                if (this.skipOutro) sponsorSelected.push("outro");
 | 
			
		||||
                if (this.skipPreview) sponsorSelected.push("preview");
 | 
			
		||||
                if (this.skipInteraction) sponsorSelected.push("interaction");
 | 
			
		||||
                if (this.skipSelfPromo) sponsorSelected.push("selfpromo");
 | 
			
		||||
                if (this.skipMusicOffTopic) sponsorSelected.push("music_offtopic");
 | 
			
		||||
                if (this.skipHighlight) sponsorSelected.push("poi_highlight");
 | 
			
		||||
                if (this.skipFiller) sponsorSelected.push("filler");
 | 
			
		||||
                localStorage.setItem("selectedSkip", sponsorSelected);
 | 
			
		||||
                var skipOptions = {};
 | 
			
		||||
                this.skipOptions.forEach((v, k) => (skipOptions[k] = v.value));
 | 
			
		||||
                localStorage.setItem("skipOptions", JSON.stringify(skipOptions));
 | 
			
		||||
 | 
			
		||||
                localStorage.setItem("showMarkers", this.showMarkers);
 | 
			
		||||
                localStorage.setItem("minSegmentLength", this.minSegmentLength);
 | 
			
		||||
 | 
			
		||||
                localStorage.setItem("dearrow", this.dearrow);
 | 
			
		||||
 | 
			
		||||
                localStorage.setItem("theme", this.selectedTheme);
 | 
			
		||||
                localStorage.setItem("playerAutoPlay", this.autoPlayVideo);
 | 
			
		||||
                localStorage.setItem("autoDisplayCaptions", this.autoDisplayCaptions);
 | 
			
		||||
                localStorage.setItem("autoPlayNextCountdown", this.autoPlayNextCountdown);
 | 
			
		||||
                localStorage.setItem("listen", this.listen);
 | 
			
		||||
                localStorage.setItem("quality", this.defaultQuality);
 | 
			
		||||
                localStorage.setItem("bufferGoal", this.bufferingGoal);
 | 
			
		||||
| 
						 | 
				
			
			@ -663,6 +1004,7 @@ export default {
 | 
			
		|||
                localStorage.setItem("minimizeRecommendations", this.minimizeRecommendations);
 | 
			
		||||
                localStorage.setItem("minimizeChapters", this.minimizeChapters);
 | 
			
		||||
                localStorage.setItem("showWatchOnYouTube", this.showWatchOnYouTube);
 | 
			
		||||
                localStorage.setItem("searchSuggestions", this.searchSuggestions);
 | 
			
		||||
                localStorage.setItem("watchHistory", this.watchHistory);
 | 
			
		||||
                localStorage.setItem("searchHistory", this.searchHistory);
 | 
			
		||||
                if (!this.searchHistory) localStorage.removeItem("search_history");
 | 
			
		||||
| 
						 | 
				
			
			@ -671,6 +1013,7 @@ export default {
 | 
			
		|||
                localStorage.setItem("disableLBRY", this.disableLBRY);
 | 
			
		||||
                localStorage.setItem("proxyLBRY", this.proxyLBRY);
 | 
			
		||||
                localStorage.setItem("hideWatched", this.hideWatched);
 | 
			
		||||
                localStorage.setItem("mobileChapterLayout", this.mobileChapterLayout);
 | 
			
		||||
 | 
			
		||||
                if (shouldReload) window.location.reload();
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -700,7 +1043,7 @@ export default {
 | 
			
		|||
            window.location = "/";
 | 
			
		||||
        },
 | 
			
		||||
        resetPreferences() {
 | 
			
		||||
            if (!confirm(this.$t("actions.confirm_reset_preferences"))) return;
 | 
			
		||||
            this.showConfirmResetPrefsDialog = false;
 | 
			
		||||
            // clear the local storage
 | 
			
		||||
            localStorage.clear();
 | 
			
		||||
            // redirect to the home page
 | 
			
		||||
| 
						 | 
				
			
			@ -740,4 +1083,10 @@ export default {
 | 
			
		|||
.pref {
 | 
			
		||||
    @apply flex justify-between items-center;
 | 
			
		||||
}
 | 
			
		||||
.pref:nth-child(odd) {
 | 
			
		||||
    @apply bg-gray-200;
 | 
			
		||||
}
 | 
			
		||||
.dark .pref:nth-child(odd) {
 | 
			
		||||
    @apply bg-dark-800;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								src/components/QrCode.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/components/QrCode.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <canvas ref="qrCodeCanvas" style="border-radius: var(--efy_radius)" />
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import QRCode from "qrcode";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        text: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        text() {
 | 
			
		||||
            this.generateQrCode();
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.generateQrCode();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        generateQrCode() {
 | 
			
		||||
            QRCode.toCanvas(this.$refs.qrCodeCanvas, this.text, error => {
 | 
			
		||||
                if (error) console.error(error);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										114
									
								
								src/components/RegisterPage.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/components/RegisterPage.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <h1 v-t="'titles.register'" class="my-4 text-center font-bold" />
 | 
			
		||||
    <hr />
 | 
			
		||||
    <div class="flex justify-center text-center">
 | 
			
		||||
        <form class="items-center px-3 children:pb-3">
 | 
			
		||||
            <div>
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="username"
 | 
			
		||||
                    class="input w-full"
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    autocomplete="username"
 | 
			
		||||
                    :placeholder="$t('login.username')"
 | 
			
		||||
                    :aria-label="$t('login.username')"
 | 
			
		||||
                    @keyup.enter="register"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex justify-center">
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="password"
 | 
			
		||||
                    class="input w-full"
 | 
			
		||||
                    :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
                    autocomplete="password"
 | 
			
		||||
                    :placeholder="$t('login.password')"
 | 
			
		||||
                    :aria-label="$t('login.password')"
 | 
			
		||||
                    @keyup.enter="register"
 | 
			
		||||
                />
 | 
			
		||||
                <button type="button" class="btn ml-2" @click="showPassword = !showPassword">
 | 
			
		||||
                    <div class="i-fa6-solid:eye" />
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex justify-center">
 | 
			
		||||
                <input
 | 
			
		||||
                    v-model="passwordConfirm"
 | 
			
		||||
                    class="input w-full"
 | 
			
		||||
                    :type="showConfirmPassword ? 'text' : 'password'"
 | 
			
		||||
                    autocomplete="password"
 | 
			
		||||
                    :placeholder="$t('login.password_confirm')"
 | 
			
		||||
                    :aria-label="$t('login.password_confirm')"
 | 
			
		||||
                    @keyup.enter="register"
 | 
			
		||||
                />
 | 
			
		||||
                <button type="button" class="btn ml-2" @click="showConfirmPassword = !showConfirmPassword">
 | 
			
		||||
                    <div class="i-fa6-solid:eye" />
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <a v-t="'titles.register'" class="btn w-auto" @click="register" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ConfirmModal
 | 
			
		||||
        v-if="showUnsecureRegisterDialog"
 | 
			
		||||
        :message="$t('info.register_no_email_note')"
 | 
			
		||||
        @close="showUnsecureRegisterDialog = false"
 | 
			
		||||
        @confirm="
 | 
			
		||||
            forceUnsecureRegister = true;
 | 
			
		||||
            showUnsecureRegisterDialog = false;
 | 
			
		||||
            register();
 | 
			
		||||
        "
 | 
			
		||||
    />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { isEmail } from "../utils/Misc.js";
 | 
			
		||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: { ConfirmModal },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            username: null,
 | 
			
		||||
            password: null,
 | 
			
		||||
            passwordConfirm: null,
 | 
			
		||||
            showPassword: false,
 | 
			
		||||
            showConfirmPassword: false,
 | 
			
		||||
            showUnsecureRegisterDialog: false,
 | 
			
		||||
            forceUnsecureRegister: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        //TODO: Add Server Side check
 | 
			
		||||
        if (this.getAuthToken()) {
 | 
			
		||||
            this.$router.push("/");
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        document.title = "Register - Piped";
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        register() {
 | 
			
		||||
            if (!this.username || !this.password) return;
 | 
			
		||||
            if (this.password != this.passwordConfirm) {
 | 
			
		||||
                alert(this.$t("login.passwords_incorrect"));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (isEmail(this.username) && !this.forceUnsecureRegister) {
 | 
			
		||||
                this.showUnsecureRegisterDialog = true;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            this.fetchJson(this.authApiUrl() + "/register", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    username: this.username,
 | 
			
		||||
                    password: this.password,
 | 
			
		||||
                }),
 | 
			
		||||
            }).then(resp => {
 | 
			
		||||
                if (resp.token) {
 | 
			
		||||
                    this.setPreference("authToken" + this.hashCode(this.authApiUrl()), resp.token);
 | 
			
		||||
                    window.location = "/"; // done to bypass cache
 | 
			
		||||
                } else alert(resp.error);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,11 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <h1 class="text-center my-2" v-text="$route.query.search_query" />
 | 
			
		||||
    <h1 class="my-2 text-center" v-text="$route.query.search_query" />
 | 
			
		||||
 | 
			
		||||
    <label for="ddlSearchFilters" class="mr-2">
 | 
			
		||||
        <strong v-text="`${$t('actions.filter')}:`" />
 | 
			
		||||
    </label>
 | 
			
		||||
    <select id="ddlSearchFilters" v-model="selectedFilter" default="all" class="select w-auto" @change="updateFilter()">
 | 
			
		||||
        <option v-for="filter in availableFilters" :key="filter" :value="filter" v-t="`search.${filter}`" />
 | 
			
		||||
        <option v-for="filter in availableFilters" :key="filter" v-t="`search.${filter}`" :value="filter" />
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <hr />
 | 
			
		||||
| 
						 | 
				
			
			@ -18,19 +18,21 @@
 | 
			
		|||
        </i18n-t>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div v-if="results" class="video-grid">
 | 
			
		||||
    <LoadingIndicatorPage :show-content="results != null && results.items?.length" class="video-grid">
 | 
			
		||||
        <template v-for="result in results.items" :key="result.url">
 | 
			
		||||
            <ContentItem :item="result" height="94" width="168" />
 | 
			
		||||
        </template>
 | 
			
		||||
    </div>
 | 
			
		||||
    </LoadingIndicatorPage>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ContentItem from "./ContentItem.vue";
 | 
			
		||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ContentItem,
 | 
			
		||||
        LoadingIndicatorPage,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +46,7 @@ export default {
 | 
			
		|||
                "music_videos",
 | 
			
		||||
                "music_albums",
 | 
			
		||||
                "music_playlists",
 | 
			
		||||
                "music_artists",
 | 
			
		||||
            ],
 | 
			
		||||
            selectedFilter: this.$route.query.filter ?? "all",
 | 
			
		||||
        };
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +75,10 @@ export default {
 | 
			
		|||
        },
 | 
			
		||||
        async updateResults() {
 | 
			
		||||
            document.title = this.$route.query.search_query + " - Piped";
 | 
			
		||||
            this.results = this.fetchResults().then(json => (this.results = json));
 | 
			
		||||
            this.results = this.fetchResults().then(json => {
 | 
			
		||||
                this.results = json;
 | 
			
		||||
                this.updateWatched(this.results.items);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        updateFilter() {
 | 
			
		||||
            this.$router.replace({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div class="absolute suggestions-container">
 | 
			
		||||
    <div class="suggestions-container absolute">
 | 
			
		||||
        <ul>
 | 
			
		||||
            <li
 | 
			
		||||
                v-for="(suggestion, i) in searchSuggestions"
 | 
			
		||||
| 
						 | 
				
			
			@ -50,12 +50,16 @@ export default {
 | 
			
		|||
            if (!this.searchText) {
 | 
			
		||||
                if (this.getPreferenceBoolean("searchHistory", false))
 | 
			
		||||
                    this.searchSuggestions = JSON.parse(localStorage.getItem("search_history")) ?? [];
 | 
			
		||||
            } else if (this.getPreferenceBoolean("searchSuggestions", true)) {
 | 
			
		||||
                this.searchSuggestions =
 | 
			
		||||
                    (
 | 
			
		||||
                        await this.fetchJson(this.apiUrl() + "/opensearch/suggestions", {
 | 
			
		||||
                            query: this.searchText,
 | 
			
		||||
                        })
 | 
			
		||||
                    )?.[1] ?? [];
 | 
			
		||||
            } else {
 | 
			
		||||
                this.searchSuggestions = (
 | 
			
		||||
                    await this.fetchJson(this.apiUrl() + "/opensearch/suggestions", {
 | 
			
		||||
                        query: this.searchText,
 | 
			
		||||
                    })
 | 
			
		||||
                )?.[1];
 | 
			
		||||
                this.searchSuggestions = [];
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            this.searchSuggestions.unshift(this.searchText);
 | 
			
		||||
            this.setSelected(0);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,34 +3,45 @@
 | 
			
		|||
        <h4 v-t="'actions.share'" />
 | 
			
		||||
        <div class="flex justify-between mt-2 mb-2">
 | 
			
		||||
            <label v-t="'actions.piped_link'" />
 | 
			
		||||
            <input type="checkbox" v-model="pipedLink" @change="onChange" />
 | 
			
		||||
            <input v-model="pipedLink" type="checkbox" @change="onChange" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-if="this.hasPlaylist" class="flex justify-between">
 | 
			
		||||
        <div v-if="hasPlaylist" class="flex justify-between">
 | 
			
		||||
            <label v-t="'actions.with_playlist'" />
 | 
			
		||||
            <input type="checkbox" v-model="withPlaylist" @change="onChange" />
 | 
			
		||||
            <input v-model="withPlaylist" type="checkbox" @change="onChange" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="flex justify-between">
 | 
			
		||||
            <label v-t="'actions.with_timecode'" for="withTimeCode" />
 | 
			
		||||
            <input id="withTimeCode" type="checkbox" v-model="withTimeCode" @change="onChange" />
 | 
			
		||||
            <input id="withTimeCode" v-model="withTimeCode" type="checkbox" @change="onChange" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-if="this.withTimeCode" class="flex justify-between mt-2" style="align-items: center">
 | 
			
		||||
        <div v-if="withTimeCode" class="flex justify-between mt-2" style="align-items: center">
 | 
			
		||||
            <label v-t="'actions.time_code'" />
 | 
			
		||||
            <input class="input w-300 mb-0rem" type="text" v-model="timeStamp" />
 | 
			
		||||
            <input v-model="timeStamp" class="input w-12" type="text" @change="onChange" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <a :href="generatedLink" target="_blank">
 | 
			
		||||
            <h6 class="mb-2 mt-2" v-text="generatedLink" />
 | 
			
		||||
        </a>
 | 
			
		||||
        <div class="flex justify-end mt-4">
 | 
			
		||||
            <button class="btn" style="margin-right: 15rem" v-t="'actions.follow_link'" @click="followLink()" />
 | 
			
		||||
            <button class="btn" v-t="'actions.copy_link'" @click="copyLink()" />
 | 
			
		||||
        <QrCode v-if="showQrCode" :text="generatedLink" />
 | 
			
		||||
        <div class="mt-4 flex justify-end">
 | 
			
		||||
            <button v-t="'actions.generate_qrcode'" class="btn" @click="showQrCode = !showQrCode" />
 | 
			
		||||
            <button v-t="'actions.follow_link'" class="btn ml-3" @click="followLink()" />
 | 
			
		||||
            <button v-t="'actions.copy_link'" class="btn ml-3" @click="copyLink()" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </ModalComponent>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { defineAsyncComponent } from "vue";
 | 
			
		||||
 | 
			
		||||
const QrCode = defineAsyncComponent(() => import("./QrCode.vue"));
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ModalComponent from "./ModalComponent.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ModalComponent,
 | 
			
		||||
    },
 | 
			
		||||
    props: {
 | 
			
		||||
        videoId: {
 | 
			
		||||
            type: String,
 | 
			
		||||
| 
						 | 
				
			
			@ -42,14 +53,13 @@ export default {
 | 
			
		|||
        },
 | 
			
		||||
        playlistId: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            default: undefined,
 | 
			
		||||
        },
 | 
			
		||||
        playlistIndex: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: undefined,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
        ModalComponent,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            withTimeCode: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -57,8 +67,23 @@ export default {
 | 
			
		|||
            withPlaylist: true,
 | 
			
		||||
            timeStamp: null,
 | 
			
		||||
            hasPlaylist: false,
 | 
			
		||||
            showQrCode: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        generatedLink() {
 | 
			
		||||
            var baseUrl = this.pipedLink
 | 
			
		||||
                ? window.location.origin + "/watch?v=" + this.videoId
 | 
			
		||||
                : "https://youtu.be/" + this.videoId;
 | 
			
		||||
            var url = new URL(baseUrl);
 | 
			
		||||
            if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
 | 
			
		||||
            if (this.hasPlaylist && this.withPlaylist) {
 | 
			
		||||
                url.searchParams.append("list", this.playlistId);
 | 
			
		||||
                url.searchParams.append("index", this.playlistIndex);
 | 
			
		||||
            }
 | 
			
		||||
            return url.href;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.timeStamp = parseInt(this.currentTime);
 | 
			
		||||
        this.withTimeCode = this.getPreferenceBoolean("shareWithTimeCode", true);
 | 
			
		||||
| 
						 | 
				
			
			@ -87,19 +112,5 @@ export default {
 | 
			
		|||
            this.setPreference("shareWithPlaylist", this.withPlaylist, true);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        generatedLink() {
 | 
			
		||||
            var baseUrl = this.pipedLink
 | 
			
		||||
                ? window.location.origin + "/watch?v=" + this.videoId
 | 
			
		||||
                : "https://youtu.be/" + this.videoId;
 | 
			
		||||
            var url = new URL(baseUrl);
 | 
			
		||||
            if (this.withTimeCode && this.timeStamp > 0) url.searchParams.append("t", this.timeStamp);
 | 
			
		||||
            if (this.hasPlaylist && this.withPlaylist) {
 | 
			
		||||
                url.searchParams.append("list", this.playlistId);
 | 
			
		||||
                url.searchParams.append("index", this.playlistIndex);
 | 
			
		||||
            }
 | 
			
		||||
            return url.href;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <label for="ddlSortBy" v-t="'actions.sort_by'" class="mr-2" />
 | 
			
		||||
    <label v-t="'actions.sort_by'" for="ddlSortBy" class="mr-2" />
 | 
			
		||||
    <select id="ddlSortBy" v-model="selectedSort" class="select w-auto m-0">
 | 
			
		||||
        <option v-for="(value, key) in options" v-t="`actions.${key}`" :key="key" :value="value" />
 | 
			
		||||
        <option v-for="(value, key) in options" :key="key" v-t="`actions.${key}`" :value="value" />
 | 
			
		||||
    </select>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,10 @@ const options = {
 | 
			
		|||
const selectedSort = ref("descending");
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
    byKey: String,
 | 
			
		||||
    byKey: {
 | 
			
		||||
        type: String,
 | 
			
		||||
        required: true,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(["apply"]);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,26 +4,77 @@
 | 
			
		|||
            <button class="btn mr-2">
 | 
			
		||||
                <router-link to="/import" v-t="'actions.import_from_json'" />
 | 
			
		||||
            </button>
 | 
			
		||||
            <button class="btn" @click="exportHandler" v-t="'actions.export_to_json'" />
 | 
			
		||||
            <button v-t="'actions.export_to_json'" class="btn" @click="exportHandler" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <i18n-t keypath="subscriptions.subscribed_channels_count">{{ subscriptions.length }}</i18n-t>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr />
 | 
			
		||||
    <div class="w-full flex flex-wrap">
 | 
			
		||||
        <button
 | 
			
		||||
            v-for="group in channelGroups"
 | 
			
		||||
            :key="group.groupName"
 | 
			
		||||
            class="btn mx-1 w-max"
 | 
			
		||||
            :class="{ selected: selectedGroup === group }"
 | 
			
		||||
            @click="selectGroup(group)"
 | 
			
		||||
        >
 | 
			
		||||
            <span v-text="group.groupName !== '' ? group.groupName : $t('video.all')" />
 | 
			
		||||
            <div v-if="group.groupName != '' && selectedGroup == group">
 | 
			
		||||
                <font-awesome-icon class="mx-2" icon="edit" @click="showEditGroupModal = true" />
 | 
			
		||||
                <font-awesome-icon class="mx-2" icon="circle-minus" @click="deleteGroup(group)" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </button>
 | 
			
		||||
        <button class="btn mx-1">
 | 
			
		||||
            <font-awesome-icon icon="circle-plus" @click="showCreateGroupModal = true" />
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br />
 | 
			
		||||
    <hr />
 | 
			
		||||
    <!-- Subscriptions card list -->
 | 
			
		||||
    <div class="pp-subs-cards">
 | 
			
		||||
        <!-- channel info card -->
 | 
			
		||||
        <div efy_card class="pp-subs-card" v-for="subscription in subscriptions" :key="subscription.url">
 | 
			
		||||
        <div v-for="subscription in filteredSubscriptions" :key="subscription.url" efy_card class="pp-subs-card">
 | 
			
		||||
            <router-link :to="subscription.url" class="pp-import-channel flex font-bold">
 | 
			
		||||
                <img :src="subscription.avatar" width="48" height="48" />
 | 
			
		||||
                <span class="mx-2" v-text="subscription.name" />
 | 
			
		||||
            </router-link>
 | 
			
		||||
            <!-- (un)subscribe btn -->
 | 
			
		||||
            <button
 | 
			
		||||
                @click="handleButton(subscription)"
 | 
			
		||||
                v-t="`actions.${subscription.subscribed ? 'unsubscribe' : 'subscribe'}`"
 | 
			
		||||
                class="btn mt-2 w-full"
 | 
			
		||||
                @click="handleButton(subscription)"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <br />
 | 
			
		||||
 | 
			
		||||
    <ModalComponent v-if="showCreateGroupModal" @close="showCreateGroupModal = !showCreateGroupModal">
 | 
			
		||||
        <h2 v-t="'actions.create_group'" />
 | 
			
		||||
        <div class="flex flex-col">
 | 
			
		||||
            <input v-model="newGroupName" class="input my-4" type="text" :placeholder="$t('actions.group_name')" />
 | 
			
		||||
            <button v-t="'actions.create_group'" class="btn ml-auto w-max" @click="createGroup()" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </ModalComponent>
 | 
			
		||||
 | 
			
		||||
    <ModalComponent v-if="showEditGroupModal" @close="showEditGroupModal = false">
 | 
			
		||||
        <div class="mb-5 mt-3 flex justify-between">
 | 
			
		||||
            <input v-model="editedGroupName" type="text" class="input" />
 | 
			
		||||
            <button v-t="'actions.okay'" class="btn" :placeholder="$t('actions.group_name')" @click="editGroupName()" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="mb-2 mt-3 flex flex-col overflow-y-scroll" style="height: 300rem">
 | 
			
		||||
            <div v-for="subscription in subscriptions" :key="subscription.name">
 | 
			
		||||
                <div class="mr-3 flex justify-between">
 | 
			
		||||
                    <span>{{ subscription.name }}</span>
 | 
			
		||||
                    <input
 | 
			
		||||
                        type="checkbox"
 | 
			
		||||
                        class="checkbox"
 | 
			
		||||
                        :checked="selectedGroup.channels.includes(subscription.url.substr(-11))"
 | 
			
		||||
                        @change="checkedChange(subscription)"
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
                <hr />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </ModalComponent>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
| 
						 | 
				
			
			@ -39,20 +90,58 @@
 | 
			
		|||
    margin-bottom: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.selected {
 | 
			
		||||
    border: 0.1rem outset red;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import ModalComponent from "./ModalComponent.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: { ModalComponent },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            subscriptions: [],
 | 
			
		||||
            selectedGroup: {
 | 
			
		||||
                groupName: "",
 | 
			
		||||
                channels: [],
 | 
			
		||||
            },
 | 
			
		||||
            channelGroups: [],
 | 
			
		||||
            showCreateGroupModal: false,
 | 
			
		||||
            showEditGroupModal: false,
 | 
			
		||||
            newGroupName: "",
 | 
			
		||||
            editedGroupName: "",
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        filteredSubscriptions(_this) {
 | 
			
		||||
            return _this.selectedGroup.groupName == ""
 | 
			
		||||
                ? _this.subscriptions
 | 
			
		||||
                : _this.subscriptions.filter(channel => _this.selectedGroup.channels.includes(channel.url.substr(-11)));
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.fetchSubscriptions().then(json => {
 | 
			
		||||
            this.subscriptions = json;
 | 
			
		||||
            this.subscriptions.forEach(subscription => (subscription.subscribed = true));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.channelGroups.push(this.selectedGroup);
 | 
			
		||||
 | 
			
		||||
        if (!window.db) return;
 | 
			
		||||
        const cursor = this.getChannelGroupsCursor();
 | 
			
		||||
        cursor.onsuccess = e => {
 | 
			
		||||
            const cursor = e.target.result;
 | 
			
		||||
            if (cursor) {
 | 
			
		||||
                const group = cursor.value;
 | 
			
		||||
                this.channelGroups.push({
 | 
			
		||||
                    groupName: group.groupName,
 | 
			
		||||
                    channels: JSON.parse(group.channels),
 | 
			
		||||
                });
 | 
			
		||||
                cursor.continue();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        document.title = "Subscriptions - Piped";
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +180,6 @@ export default {
 | 
			
		|||
        },
 | 
			
		||||
        exportHandler() {
 | 
			
		||||
            const subscriptions = [];
 | 
			
		||||
 | 
			
		||||
            this.subscriptions.forEach(subscription => {
 | 
			
		||||
                subscriptions.push({
 | 
			
		||||
                    url: "https://www.youtube.com" + subscription.url,
 | 
			
		||||
| 
						 | 
				
			
			@ -99,15 +187,58 @@ export default {
 | 
			
		|||
                    service_id: 0,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const json = JSON.stringify({
 | 
			
		||||
                app_version: "",
 | 
			
		||||
                app_version_int: 0,
 | 
			
		||||
                subscriptions: subscriptions,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.download(json, "subscriptions.json", "application/json");
 | 
			
		||||
        },
 | 
			
		||||
        selectGroup(group) {
 | 
			
		||||
            this.selectedGroup = group;
 | 
			
		||||
            this.editedGroupName = group.groupName;
 | 
			
		||||
        },
 | 
			
		||||
        createGroup() {
 | 
			
		||||
            if (!this.newGroupName || this.channelGroups.some(group => group.groupName == this.newGroupName)) return;
 | 
			
		||||
 | 
			
		||||
            const newGroup = {
 | 
			
		||||
                groupName: this.newGroupName,
 | 
			
		||||
                channels: [],
 | 
			
		||||
            };
 | 
			
		||||
            this.channelGroups.push(newGroup);
 | 
			
		||||
            this.createOrUpdateChannelGroup(newGroup);
 | 
			
		||||
 | 
			
		||||
            this.newGroupName = "";
 | 
			
		||||
 | 
			
		||||
            this.showCreateGroupModal = false;
 | 
			
		||||
        },
 | 
			
		||||
        editGroupName() {
 | 
			
		||||
            const oldGroupName = this.selectedGroup.groupName;
 | 
			
		||||
            const newGroupName = this.editedGroupName;
 | 
			
		||||
 | 
			
		||||
            // the group mustn't yet exist and the name can't be empty
 | 
			
		||||
            if (!newGroupName || newGroupName == oldGroupName) return;
 | 
			
		||||
            if (this.channelGroups.some(group => group.groupName == newGroupName)) return;
 | 
			
		||||
 | 
			
		||||
            // create a new group with the same info and delete the old one
 | 
			
		||||
            this.selectedGroup.groupName = newGroupName;
 | 
			
		||||
            this.createOrUpdateChannelGroup(this.selectedGroup);
 | 
			
		||||
            this.deleteChannelGroup(oldGroupName);
 | 
			
		||||
 | 
			
		||||
            this.showEditGroupModal = false;
 | 
			
		||||
        },
 | 
			
		||||
        deleteGroup(group) {
 | 
			
		||||
            this.deleteChannelGroup(group.groupName);
 | 
			
		||||
            this.channelGroups = this.channelGroups.filter(g => g != group);
 | 
			
		||||
            this.selectedGroup = this.channelGroups[0];
 | 
			
		||||
        },
 | 
			
		||||
        checkedChange(subscription) {
 | 
			
		||||
            const channelId = subscription.url.substr(-11);
 | 
			
		||||
            this.selectedGroup.channels = this.selectedGroup.channels.includes(channelId)
 | 
			
		||||
                ? this.selectedGroup.channels.filter(channel => channel != channelId)
 | 
			
		||||
                : this.selectedGroup.channels.concat(channelId);
 | 
			
		||||
            this.createOrUpdateChannelGroup(this.selectedGroup);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										29
									
								
								src/components/ToastComponent.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/components/ToastComponent.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div class="toast">
 | 
			
		||||
        <slot />
 | 
			
		||||
        <button v-t="'actions.dismiss'" @click="dismiss" />
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    emits: ["dismissed"],
 | 
			
		||||
    methods: {
 | 
			
		||||
        dismiss() {
 | 
			
		||||
            this.$emit("dismissed");
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.toast {
 | 
			
		||||
    @apply bg-white/80 text-black flex flex-col justify-center fixed top-12 right-12 p-4 min-w-max shadow rounded duration-200 z-9999;
 | 
			
		||||
}
 | 
			
		||||
.dark .toast {
 | 
			
		||||
    @apply bg-dark-900/80 text-white;
 | 
			
		||||
}
 | 
			
		||||
.toast button {
 | 
			
		||||
    @apply underline;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +1,17 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div class="video-grid">
 | 
			
		||||
    <LoadingIndicatorPage :show-content="videos.length != 0" class="video-grid">
 | 
			
		||||
        <VideoItem v-for="video in videos" :key="video.url" :item="video" height="118" width="210" />
 | 
			
		||||
    </div>
 | 
			
		||||
    </LoadingIndicatorPage>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
			
		||||
import VideoItem from "./VideoItem.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        VideoItem,
 | 
			
		||||
        LoadingIndicatorPage,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
| 
						 | 
				
			
			@ -23,21 +25,15 @@ export default {
 | 
			
		|||
        this.fetchTrending(region).then(videos => {
 | 
			
		||||
            this.videos = videos;
 | 
			
		||||
            this.updateWatched(this.videos);
 | 
			
		||||
            this.fetchDeArrowContent(this.videos);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    activated() {
 | 
			
		||||
        document.title = this.$t("titles.trending") + " - Piped";
 | 
			
		||||
        if (this.videos.length > 0) this.updateWatched(this.videos);
 | 
			
		||||
        if (this.$route.path == "/") {
 | 
			
		||||
            switch (this.getPreferenceString("homepage", "trending")) {
 | 
			
		||||
                case "trending":
 | 
			
		||||
                    break;
 | 
			
		||||
                case "feed":
 | 
			
		||||
                    this.$router.push("/feed");
 | 
			
		||||
                    return;
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            let homepage = this.getHomePage(this);
 | 
			
		||||
            if (homepage !== undefined) this.$router.push(homepage);
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div v-if="showVideo" class="efy_trans_filter">
 | 
			
		||||
    <div v-if="showVideo" class="efy_trans_filter efy_shadow_trans">
 | 
			
		||||
        <router-link
 | 
			
		||||
            class="video_item_link inline-block w-full focus:underline hover:underline"
 | 
			
		||||
            :to="{
 | 
			
		||||
                path: '/watch',
 | 
			
		||||
                query: {
 | 
			
		||||
| 
						 | 
				
			
			@ -9,18 +10,8 @@
 | 
			
		|||
                    ...(index >= 0 && { index: index + 1 }),
 | 
			
		||||
                },
 | 
			
		||||
            }"
 | 
			
		||||
            class="video_item_link"
 | 
			
		||||
        >
 | 
			
		||||
            <img
 | 
			
		||||
                class="w-full"
 | 
			
		||||
                :src="item.thumbnail"
 | 
			
		||||
                :alt="item.title"
 | 
			
		||||
                :class="{ 'shorts-img': item.isShort }"
 | 
			
		||||
                loading="lazy"
 | 
			
		||||
            />
 | 
			
		||||
            <p class="pp-video-card-title my-2 overflow-hidden flex link" :title="item.title" v-text="item.title" />
 | 
			
		||||
        </router-link>
 | 
			
		||||
 | 
			
		||||
            <!-- EFY
 | 
			
		||||
        <div class="pp-video-card-buttons">
 | 
			
		||||
            <button v-if="item.duration > 0" v-text="timeFormat(item.duration)" tabindex="-1" />
 | 
			
		||||
            <button v-if="item.views >= 0" tabindex="-1">
 | 
			
		||||
| 
						 | 
				
			
			@ -77,9 +68,129 @@
 | 
			
		|||
 | 
			
		||||
            <div class="pp-text" title="item.uploaderName">
 | 
			
		||||
                <span v-text="item.uploaderName" />
 | 
			
		||||
                <font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />
 | 
			
		||||
                <font-awesome-icon class="ml-1.5" v-if="item.uploaderVerified" icon="check" />-->
 | 
			
		||||
 | 
			
		||||
            <div class="w-full">
 | 
			
		||||
                <img
 | 
			
		||||
                    class="w-full"
 | 
			
		||||
                    :src="thumbnail"
 | 
			
		||||
                    :alt="title"
 | 
			
		||||
                    :class="{ 'shorts-img': item.isShort, 'opacity-75': item.watched }"
 | 
			
		||||
                    loading="lazy"
 | 
			
		||||
                />
 | 
			
		||||
                <!-- progress bar -->
 | 
			
		||||
                <div class="relative h-1 w-full">
 | 
			
		||||
                    <div
 | 
			
		||||
                        v-if="item.watched && item.duration > 0"
 | 
			
		||||
                        class="absolute bottom-0 left-0 h-1 bg-red-600"
 | 
			
		||||
                        :style="{ width: `clamp(0%, ${(item.currentTime / item.duration) * 100}%, 100%` }"
 | 
			
		||||
                    />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="relative text-sm">
 | 
			
		||||
                <span
 | 
			
		||||
                    v-if="item.duration > 0"
 | 
			
		||||
                    class="thumbnail-overlay thumbnail-right"
 | 
			
		||||
                    v-text="timeFormat(item.duration)"
 | 
			
		||||
                />
 | 
			
		||||
                <!-- shorts thumbnail -->
 | 
			
		||||
                <span v-if="item.isShort" v-t="'video.shorts'" class="thumbnail-overlay thumbnail-left" />
 | 
			
		||||
                <span
 | 
			
		||||
                    v-else-if="item.duration >= 0"
 | 
			
		||||
                    class="thumbnail-overlay thumbnail-right"
 | 
			
		||||
                    v-text="timeFormat(item.duration)"
 | 
			
		||||
                />
 | 
			
		||||
                <i18n-t v-else keypath="video.live" class="thumbnail-overlay thumbnail-right !bg-red-600" tag="div">
 | 
			
		||||
                    <font-awesome-icon class="w-3" :icon="['fas', 'broadcast-tower']" />
 | 
			
		||||
                </i18n-t>
 | 
			
		||||
                <span v-if="item.watched" v-t="'video.watched'" class="thumbnail-overlay bottom-5px left-5px" />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <p
 | 
			
		||||
                style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical"
 | 
			
		||||
                class="pp-video-card-title my-2 overflow-hidden flex link"
 | 
			
		||||
                :title="title"
 | 
			
		||||
                v-text="title"
 | 
			
		||||
            />
 | 
			
		||||
        </router-link>
 | 
			
		||||
 | 
			
		||||
        <div class="flex">
 | 
			
		||||
            <router-link :to="item.uploaderUrl">
 | 
			
		||||
                <img
 | 
			
		||||
                    v-if="item.uploaderAvatar"
 | 
			
		||||
                    :src="item.uploaderAvatar"
 | 
			
		||||
                    loading="lazy"
 | 
			
		||||
                    class="mr-0.5 mt-0.5 h-32px w-32px rounded-full"
 | 
			
		||||
                    width="68"
 | 
			
		||||
                    height="68"
 | 
			
		||||
                />
 | 
			
		||||
            </router-link>
 | 
			
		||||
 | 
			
		||||
            <div class="flex-1 px-2">
 | 
			
		||||
                <router-link
 | 
			
		||||
                    v-if="item.uploaderUrl && item.uploaderName && !hideChannel"
 | 
			
		||||
                    class="link-secondary block overflow-hidden text-sm"
 | 
			
		||||
                    :to="item.uploaderUrl"
 | 
			
		||||
                    :title="item.uploaderName"
 | 
			
		||||
                >
 | 
			
		||||
                    <span v-text="item.uploaderName" />
 | 
			
		||||
                    <font-awesome-icon v-if="item.uploaderVerified" class="ml-1.5" icon="check" />
 | 
			
		||||
                </router-link>
 | 
			
		||||
 | 
			
		||||
                <div v-if="item.views >= 0 || item.uploadedDate" class="mt-1 text-xs font-normal text-gray-300">
 | 
			
		||||
                    <span v-if="item.views >= 0">
 | 
			
		||||
                        <font-awesome-icon icon="eye" />
 | 
			
		||||
                        <span class="pl-1" v-text="`${numberFormat(item.views)} •`" />
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <span v-if="item.uploaded > 0" class="pl-0.5" v-text="timeAgo(item.uploaded)" />
 | 
			
		||||
                    <span v-else-if="item.uploadedDate" class="pl-0.5" v-text="item.uploadedDate" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="flex items-center gap-2.5">
 | 
			
		||||
                <router-link
 | 
			
		||||
                    :to="{
 | 
			
		||||
                        path: '/watch',
 | 
			
		||||
                        query: {
 | 
			
		||||
                            v: item.url.substr(-11),
 | 
			
		||||
                            ...(playlistId && { list: playlistId }),
 | 
			
		||||
                            ...(index >= 0 && { index: index + 1 }),
 | 
			
		||||
                            listen: '1',
 | 
			
		||||
                        },
 | 
			
		||||
                    }"
 | 
			
		||||
                    :aria-label="'Listen to ' + title"
 | 
			
		||||
                    :title="'Listen to ' + title"
 | 
			
		||||
                >
 | 
			
		||||
                    <font-awesome-icon icon="headphones" />
 | 
			
		||||
                </router-link>
 | 
			
		||||
                <button :title="$t('actions.add_to_playlist')" @click="showModal = !showModal">
 | 
			
		||||
                    <font-awesome-icon icon="circle-plus" />
 | 
			
		||||
                </button>
 | 
			
		||||
                <button
 | 
			
		||||
                    v-if="admin"
 | 
			
		||||
                    ref="removeButton"
 | 
			
		||||
                    :title="$t('actions.remove_from_playlist')"
 | 
			
		||||
                    @click="showConfirmRemove = true"
 | 
			
		||||
                >
 | 
			
		||||
                    <font-awesome-icon icon="circle-minus" />
 | 
			
		||||
                </button>
 | 
			
		||||
                <ConfirmModal
 | 
			
		||||
                    v-if="showConfirmRemove"
 | 
			
		||||
                    :message="$t('actions.delete_playlist_video_confirm')"
 | 
			
		||||
                    @close="showConfirmRemove = false"
 | 
			
		||||
                    @confirm="removeVideo(item.url.substr(-11))"
 | 
			
		||||
                />
 | 
			
		||||
                <PlaylistAddModal
 | 
			
		||||
                    v-if="showModal"
 | 
			
		||||
                    :video-id="item.url.substr(-11)"
 | 
			
		||||
                    :video-info="item"
 | 
			
		||||
                    @close="showModal = !showModal"
 | 
			
		||||
                />
 | 
			
		||||
                <!--Master-->
 | 
			
		||||
            </div>
 | 
			
		||||
            <!-- </router-link> -->
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <PlaylistAddModal v-if="showModal" :video-id="video.url.substr(-11)" @close="showModal = !showModal" />
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -95,8 +206,10 @@
 | 
			
		|||
 | 
			
		||||
<script>
 | 
			
		||||
import PlaylistAddModal from "./PlaylistAddModal.vue";
 | 
			
		||||
import ConfirmModal from "./ConfirmModal.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    components: { PlaylistAddModal, ConfirmModal },
 | 
			
		||||
    props: {
 | 
			
		||||
        item: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
| 
						 | 
				
			
			@ -115,34 +228,32 @@ export default {
 | 
			
		|||
        playlistId: { type: String, default: null },
 | 
			
		||||
        admin: { type: Boolean, default: false },
 | 
			
		||||
    },
 | 
			
		||||
    emits: ["remove"],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            showModal: false,
 | 
			
		||||
            showVideo: true,
 | 
			
		||||
            showConfirmRemove: false,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        title() {
 | 
			
		||||
            return this.item.dearrow?.titles[0]?.title ?? this.item.title;
 | 
			
		||||
        },
 | 
			
		||||
        thumbnail() {
 | 
			
		||||
            return this.item.dearrow?.thumbnails[0]?.thumbnail ?? this.item.thumbnail;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.shouldShowVideo();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        removeVideo() {
 | 
			
		||||
            if (confirm(this.$t("actions.delete_playlist_video_confirm"))) {
 | 
			
		||||
                this.$refs.removeButton.disabled = true;
 | 
			
		||||
                this.fetchJson(this.authApiUrl() + "/user/playlists/remove", null, {
 | 
			
		||||
                    method: "POST",
 | 
			
		||||
                    body: JSON.stringify({
 | 
			
		||||
                        playlistId: this.playlistId,
 | 
			
		||||
                        index: this.index,
 | 
			
		||||
                    }),
 | 
			
		||||
                    headers: {
 | 
			
		||||
                        Authorization: this.getAuthToken(),
 | 
			
		||||
                        "Content-Type": "application/json",
 | 
			
		||||
                    },
 | 
			
		||||
                }).then(json => {
 | 
			
		||||
                    if (json.error) alert(json.error);
 | 
			
		||||
                    else this.$emit("remove");
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            this.$refs.removeButton.disabled = true;
 | 
			
		||||
            this.removeVideoFromPlaylist(this.playlistId, this.index).then(json => {
 | 
			
		||||
                if (json.error) alert(json.error);
 | 
			
		||||
                else this.$emit("remove");
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        shouldShowVideo() {
 | 
			
		||||
            if (!this.isFeed || !this.getPreferenceBoolean("hideWatched", false)) return;
 | 
			
		||||
| 
						 | 
				
			
			@ -158,6 +269,5 @@ export default {
 | 
			
		|||
            };
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    components: { PlaylistAddModal },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,15 +2,35 @@
 | 
			
		|||
    <div
 | 
			
		||||
        ref="container"
 | 
			
		||||
        data-shaka-player-container
 | 
			
		||||
        class="w-full max-h-screen flex justify-center efy_trans_filter_off"
 | 
			
		||||
        class="relative max-h-screen w-full flex justify-center efy_trans_filter_off"
 | 
			
		||||
        :class="{ 'player-container': !isEmbed }"
 | 
			
		||||
    >
 | 
			
		||||
        <video ref="videoEl" class="w-full" data-shaka-player :autoplay="shouldAutoPlay" :loop="selectedAutoLoop" />
 | 
			
		||||
        <span
 | 
			
		||||
            id="preview-container"
 | 
			
		||||
            ref="previewContainer"
 | 
			
		||||
            class="absolute bottom-0 z-[2000] mb-[3.5%] hidden flex-col items-center"
 | 
			
		||||
        >
 | 
			
		||||
            <canvas id="preview" ref="preview" class="rounded-sm" />
 | 
			
		||||
            <span class="mt-2 w-min rounded-xl bg-dark-700 px-2 pb-1 pt-1.5 text-sm" v-text="timeFormat(currentTime)" />
 | 
			
		||||
        </span>
 | 
			
		||||
        <button
 | 
			
		||||
            v-if="inSegment"
 | 
			
		||||
            class="skip-segment-button"
 | 
			
		||||
            type="button"
 | 
			
		||||
            :aria-label="$t('actions.skip_segment')"
 | 
			
		||||
            aria-pressed="false"
 | 
			
		||||
            @click="onClickSkipSegment"
 | 
			
		||||
        >
 | 
			
		||||
            <span v-t="'actions.skip_segment'" />
 | 
			
		||||
            <i class="material-icons-round">skip_next</i>
 | 
			
		||||
        </button>
 | 
			
		||||
    </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import("shaka-player/dist/controls.css");
 | 
			
		||||
import "shaka-player/dist/controls.css";
 | 
			
		||||
import { parseTimeParam } from "@/utils/Misc";
 | 
			
		||||
const shaka = import("shaka-player/dist/shaka-player.ui.js");
 | 
			
		||||
if (!window.muxjs) {
 | 
			
		||||
    import("mux.js").then(muxjs => {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,14 +47,6 @@ export default {
 | 
			
		|||
                return {};
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        playlist: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            default: null,
 | 
			
		||||
        },
 | 
			
		||||
        index: {
 | 
			
		||||
            type: Number,
 | 
			
		||||
            default: -1,
 | 
			
		||||
        },
 | 
			
		||||
        sponsors: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            default: () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -45,12 +57,16 @@ export default {
 | 
			
		|||
        selectedAutoLoop: Boolean,
 | 
			
		||||
        isEmbed: Boolean,
 | 
			
		||||
    },
 | 
			
		||||
    emits: ["timeupdate"],
 | 
			
		||||
    emits: ["timeupdate", "ended", "navigateNext"],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            lastUpdate: new Date().getTime(),
 | 
			
		||||
            initialSeekComplete: false,
 | 
			
		||||
            destroying: false,
 | 
			
		||||
            inSegment: false,
 | 
			
		||||
            isHoveringTimebar: false,
 | 
			
		||||
            currentTime: 0,
 | 
			
		||||
            seekbarPadding: 2,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +104,7 @@ export default {
 | 
			
		|||
        this.hotkeysPromise.then(() => {
 | 
			
		||||
            var self = this;
 | 
			
		||||
            this.$hotkeys(
 | 
			
		||||
                "f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.",
 | 
			
		||||
                "f,m,j,k,l,c,space,up,down,left,right,0,1,2,3,4,5,6,7,8,9,shift+n,shift+,,shift+.,alt+p,return,.,,",
 | 
			
		||||
                function (e, handler) {
 | 
			
		||||
                    const videoEl = self.$refs.videoEl;
 | 
			
		||||
                    switch (handler.key) {
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +191,7 @@ export default {
 | 
			
		|||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "shift+n":
 | 
			
		||||
                            self.navigateNext();
 | 
			
		||||
                            self.$emit("navigateNext");
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "shift+,":
 | 
			
		||||
| 
						 | 
				
			
			@ -184,6 +200,22 @@ export default {
 | 
			
		|||
                        case "shift+.":
 | 
			
		||||
                            self.$player.trickPlay(Math.min(videoEl.playbackRate + 0.25, 2));
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "alt+p":
 | 
			
		||||
                            document.pictureInPictureElement
 | 
			
		||||
                                ? document.exitPictureInPicture()
 | 
			
		||||
                                : videoEl.requestPictureInPicture();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "return":
 | 
			
		||||
                            self.skipSegment(videoEl);
 | 
			
		||||
                            break;
 | 
			
		||||
                        case ".":
 | 
			
		||||
                            videoEl.currentTime += 0.04;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                        case ",":
 | 
			
		||||
                            videoEl.currentTime -= 0.04;
 | 
			
		||||
                            e.preventDefault();
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
| 
						 | 
				
			
			@ -199,6 +231,8 @@ export default {
 | 
			
		|||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        async loadVideo() {
 | 
			
		||||
            this.updateSponsors();
 | 
			
		||||
 | 
			
		||||
            const component = this;
 | 
			
		||||
            const videoEl = this.$refs.videoEl;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -207,26 +241,9 @@ export default {
 | 
			
		|||
            const time = this.$route.query.t ?? this.$route.query.start;
 | 
			
		||||
 | 
			
		||||
            if (time) {
 | 
			
		||||
                let start = 0;
 | 
			
		||||
                if (/^[\d]*$/g.test(time)) {
 | 
			
		||||
                    start = time;
 | 
			
		||||
                } else {
 | 
			
		||||
                    const hours = /([\d]*)h/gi.exec(time)?.[1];
 | 
			
		||||
                    const minutes = /([\d]*)m/gi.exec(time)?.[1];
 | 
			
		||||
                    const seconds = /([\d]*)s/gi.exec(time)?.[1];
 | 
			
		||||
                    if (hours) {
 | 
			
		||||
                        start += parseInt(hours) * 60 * 60;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (minutes) {
 | 
			
		||||
                        start += parseInt(minutes) * 60;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (seconds) {
 | 
			
		||||
                        start += parseInt(seconds);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                videoEl.currentTime = start;
 | 
			
		||||
                videoEl.currentTime = parseTimeParam(time);
 | 
			
		||||
                this.initialSeekComplete = true;
 | 
			
		||||
            } else if (window.db) {
 | 
			
		||||
            } else if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
 | 
			
		||||
                var tx = window.db.transaction("watch_history", "readonly");
 | 
			
		||||
                var store = tx.objectStore("watch_history");
 | 
			
		||||
                var request = store.get(this.video.id);
 | 
			
		||||
| 
						 | 
				
			
			@ -256,9 +273,7 @@ export default {
 | 
			
		|||
 | 
			
		||||
            const MseSupport = window.MediaSource !== undefined;
 | 
			
		||||
 | 
			
		||||
            const lbry = this.getPreferenceBoolean("disableLBRY", false)
 | 
			
		||||
                ? null
 | 
			
		||||
                : this.video.videoStreams.filter(stream => stream.quality === "LBRY")[0];
 | 
			
		||||
            const lbry = null;
 | 
			
		||||
 | 
			
		||||
            var uri;
 | 
			
		||||
            var mime;
 | 
			
		||||
| 
						 | 
				
			
			@ -268,9 +283,10 @@ export default {
 | 
			
		|||
                mime = "application/x-mpegURL";
 | 
			
		||||
            } else if (this.video.audioStreams.length > 0 && !lbry && MseSupport) {
 | 
			
		||||
                if (!this.video.dash) {
 | 
			
		||||
                    const dash = (
 | 
			
		||||
                        await import("@/utils/DashUtils.js").then(mod => mod.default)
 | 
			
		||||
                    ).generate_dash_file_from_formats(streams, this.video.duration);
 | 
			
		||||
                    const dash = (await import("../utils/DashUtils.js")).generate_dash_file_from_formats(
 | 
			
		||||
                        streams,
 | 
			
		||||
                        this.video.duration,
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    uri = "data:application/dash+xml;charset=utf-8;base64," + btoa(dash);
 | 
			
		||||
                } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -306,7 +322,7 @@ export default {
 | 
			
		|||
                uri = this.video.hls;
 | 
			
		||||
                mime = "application/x-mpegURL";
 | 
			
		||||
            } else {
 | 
			
		||||
                uri = this.video.videoStreams.filter(stream => stream.codec == null).slice(-1)[0].url;
 | 
			
		||||
                uri = this.video.videoStreams.findLast(stream => stream.codec == null).url;
 | 
			
		||||
                mime = "video/mp4";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -356,22 +372,19 @@ export default {
 | 
			
		|||
            else this.setPlayerAttrs(this.$player, videoEl, uri, mime, this.$shaka);
 | 
			
		||||
 | 
			
		||||
            if (noPrevPlayer) {
 | 
			
		||||
                videoEl.addEventListener("loadeddata", () => {
 | 
			
		||||
                    if (document.pictureInPictureElement) videoEl.requestPictureInPicture();
 | 
			
		||||
                });
 | 
			
		||||
                videoEl.addEventListener("timeupdate", () => {
 | 
			
		||||
                    const time = videoEl.currentTime;
 | 
			
		||||
                    this.$emit("timeupdate", time);
 | 
			
		||||
                    this.updateProgressDatabase(time);
 | 
			
		||||
                    if (this.sponsors && this.sponsors.segments) {
 | 
			
		||||
                        this.sponsors.segments.map(segment => {
 | 
			
		||||
                            if (!segment.skipped || this.selectedAutoLoop) {
 | 
			
		||||
                                const end = segment.segment[1];
 | 
			
		||||
                                if (time >= segment.segment[0] && time < end) {
 | 
			
		||||
                                    console.log("Skipped segment at " + time);
 | 
			
		||||
                                    videoEl.currentTime = end;
 | 
			
		||||
                                    segment.skipped = true;
 | 
			
		||||
                                    return;
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                        const segment = this.findCurrentSegment(time);
 | 
			
		||||
                        this.inSegment = !!segment;
 | 
			
		||||
                        if (segment?.autoskip && (!segment.skipped || this.selectedAutoLoop)) {
 | 
			
		||||
                            this.skipSegment(videoEl, segment);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -386,18 +399,27 @@ export default {
 | 
			
		|||
                });
 | 
			
		||||
 | 
			
		||||
                videoEl.addEventListener("ended", () => {
 | 
			
		||||
                    if (
 | 
			
		||||
                        !this.selectedAutoLoop &&
 | 
			
		||||
                        this.selectedAutoPlay &&
 | 
			
		||||
                        (this.playlist?.relatedStreams?.length > 0 || this.video.relatedStreams.length > 0)
 | 
			
		||||
                    ) {
 | 
			
		||||
                        this.navigateNext();
 | 
			
		||||
                    }
 | 
			
		||||
                    this.$emit("ended");
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //TODO: Add sponsors on seekbar: https://github.com/ajayyy/SponsorBlock/blob/e39de9fd852adb9196e0358ed827ad38d9933e29/src/js-components/previewBar.ts#L12
 | 
			
		||||
        },
 | 
			
		||||
        findCurrentSegment(time) {
 | 
			
		||||
            return this.sponsors?.segments?.find(s => time >= s.segment[0] && time < s.segment[1]);
 | 
			
		||||
        },
 | 
			
		||||
        onClickSkipSegment() {
 | 
			
		||||
            const videoEl = this.$refs.videoEl;
 | 
			
		||||
            this.skipSegment(videoEl);
 | 
			
		||||
        },
 | 
			
		||||
        skipSegment(videoEl, segment) {
 | 
			
		||||
            const time = videoEl.currentTime;
 | 
			
		||||
            if (!segment) segment = this.findCurrentSegment(time);
 | 
			
		||||
            if (!segment) return;
 | 
			
		||||
            console.log("Skipped segment at " + time);
 | 
			
		||||
            videoEl.currentTime = segment.segment[1];
 | 
			
		||||
            segment.skipped = true;
 | 
			
		||||
        },
 | 
			
		||||
        setPlayerAttrs(localPlayer, videoEl, uri, mime, shaka) {
 | 
			
		||||
            const url = "/watch?v=" + this.video.id;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -471,8 +493,13 @@ export default {
 | 
			
		|||
 | 
			
		||||
            this.updateMarkers();
 | 
			
		||||
 | 
			
		||||
            const event = new Event("playerInit");
 | 
			
		||||
            window.dispatchEvent(event);
 | 
			
		||||
 | 
			
		||||
            const player = this.$ui.getControls().getPlayer();
 | 
			
		||||
 | 
			
		||||
            this.setupSeekbarPreview();
 | 
			
		||||
 | 
			
		||||
            this.$player = player;
 | 
			
		||||
 | 
			
		||||
            const disableVideo = this.getPreferenceBoolean("listen", false) && !this.video.livestream;
 | 
			
		||||
| 
						 | 
				
			
			@ -543,7 +570,7 @@ export default {
 | 
			
		|||
                    player.addTextTrackAsync(
 | 
			
		||||
                        subtitle.url,
 | 
			
		||||
                        subtitle.code,
 | 
			
		||||
                        "SUBTITLE",
 | 
			
		||||
                        "subtitles",
 | 
			
		||||
                        subtitle.mimeType,
 | 
			
		||||
                        null,
 | 
			
		||||
                        subtitle.name,
 | 
			
		||||
| 
						 | 
				
			
			@ -553,7 +580,14 @@ export default {
 | 
			
		|||
                const rate = this.getPreferenceNumber("rate", 1);
 | 
			
		||||
                videoEl.playbackRate = rate;
 | 
			
		||||
                videoEl.defaultPlaybackRate = rate;
 | 
			
		||||
 | 
			
		||||
                const autoDisplayCaptions = this.getPreferenceBoolean("autoDisplayCaptions", false);
 | 
			
		||||
                this.$player.setTextTrackVisibility(autoDisplayCaptions);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // expand the player to fullscreen when the fullscreen query equals true
 | 
			
		||||
            if (this.$route.query.fullscreen === "true" && !this.$ui.getControls().isFullScreenEnabled())
 | 
			
		||||
                this.$ui.getControls().toggleFullScreen();
 | 
			
		||||
        },
 | 
			
		||||
        async updateProgressDatabase(time) {
 | 
			
		||||
            // debounce
 | 
			
		||||
| 
						 | 
				
			
			@ -578,29 +612,7 @@ export default {
 | 
			
		|||
                this.$refs.videoEl.currentTime = time;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        navigateNext() {
 | 
			
		||||
            const params = this.$route.query;
 | 
			
		||||
            let url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url;
 | 
			
		||||
            const searchParams = new URLSearchParams();
 | 
			
		||||
            for (var param in params)
 | 
			
		||||
                switch (param) {
 | 
			
		||||
                    case "v":
 | 
			
		||||
                    case "t":
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "index":
 | 
			
		||||
                        if (this.index < this.playlist.relatedStreams.length) searchParams.set("index", this.index + 1);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "list":
 | 
			
		||||
                        if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list);
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        searchParams.set(param, params[param]);
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            const paramStr = searchParams.toString();
 | 
			
		||||
            if (paramStr.length > 0) url += "&" + paramStr;
 | 
			
		||||
            this.$router.push(url);
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        updateMarkers() {
 | 
			
		||||
            const markers = this.$refs.container.querySelector(".shaka-ad-markers");
 | 
			
		||||
            const array = ["to right"];
 | 
			
		||||
| 
						 | 
				
			
			@ -608,38 +620,19 @@ export default {
 | 
			
		|||
                const start = (segment.segment[0] / this.video.duration) * 100;
 | 
			
		||||
                const end = (segment.segment[1] / this.video.duration) * 100;
 | 
			
		||||
 | 
			
		||||
                var color;
 | 
			
		||||
                switch (segment.category) {
 | 
			
		||||
                    case "sponsor":
 | 
			
		||||
                        color = "#00d400";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "selfpromo":
 | 
			
		||||
                        color = "#ffff00";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "interaction":
 | 
			
		||||
                        color = "#cc00ff";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "poi_highlight":
 | 
			
		||||
                        color = "#ff1684";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "intro":
 | 
			
		||||
                        color = "#00ffff";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "outro":
 | 
			
		||||
                        color = "#0202ed";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "preview":
 | 
			
		||||
                        color = "#008fd6";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "filler":
 | 
			
		||||
                        color = "#7300FF";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "music_offtopic":
 | 
			
		||||
                        color = "#ff9900";
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        color = "white";
 | 
			
		||||
                }
 | 
			
		||||
                var color = [
 | 
			
		||||
                    "sponsor",
 | 
			
		||||
                    "selfpromo",
 | 
			
		||||
                    "interaction",
 | 
			
		||||
                    "poi_highlight",
 | 
			
		||||
                    "intro",
 | 
			
		||||
                    "outro",
 | 
			
		||||
                    "preview",
 | 
			
		||||
                    "filler",
 | 
			
		||||
                    "music_offtopic",
 | 
			
		||||
                ].includes(segment.category)
 | 
			
		||||
                    ? `var(--spon-seg-${segment.category})`
 | 
			
		||||
                    : "var(--spon-seg-default)";
 | 
			
		||||
 | 
			
		||||
                array.push(`transparent ${start}%`);
 | 
			
		||||
                array.push(`${color} ${start}%`);
 | 
			
		||||
| 
						 | 
				
			
			@ -653,33 +646,133 @@ export default {
 | 
			
		|||
 | 
			
		||||
            if (markers) markers.style.background = `linear-gradient(${array.join(",")})`;
 | 
			
		||||
        },
 | 
			
		||||
        destroy(hotkeys) {
 | 
			
		||||
            if (this.$ui) {
 | 
			
		||||
                this.$ui.destroy();
 | 
			
		||||
                this.$ui = undefined;
 | 
			
		||||
                this.$player = undefined;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.$player) {
 | 
			
		||||
                this.$player.destroy();
 | 
			
		||||
                this.$player = undefined;
 | 
			
		||||
            }
 | 
			
		||||
            if (hotkeys) this.$hotkeys?.unbind();
 | 
			
		||||
            this.$refs.container?.querySelectorAll("div").forEach(node => node.remove());
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    watch: {
 | 
			
		||||
        sponsors() {
 | 
			
		||||
        updateSponsors() {
 | 
			
		||||
            if (this.getPreferenceBoolean("showMarkers", true)) {
 | 
			
		||||
                this.shakaPromise.then(() => {
 | 
			
		||||
                    this.updateMarkers();
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        setupSeekbarPreview() {
 | 
			
		||||
            if (!this.video.previewFrames) return;
 | 
			
		||||
            let seekBar = document.querySelector(".shaka-seek-bar");
 | 
			
		||||
            // load the thumbnail preview when the user moves over the seekbar
 | 
			
		||||
            seekBar.addEventListener("mousemove", e => {
 | 
			
		||||
                this.isHoveringTimebar = true;
 | 
			
		||||
                const position = (e.offsetX / e.target.offsetWidth) * this.video.duration;
 | 
			
		||||
                this.showSeekbarPreview(position * 1000);
 | 
			
		||||
            });
 | 
			
		||||
            // hide the preview when the user stops hovering the seekbar
 | 
			
		||||
            seekBar.addEventListener("mouseout", () => {
 | 
			
		||||
                this.isHoveringTimebar = false;
 | 
			
		||||
                this.$refs.previewContainer.style.display = "none";
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async showSeekbarPreview(position) {
 | 
			
		||||
            const frame = this.getFrame(position);
 | 
			
		||||
            const originalImage = await this.loadImage(frame.url);
 | 
			
		||||
            if (!this.isHoveringTimebar) return;
 | 
			
		||||
 | 
			
		||||
            const seekBar = document.querySelector(".shaka-seek-bar");
 | 
			
		||||
            const container = this.$refs.previewContainer;
 | 
			
		||||
            const canvas = this.$refs.preview;
 | 
			
		||||
            const ctx = canvas.getContext("2d");
 | 
			
		||||
 | 
			
		||||
            const offsetX = frame.positionX * frame.frameWidth;
 | 
			
		||||
            const offsetY = frame.positionY * frame.frameHeight;
 | 
			
		||||
 | 
			
		||||
            canvas.width = frame.frameWidth > 100 ? frame.frameWidth : frame.frameWidth * 2;
 | 
			
		||||
            canvas.height = frame.frameWidth > 100 ? frame.frameHeight : frame.frameHeight * 2;
 | 
			
		||||
            // draw the thumbnail preview into the canvas by cropping only the relevant part
 | 
			
		||||
            ctx.drawImage(
 | 
			
		||||
                originalImage,
 | 
			
		||||
                offsetX,
 | 
			
		||||
                offsetY,
 | 
			
		||||
                frame.frameWidth,
 | 
			
		||||
                frame.frameHeight,
 | 
			
		||||
                0,
 | 
			
		||||
                0,
 | 
			
		||||
                canvas.width,
 | 
			
		||||
                canvas.height,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // calculate the thumbnail preview offset and display it
 | 
			
		||||
            const centerOffset = position / this.video.duration / 10;
 | 
			
		||||
            const left = centerOffset - ((0.5 * canvas.width) / seekBar.clientWidth) * 100;
 | 
			
		||||
            const maxLeft =
 | 
			
		||||
                ((seekBar.clientWidth - canvas.clientWidth) / seekBar.clientWidth) * 100 - this.seekbarPadding;
 | 
			
		||||
 | 
			
		||||
            this.currentTime = position / 1000;
 | 
			
		||||
 | 
			
		||||
            container.style.left = `max(${this.seekbarPadding}%, min(${left}%, ${maxLeft}%))`;
 | 
			
		||||
            container.style.display = "flex";
 | 
			
		||||
        },
 | 
			
		||||
        // ineffective algorithm to find the thumbnail corresponding to the currently hovered position in the video
 | 
			
		||||
        getFrame(position) {
 | 
			
		||||
            let startPosition = 0;
 | 
			
		||||
            const framePage = this.video.previewFrames.at(-1);
 | 
			
		||||
            for (let i = 0; i < framePage.urls.length; i++) {
 | 
			
		||||
                for (let positionY = 0; positionY < framePage.framesPerPageY; positionY++) {
 | 
			
		||||
                    for (let positionX = 0; positionX < framePage.framesPerPageX; positionX++) {
 | 
			
		||||
                        const endPosition = startPosition + framePage.durationPerFrame;
 | 
			
		||||
                        if (position >= startPosition && position <= endPosition) {
 | 
			
		||||
                            return {
 | 
			
		||||
                                url: framePage.urls[i],
 | 
			
		||||
                                positionX: positionX,
 | 
			
		||||
                                positionY: positionY,
 | 
			
		||||
                                frameWidth: framePage.frameWidth,
 | 
			
		||||
                                frameHeight: framePage.frameHeight,
 | 
			
		||||
                            };
 | 
			
		||||
                        }
 | 
			
		||||
                        startPosition = endPosition;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        },
 | 
			
		||||
        // creates a new image from an URL
 | 
			
		||||
        loadImage(url) {
 | 
			
		||||
            return new Promise(r => {
 | 
			
		||||
                const i = new Image();
 | 
			
		||||
                i.onload = () => r(i);
 | 
			
		||||
                i.src = url;
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        destroy(hotkeys) {
 | 
			
		||||
            if (this.$ui && !document.pictureInPictureElement) {
 | 
			
		||||
                this.$ui.destroy();
 | 
			
		||||
                this.$ui = undefined;
 | 
			
		||||
                this.$player = undefined;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.$player) {
 | 
			
		||||
                this.$player.destroy();
 | 
			
		||||
                if (!document.pictureInPictureElement) this.$player = undefined;
 | 
			
		||||
            }
 | 
			
		||||
            if (hotkeys) this.$hotkeys?.unbind();
 | 
			
		||||
            this.$refs.container?.querySelectorAll("div").forEach(node => node.remove());
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
:root {
 | 
			
		||||
    --player-base: rgba(255, 255, 255, 0.3);
 | 
			
		||||
    --player-buffered: rgba(255, 255, 255, 0.54);
 | 
			
		||||
    --player-played: rgba(255, 0, 0);
 | 
			
		||||
 | 
			
		||||
    --spon-seg-sponsor: #00d400;
 | 
			
		||||
    --spon-seg-selfpromo: #ffff00;
 | 
			
		||||
    --spon-seg-interaction: #cc00ff;
 | 
			
		||||
    --spon-seg-poi_highlight: #ff1684;
 | 
			
		||||
    --spon-seg-intro: #00ffff;
 | 
			
		||||
    --spon-seg-outro: #0202ed;
 | 
			
		||||
    --spon-seg-preview: #008fd6;
 | 
			
		||||
    --spon-seg-filler: #7300ff;
 | 
			
		||||
    --spon-seg-music_offtopic: #ff9900;
 | 
			
		||||
    --spon-seg-default: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.player-container {
 | 
			
		||||
    @apply max-h-75vh min-h-64;
 | 
			
		||||
    background: #000;
 | 
			
		||||
| 
						 | 
				
			
			@ -732,11 +825,15 @@ html .shaka-range-element:focus {
 | 
			
		|||
.material-icons-round {
 | 
			
		||||
    font-family: "Material Icons Round" !important;
 | 
			
		||||
}
 | 
			
		||||
.material-icons-round,
 | 
			
		||||
.shaka-bottom-controls .material-icons-round,
 | 
			
		||||
.shaka-current-time {
 | 
			
		||||
    -webkit-text-fill-color: #fff !important;
 | 
			
		||||
    filter: none !important;
 | 
			
		||||
    opacity: 1 !important;
 | 
			
		||||
    box-shadow: none !important;
 | 
			
		||||
}
 | 
			
		||||
.shaka-bottom-controls .material-icons-round {
 | 
			
		||||
    box-shadow: none !important;
 | 
			
		||||
}
 | 
			
		||||
.shaka-overflow-menu,
 | 
			
		||||
.shaka-settings-menu {
 | 
			
		||||
| 
						 | 
				
			
			@ -767,4 +864,17 @@ html .shaka-range-element:focus {
 | 
			
		|||
.shaka-controls-container {
 | 
			
		||||
    border-radius: var(--efy_radius) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.skip-segment-button {
 | 
			
		||||
    z-index: 1000;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    transform: translate(0, -50%);
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    right: 0;
 | 
			
		||||
    border-right: 0;
 | 
			
		||||
    border-radius: var(--efy_radius) 0 0 var(--efy_radius);
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										29
									
								
								src/components/WatchOnButton.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/components/WatchOnButton.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        link: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
        platform: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            required: false,
 | 
			
		||||
            default: "YouTube",
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <template v-if="getPreferenceBoolean('showWatchOnYouTube', false)">
 | 
			
		||||
        <a
 | 
			
		||||
            :href="link"
 | 
			
		||||
            role="button"
 | 
			
		||||
            class="pp-square flex items-center justify-center"
 | 
			
		||||
            :aria-label="'Watch on Odysee'"
 | 
			
		||||
            :title="`${$t('player.watch_on')}${platform}`"
 | 
			
		||||
        >
 | 
			
		||||
            <font-awesome-icon class="mx-1.5" :icon="['fab', platform.toLowerCase()]" />
 | 
			
		||||
        </a>
 | 
			
		||||
    </template>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +0,0 @@
 | 
			
		|||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    props: {
 | 
			
		||||
        link: String,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <template v-if="this.getPreferenceBoolean('showWatchOnYouTube', false)">
 | 
			
		||||
        <a :href="link" class="pp-watch-onyt-btn btn" role="button" :title="$t('player.watch_on') + 'YouTube'">
 | 
			
		||||
            <font-awesome-icon class="mx-1.5" :icon="['fab', 'youtube']" />
 | 
			
		||||
        </a>
 | 
			
		||||
    </template>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.pp-watch-onyt-btn {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    margin-left: var(--efy_gap0);
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    gap: 5rem;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,43 +1,48 @@
 | 
			
		|||
<template>
 | 
			
		||||
    <div v-if="video && isEmbed" class="absolute top-0 left-0 h-full w-full bg-black z-50">
 | 
			
		||||
    <div v-if="video && isEmbed" class="absolute left-0 top-0 z-50 h-full w-full bg-black">
 | 
			
		||||
        <VideoPlayer
 | 
			
		||||
            ref="videoPlayer"
 | 
			
		||||
            :video="video"
 | 
			
		||||
            :sponsors="sponsors"
 | 
			
		||||
            :playlist="playlist"
 | 
			
		||||
            :index="index"
 | 
			
		||||
            :selected-auto-play="false"
 | 
			
		||||
            :selected-auto-loop="selectedAutoLoop"
 | 
			
		||||
            :is-embed="isEmbed"
 | 
			
		||||
        />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div v-if="video && !isEmbed" class="w-full">
 | 
			
		||||
    <LoadingIndicatorPage :show-content="video && !isEmbed" class="w-full">
 | 
			
		||||
        <ErrorHandler v-if="video && video.error" :message="video.message" :error="video.error" />
 | 
			
		||||
        <Transition>
 | 
			
		||||
            <ToastComponent v-if="shouldShowToast" @dismissed="dismiss">
 | 
			
		||||
                <i18n-t keypath="info.next_video_countdown">{{ counter }}</i18n-t>
 | 
			
		||||
            </ToastComponent>
 | 
			
		||||
        </Transition>
 | 
			
		||||
 | 
			
		||||
        <div v-show="!video.error">
 | 
			
		||||
            <div :class="isMobile ? 'flex-col' : 'flex'">
 | 
			
		||||
                <VideoPlayer
 | 
			
		||||
                    ref="videoPlayer"
 | 
			
		||||
                    :video="video"
 | 
			
		||||
                    :sponsors="sponsors"
 | 
			
		||||
                    :playlist="playlist"
 | 
			
		||||
                    :index="index"
 | 
			
		||||
                    :selected-auto-play="selectedAutoPlay"
 | 
			
		||||
                    :selected-auto-loop="selectedAutoLoop"
 | 
			
		||||
                    @timeupdate="onTimeUpdate"
 | 
			
		||||
                />
 | 
			
		||||
                <keep-alive>
 | 
			
		||||
                    <VideoPlayer
 | 
			
		||||
                        ref="videoPlayer"
 | 
			
		||||
                        :video="video"
 | 
			
		||||
                        :sponsors="sponsors"
 | 
			
		||||
                        :selected-auto-play="selectedAutoPlay"
 | 
			
		||||
                        :selected-auto-loop="selectedAutoLoop"
 | 
			
		||||
                        @timeupdate="onTimeUpdate"
 | 
			
		||||
                        @ended="onVideoEnded"
 | 
			
		||||
                        @navigate-next="navigateNext"
 | 
			
		||||
                    />
 | 
			
		||||
                </keep-alive>
 | 
			
		||||
                <ChaptersBar
 | 
			
		||||
                    :mobileLayout="isMobile"
 | 
			
		||||
                    v-if="video?.chapters?.length > 0 && showChapters"
 | 
			
		||||
                    :mobile-layout="isMobile"
 | 
			
		||||
                    :chapters="video.chapters"
 | 
			
		||||
                    :player-position="currentTime"
 | 
			
		||||
                    @seek="navigate"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
            <!-- video title -->
 | 
			
		||||
            <div class="pp-video-title font-bold mt-2 text-2xl break-words" v-text="video.title" />
 | 
			
		||||
            <div class="pp-bellow-video flex flex-wrap mt-3 mb-3">
 | 
			
		||||
            <div class="pp-video-title mt-2 break-words text-2xl font-bold" v-text="video.title" />
 | 
			
		||||
            <div class="pp-bellow-video mb-3 mt-3 flex flex-wrap">
 | 
			
		||||
                <!-- views / date -->
 | 
			
		||||
                <div class="flex flex-auto">
 | 
			
		||||
                    <span v-t="{ path: 'video.views', args: { views: addCommas(video.views) } }" />
 | 
			
		||||
| 
						 | 
				
			
			@ -48,11 +53,11 @@
 | 
			
		|||
                <div class="pp-likes flex children:mr-2">
 | 
			
		||||
                    <template v-if="video.likes >= 0">
 | 
			
		||||
                        <div class="flex items-center">
 | 
			
		||||
                            <div class="i-fa-solid:thumbs-up" />
 | 
			
		||||
                            <div class="i-fa6-solid:thumbs-up" />
 | 
			
		||||
                            <strong class="ml-1" v-text="addCommas(video.likes)" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="flex items-center">
 | 
			
		||||
                            <div class="i-fa-solid:thumbs-down" />
 | 
			
		||||
                            <div class="i-fa6-solid:thumbs-down" />
 | 
			
		||||
                            <strong class="ml-1" v-text="video.dislikes >= 0 ? addCommas(video.dislikes) : '?'" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
| 
						 | 
				
			
			@ -72,54 +77,24 @@
 | 
			
		|||
                        video.uploader
 | 
			
		||||
                    }}</router-link>
 | 
			
		||||
                    <!-- Verified Badge -->
 | 
			
		||||
                    <font-awesome-icon class="ml-1" v-if="video.uploaderVerified" icon="check" />
 | 
			
		||||
                    <font-awesome-icon v-if="video.uploaderVerified" class="ml-1" icon="check" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="pp-watch-buttons">
 | 
			
		||||
                    <!-- Subscribe button -->
 | 
			
		||||
                    <button
 | 
			
		||||
                        class="btn"
 | 
			
		||||
                        @click="subscribeHandler"
 | 
			
		||||
                        v-t="{
 | 
			
		||||
                            path: `actions.${subscribed ? 'unsubscribe' : 'subscribe'}`,
 | 
			
		||||
                            args: { count: numberFormat(video.uploaderSubscriberCount) },
 | 
			
		||||
                        }"
 | 
			
		||||
                        class="btn"
 | 
			
		||||
                        @click="subscribeHandler"
 | 
			
		||||
                    />
 | 
			
		||||
                    <!-- RSS Feed button -->
 | 
			
		||||
                    <a
 | 
			
		||||
                        aria-label="RSS feed"
 | 
			
		||||
                        title="RSS feed"
 | 
			
		||||
                        role="button"
 | 
			
		||||
                        v-if="video.uploaderUrl"
 | 
			
		||||
                        :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
 | 
			
		||||
                        target="_blank"
 | 
			
		||||
                        class="btn flex-col"
 | 
			
		||||
                    >
 | 
			
		||||
                        <font-awesome-icon icon="rss" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <WatchOnYouTubeButton :link="`https://youtu.be/${getVideoId()}`" />
 | 
			
		||||
                    <!-- Share Dialog -->
 | 
			
		||||
                    <button class="btn" @click="showShareModal = !showShareModal">
 | 
			
		||||
                        <i18n-t class="lt-lg:hidden" keypath="actions.share" tag="strong"></i18n-t>
 | 
			
		||||
                        <font-awesome-icon class="mx-1.5 ml-1" icon="fa-share" />
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <!-- LBRY -->
 | 
			
		||||
                    <a v-if="video.lbryId" :href="'https://odysee.com/' + video.lbryId" class="btn">
 | 
			
		||||
                        <i18n-t keypath="player.watch_on" tag="strong">LBRY</i18n-t>
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <!-- listen / watch toggle -->
 | 
			
		||||
                    <router-link
 | 
			
		||||
                        :to="toggleListenUrl"
 | 
			
		||||
                        :aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                        :title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                        class="btn flex-col"
 | 
			
		||||
                    >
 | 
			
		||||
                        <font-awesome-icon :icon="isListening ? 'tv' : 'headphones'" />
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                    <!-- Playlist Add button -->
 | 
			
		||||
                    <button class="btn" v-if="authenticated" @click="showModal = !showModal">
 | 
			
		||||
                    <button v-if="authenticated" class="btn" @click="showModal = !showModal">
 | 
			
		||||
                        {{ $t("actions.add_to_playlist") }}<font-awesome-icon class="ml-1" icon="circle-plus" />
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <PlaylistAddModal v-if="showModal" :video-id="getVideoId()" @close="showModal = !showModal" />
 | 
			
		||||
                    <!-- Share Dialog -->
 | 
			
		||||
                    <ShareModal
 | 
			
		||||
                        v-if="showShareModal"
 | 
			
		||||
                        :video-id="getVideoId()"
 | 
			
		||||
| 
						 | 
				
			
			@ -128,59 +103,116 @@
 | 
			
		|||
                        :playlist-index="index"
 | 
			
		||||
                        @close="showShareModal = !showShareModal"
 | 
			
		||||
                    />
 | 
			
		||||
                    <button class="btn flex items-center" @click="showShareModal = !showShareModal">
 | 
			
		||||
                        <font-awesome-icon class="mx-1.5 mr-1" icon="fa-share" />
 | 
			
		||||
                        <i18n-t class="lt-lg:hidden" keypath="actions.share" tag="strong"></i18n-t>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <!-- YouTube -->
 | 
			
		||||
                    <WatchOnButton :link="`https://youtu.be/${getVideoId()}`" />
 | 
			
		||||
                    <!-- Odysee -->
 | 
			
		||||
                    <WatchOnButton v-if="video.lbryId" platform="Odysee" :link="`https://odysee.com/${video.lbryId}`" />
 | 
			
		||||
                    <!-- listen / watch toggle -->
 | 
			
		||||
                    <router-link
 | 
			
		||||
                        :to="toggleListenUrl"
 | 
			
		||||
                        role="button"
 | 
			
		||||
                        :aria-label="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                        :title="(isListening ? 'Watch ' : 'Listen to ') + video.title"
 | 
			
		||||
                        class="pp-square btn flex items-center"
 | 
			
		||||
                    >
 | 
			
		||||
                        <font-awesome-icon class="mx-1.5" :icon="isListening ? 'tv' : 'headphones'" />
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                    <!-- RSS Feed button -->
 | 
			
		||||
                    <a
 | 
			
		||||
                        v-if="video.uploaderUrl"
 | 
			
		||||
                        aria-label="RSS feed"
 | 
			
		||||
                        title="RSS feed"
 | 
			
		||||
                        role="button"
 | 
			
		||||
                        :href="`${apiUrl()}/feed/unauthenticated/rss?channels=${video.uploaderUrl.split('/')[2]}`"
 | 
			
		||||
                        target="_blank"
 | 
			
		||||
                        class="pp-square btn flex items-center"
 | 
			
		||||
                    >
 | 
			
		||||
                        <font-awesome-icon class="mx-1.5" icon="rss" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <hr />
 | 
			
		||||
            <hr class="mb-2" />
 | 
			
		||||
 | 
			
		||||
            <div
 | 
			
		||||
                v-for="metaInfo in video?.metaInfo ?? []"
 | 
			
		||||
                :key="metaInfo.title"
 | 
			
		||||
                class="btn my-3 flex flex-wrap cursor-default gap-2 px-4 py-2"
 | 
			
		||||
            >
 | 
			
		||||
                <span>{{ metaInfo.description ?? metaInfo.title }}</span>
 | 
			
		||||
                <a v-for="(link, linkIndex) in metaInfo.urls" :key="linkIndex" :href="link" class="underline">{{
 | 
			
		||||
                    metaInfo.urlTexts[linkIndex]
 | 
			
		||||
                }}</a>
 | 
			
		||||
                <br />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div efy_select>
 | 
			
		||||
                <input id="showDesc" type="checkbox" v-model="showDesc" />
 | 
			
		||||
                <label for="showDesc" v-t="'actions.show_description'" />
 | 
			
		||||
                <input id="showComments" type="checkbox" v-model="showComments" @click="toggleComments" />
 | 
			
		||||
                <label for="showComments" v-t="'actions.show_comments'" />
 | 
			
		||||
                <input id="showRecs" type="checkbox" v-model="showRecs" />
 | 
			
		||||
                <label for="showRecs" v-t="'actions.show_recommendations'" />
 | 
			
		||||
                <input id="showDesc" v-model="showDesc" type="checkbox" />
 | 
			
		||||
                <label v-t="'actions.show_description'" for="showDesc" />
 | 
			
		||||
                <input id="showComments" v-model="showComments" type="checkbox" @click="toggleComments" />
 | 
			
		||||
                <label
 | 
			
		||||
                    v-text="`${$t('actions.show_comments')} - ${numberFormat(comments?.commentCount)}`"
 | 
			
		||||
                    for="showComments"
 | 
			
		||||
                />
 | 
			
		||||
                <input id="showRecs" v-model="showRecs" type="checkbox" />
 | 
			
		||||
                <label v-t="'actions.show_recommendations'" for="showRecs" />
 | 
			
		||||
                <span v-show="video?.chapters?.length > 0">
 | 
			
		||||
                    <input id="showChapters" v-model="showChapters" type="checkbox" />
 | 
			
		||||
                    <label v-t="'actions.show_chapters'" class="ml-2" for="showChapters" />
 | 
			
		||||
                </span>
 | 
			
		||||
                <input id="chkAutoLoop" v-model="selectedAutoLoop" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
                <label for="chkAutoLoop" v-text="`${$t('actions.loop_this_video')}`" />
 | 
			
		||||
                <input id="chkAutoPlay" v-model="selectedAutoPlay" type="checkbox" @change="onChange($event)" />
 | 
			
		||||
                <label for="chkAutoPlay" v-text="`${$t('actions.auto_play_next_video')}`" />
 | 
			
		||||
                <span v-show="video?.chapters?.length > 0">
 | 
			
		||||
                    <input id="showChapters" type="checkbox" v-model="showChapters" />
 | 
			
		||||
                    <label class="ml-2" for="showChapters" v-t="'actions.show_chapters'" />
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
            <div
 | 
			
		||||
                v-show="showDesc"
 | 
			
		||||
                class="break-words mb-2"
 | 
			
		||||
                v-html="purifyHTML(video.description)"
 | 
			
		||||
                style="border-top: var(--efy_border); margin: 15rem 0; padding: 15rem 0"
 | 
			
		||||
            />
 | 
			
		||||
            <div
 | 
			
		||||
                v-if="showDesc && sponsors && sponsors.segments"
 | 
			
		||||
                v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
 | 
			
		||||
            />
 | 
			
		||||
            <template v-if="showDesc">
 | 
			
		||||
                <hr />
 | 
			
		||||
                <!-- eslint-disable-next-line vue/no-v-html -->
 | 
			
		||||
                <div class="description break-words" v-html="purifiedDescription" />
 | 
			
		||||
                <hr />
 | 
			
		||||
                <div
 | 
			
		||||
                    v-if="sponsors && sponsors.segments"
 | 
			
		||||
                    v-text="`${$t('video.sponsor_segments')}: ${sponsors.segments.length}`"
 | 
			
		||||
                />
 | 
			
		||||
                <div v-if="video.category" v-text="`${$t('video.category')}: ${video.category}`" />
 | 
			
		||||
                <div v-text="`${$t('video.license')}: ${video.license}`" />
 | 
			
		||||
                <div class="capitalize" v-text="`${$t('video.visibility')}: ${video.visibility}`" />
 | 
			
		||||
                <hr />
 | 
			
		||||
                <div v-if="video.tags" class="video-tags">
 | 
			
		||||
                    <router-link
 | 
			
		||||
                        v-for="tag in video.tags"
 | 
			
		||||
                        :key="tag"
 | 
			
		||||
                        class="line-clamp-1 efy_trans_filter efy_shadow_trans"
 | 
			
		||||
                        :to="`/results?search_query=${encodeURIComponent(tag)}`"
 | 
			
		||||
                        >{{ tag }}</router-link
 | 
			
		||||
                    >
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <hr />
 | 
			
		||||
 | 
			
		||||
        <div class="grid pp-rec-vids">
 | 
			
		||||
        <div class="pp-rec-vids grid">
 | 
			
		||||
            <div v-if="!showComments" class="w-full"></div>
 | 
			
		||||
            <div v-if="!comments" class="">
 | 
			
		||||
                <p class="text-center mt-8" v-t="'comment.loading'"></p>
 | 
			
		||||
            <div v-else-if="!comments">
 | 
			
		||||
                <p v-t="'comment.loading'" class="mt-8 text-center"></p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else-if="comments.disabled" class="">
 | 
			
		||||
                <p class="text-center mt-8" v-t="'comment.disabled'"></p>
 | 
			
		||||
            <div v-else-if="comments.disabled">
 | 
			
		||||
                <p v-t="'comment.disabled'" class="mt-8 text-center"></p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else ref="comments" v-show="showComments" class="pp-comments">
 | 
			
		||||
            <div v-else ref="comments" class="pp-comments">
 | 
			
		||||
                <CommentItem
 | 
			
		||||
                    v-for="comment in comments.comments"
 | 
			
		||||
                    :key="comment.commentId"
 | 
			
		||||
                    :comment="comment"
 | 
			
		||||
                    :uploader="video.uploader"
 | 
			
		||||
                    :video-id="getVideoId()"
 | 
			
		||||
                    class="efy_trans_filter"
 | 
			
		||||
                    class="efy_trans_filter efy_shadow_trans"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -203,7 +235,7 @@
 | 
			
		|||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    </LoadingIndicatorPage>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +247,11 @@ import ChaptersBar from "./ChaptersBar.vue";
 | 
			
		|||
import PlaylistAddModal from "./PlaylistAddModal.vue";
 | 
			
		||||
import ShareModal from "./ShareModal.vue";
 | 
			
		||||
import PlaylistVideos from "./PlaylistVideos.vue";
 | 
			
		||||
import WatchOnYouTubeButton from "./WatchOnYouTubeButton.vue";
 | 
			
		||||
import WatchOnButton from "./WatchOnButton.vue";
 | 
			
		||||
import LoadingIndicatorPage from "./LoadingIndicatorPage.vue";
 | 
			
		||||
import ToastComponent from "./ToastComponent.vue";
 | 
			
		||||
import { parseTimeParam } from "@/utils/Misc";
 | 
			
		||||
import { purifyHTML, rewriteDescription } from "@/utils/HtmlUtils";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "App",
 | 
			
		||||
| 
						 | 
				
			
			@ -228,14 +264,14 @@ export default {
 | 
			
		|||
        PlaylistAddModal,
 | 
			
		||||
        ShareModal,
 | 
			
		||||
        PlaylistVideos,
 | 
			
		||||
        WatchOnYouTubeButton,
 | 
			
		||||
        WatchOnButton,
 | 
			
		||||
        LoadingIndicatorPage,
 | 
			
		||||
        ToastComponent,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        const smallViewQuery = window.matchMedia("(max-width: 640px)");
 | 
			
		||||
        return {
 | 
			
		||||
            video: {
 | 
			
		||||
                title: "Loading...",
 | 
			
		||||
            },
 | 
			
		||||
            video: null,
 | 
			
		||||
            playlistId: null,
 | 
			
		||||
            playlist: null,
 | 
			
		||||
            index: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -256,6 +292,9 @@ export default {
 | 
			
		|||
            showShareModal: false,
 | 
			
		||||
            isMobile: true,
 | 
			
		||||
            currentTime: 0,
 | 
			
		||||
            shouldShowToast: false,
 | 
			
		||||
            timeoutCounter: null,
 | 
			
		||||
            counter: 0,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
| 
						 | 
				
			
			@ -277,6 +316,12 @@ export default {
 | 
			
		|||
                year: "numeric",
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        defaultCounter(_this) {
 | 
			
		||||
            return _this.getPreferenceNumber("autoPlayNextCountdown", 5);
 | 
			
		||||
        },
 | 
			
		||||
        purifiedDescription() {
 | 
			
		||||
            return purifyHTML(this.video.description);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        // check screen size
 | 
			
		||||
| 
						 | 
				
			
			@ -295,7 +340,7 @@ export default {
 | 
			
		|||
            (async () => {
 | 
			
		||||
                const videoId = this.getVideoId();
 | 
			
		||||
                const instance = this;
 | 
			
		||||
                if (window.db && !this.video.error) {
 | 
			
		||||
                if (window.db && this.getPreferenceBoolean("watchHistory", false) && !this.video.error) {
 | 
			
		||||
                    var tx = window.db.transaction("watch_history", "readwrite");
 | 
			
		||||
                    var store = tx.objectStore("watch_history");
 | 
			
		||||
                    var request = store.get(videoId);
 | 
			
		||||
| 
						 | 
				
			
			@ -325,6 +370,8 @@ export default {
 | 
			
		|||
        this.getPlaylistData();
 | 
			
		||||
        this.getSponsors();
 | 
			
		||||
        if (!this.isEmbed && this.showComments) this.getComments();
 | 
			
		||||
        if (this.isEmbed) document.querySelector("html").style.overflow = "hidden";
 | 
			
		||||
        window.addEventListener("click", this.handleClick);
 | 
			
		||||
        window.addEventListener("resize", () => {
 | 
			
		||||
            this.smallView = this.smallViewQuery.matches;
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -336,7 +383,7 @@ export default {
 | 
			
		|||
        this.showDesc = !this.getPreferenceBoolean("minimizeDescription", false);
 | 
			
		||||
        this.showRecs = !this.getPreferenceBoolean("minimizeRecommendations", false);
 | 
			
		||||
        this.showChapters = !this.getPreferenceBoolean("minimizeChapters", false);
 | 
			
		||||
        if (this.video.duration) {
 | 
			
		||||
        if (this.video?.duration) {
 | 
			
		||||
            document.title = this.video.title + " - Piped";
 | 
			
		||||
            this.$refs.videoPlayer.loadVideo();
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -345,24 +392,45 @@ export default {
 | 
			
		|||
    deactivated() {
 | 
			
		||||
        this.active = false;
 | 
			
		||||
        window.removeEventListener("scroll", this.handleScroll);
 | 
			
		||||
        this.dismiss();
 | 
			
		||||
    },
 | 
			
		||||
    unmounted() {
 | 
			
		||||
        window.removeEventListener("scroll", this.handleScroll);
 | 
			
		||||
        window.removeEventListener("click", this.handleClick);
 | 
			
		||||
        this.dismiss();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        fetchVideo() {
 | 
			
		||||
            return this.fetchJson(this.apiUrl() + "/streams/" + this.getVideoId());
 | 
			
		||||
        },
 | 
			
		||||
        async fetchSponsors() {
 | 
			
		||||
            return await this.fetchJson(this.apiUrl() + "/sponsors/" + this.getVideoId(), {
 | 
			
		||||
                category:
 | 
			
		||||
                    '["' +
 | 
			
		||||
                    this.getPreferenceString("selectedSkip", "sponsor,interaction,selfpromo,music_offtopic").replaceAll(
 | 
			
		||||
                        ",",
 | 
			
		||||
                        '","',
 | 
			
		||||
                    ) +
 | 
			
		||||
                    '"]',
 | 
			
		||||
            var selectedSkip = this.getPreferenceString(
 | 
			
		||||
                "selectedSkip",
 | 
			
		||||
                "sponsor,interaction,selfpromo,music_offtopic",
 | 
			
		||||
            ).split(",");
 | 
			
		||||
            const skipOptions = this.getPreferenceJSON("skipOptions");
 | 
			
		||||
            if (skipOptions !== undefined) {
 | 
			
		||||
                selectedSkip = Object.keys(skipOptions).filter(
 | 
			
		||||
                    k => skipOptions[k] !== undefined && skipOptions[k] !== "no",
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const sponsors = await this.fetchJson(this.apiUrl() + "/sponsors/" + this.getVideoId(), {
 | 
			
		||||
                category: JSON.stringify(selectedSkip),
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            sponsors?.segments?.forEach(segment => {
 | 
			
		||||
                const option = skipOptions[segment.category];
 | 
			
		||||
                segment.autoskip = option === undefined || option === "auto";
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const minSegmentLength = Math.max(this.getPreferenceNumber("minSegmentLength", 0), 0);
 | 
			
		||||
            sponsors.segments = sponsors.segments?.filter(segment => {
 | 
			
		||||
                const length = segment.segment[1] - segment.segment[0];
 | 
			
		||||
                return length >= minSegmentLength;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return sponsors;
 | 
			
		||||
        },
 | 
			
		||||
        toggleComments() {
 | 
			
		||||
            this.showComments = !this.showComments;
 | 
			
		||||
| 
						 | 
				
			
			@ -395,13 +463,10 @@ export default {
 | 
			
		|||
                                elem.outerHTML = elem.getAttribute("href");
 | 
			
		||||
                        });
 | 
			
		||||
                        xmlDoc.querySelectorAll("br").forEach(elem => (elem.outerHTML = "\n"));
 | 
			
		||||
                        this.video.description = this.urlify(xmlDoc.querySelector("body").innerHTML)
 | 
			
		||||
                            .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")
 | 
			
		||||
                            .replaceAll(
 | 
			
		||||
                                /(?:http(?:s)?:\/\/)?(?:www\.)?youtu\.be\/(?:watch\?v=)?([/a-zA-Z0-9_?=&-]*)/gm,
 | 
			
		||||
                                "/watch?v=$1",
 | 
			
		||||
                            )
 | 
			
		||||
                            .replaceAll("\n", "<br>");
 | 
			
		||||
                        this.video.description = rewriteDescription(xmlDoc.querySelector("body").innerHTML);
 | 
			
		||||
                        this.updateWatched(this.video.relatedStreams);
 | 
			
		||||
 | 
			
		||||
                        this.fetchDeArrowContent(this.video.relatedStreams);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -422,6 +487,9 @@ export default {
 | 
			
		|||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                await this.fetchPlaylistPages().then(() => {
 | 
			
		||||
                    this.fetchDeArrowContent(this.playlist.relatedStreams);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async fetchPlaylistPages() {
 | 
			
		||||
| 
						 | 
				
			
			@ -500,6 +568,35 @@ export default {
 | 
			
		|||
            }
 | 
			
		||||
            this.subscribed = !this.subscribed;
 | 
			
		||||
        },
 | 
			
		||||
        handleClick(event) {
 | 
			
		||||
            if (!event || !event.target) return;
 | 
			
		||||
            if (!event.target.matches("a[href]")) return;
 | 
			
		||||
            const target = event.target;
 | 
			
		||||
            if (!target.getAttribute("href")) return;
 | 
			
		||||
            if (this.handleTimestampLinks(target)) {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        handleTimestampLinks(target) {
 | 
			
		||||
            try {
 | 
			
		||||
                const url = new URL(target.getAttribute("href"), document.baseURI);
 | 
			
		||||
                if (
 | 
			
		||||
                    url.searchParams.size > 2 ||
 | 
			
		||||
                    url.searchParams.get("v") !== this.getVideoId() ||
 | 
			
		||||
                    !url.searchParams.has("t")
 | 
			
		||||
                ) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                const time = parseTimeParam(url.searchParams.get("t"));
 | 
			
		||||
                if (time) {
 | 
			
		||||
                    this.navigate(time);
 | 
			
		||||
                }
 | 
			
		||||
                return true;
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        handleScroll() {
 | 
			
		||||
            if (this.loading || !this.comments || !this.comments.nextpage) return;
 | 
			
		||||
            if (window.innerHeight + window.scrollY >= this.$refs.comments?.offsetHeight - window.innerHeight) {
 | 
			
		||||
| 
						 | 
				
			
			@ -523,6 +620,73 @@ export default {
 | 
			
		|||
        onTimeUpdate(time) {
 | 
			
		||||
            this.currentTime = time;
 | 
			
		||||
        },
 | 
			
		||||
        onVideoEnded() {
 | 
			
		||||
            if (
 | 
			
		||||
                !this.selectedAutoLoop &&
 | 
			
		||||
                this.selectedAutoPlay &&
 | 
			
		||||
                (this.playlist?.relatedStreams?.length > 0 || this.video.relatedStreams.length > 0)
 | 
			
		||||
            ) {
 | 
			
		||||
                this.showToast();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        showToast() {
 | 
			
		||||
            this.counter = this.defaultCounter;
 | 
			
		||||
            if (this.counter < 1) {
 | 
			
		||||
                this.navigateNext();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.timeoutCounter) clearInterval(this.timeoutCounter);
 | 
			
		||||
            this.timeoutCounter = setInterval(() => {
 | 
			
		||||
                this.counter--;
 | 
			
		||||
                if (this.counter === 0) {
 | 
			
		||||
                    this.dismiss();
 | 
			
		||||
                    this.navigateNext();
 | 
			
		||||
                }
 | 
			
		||||
            }, 1000);
 | 
			
		||||
            this.shouldShowToast = true;
 | 
			
		||||
        },
 | 
			
		||||
        dismiss() {
 | 
			
		||||
            clearInterval(this.timeoutCounter);
 | 
			
		||||
            this.shouldShowToast = false;
 | 
			
		||||
        },
 | 
			
		||||
        navigateNext() {
 | 
			
		||||
            const params = this.$route.query;
 | 
			
		||||
            let url = this.playlist?.relatedStreams?.[this.index]?.url ?? this.video.relatedStreams[0].url;
 | 
			
		||||
            const searchParams = new URLSearchParams();
 | 
			
		||||
            for (var param in params)
 | 
			
		||||
                switch (param) {
 | 
			
		||||
                    case "v":
 | 
			
		||||
                    case "t":
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "index":
 | 
			
		||||
                        if (this.index < this.playlist.relatedStreams.length) searchParams.set("index", this.index + 1);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "list":
 | 
			
		||||
                        if (this.index < this.playlist.relatedStreams.length) searchParams.set("list", params.list);
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        searchParams.set(param, params[param]);
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            // save the fullscreen state
 | 
			
		||||
            searchParams.set("fullscreen", this.$refs.videoPlayer.$ui.getControls().isFullScreenEnabled());
 | 
			
		||||
            const paramStr = searchParams.toString();
 | 
			
		||||
            if (paramStr.length > 0) url += "&" + paramStr;
 | 
			
		||||
            this.$router.push(url);
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.v-enter-from,
 | 
			
		||||
.v-leave-to {
 | 
			
		||||
    opacity: 0;
 | 
			
		||||
    transform: translateX(100%) scale(0.5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.description a {
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
    filter: brightness(0.75);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								src/locales/ang.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/locales/ang.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
{}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,15 +4,18 @@
 | 
			
		|||
        "login": "تسجيل الدخول",
 | 
			
		||||
        "register": "إنشاء حساب",
 | 
			
		||||
        "preferences": "الإعدادات",
 | 
			
		||||
        "history": "تاريخ التصفح",
 | 
			
		||||
        "history": "سجل المشاهدة",
 | 
			
		||||
        "subscriptions": "الاشتراكات",
 | 
			
		||||
        "playlists": "قوائم التشغيل",
 | 
			
		||||
        "feed": "التغذية",
 | 
			
		||||
        "feed": "محتوى الاشتراكات",
 | 
			
		||||
        "account": "الحساب",
 | 
			
		||||
        "instance": "الخادم",
 | 
			
		||||
        "player": "المشغل",
 | 
			
		||||
        "livestreams": "البث المباشر",
 | 
			
		||||
        "channels": "القنوات"
 | 
			
		||||
        "channels": "القنوات",
 | 
			
		||||
        "bookmarks": "الاشارات المرجعيه",
 | 
			
		||||
        "channel_groups": "مجموعات القنوات",
 | 
			
		||||
        "dearrow": "دي ارو"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "شاهد عبر"
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +44,7 @@
 | 
			
		|||
        "enable_sponsorblock": "تفعيل مانع الإعلانات",
 | 
			
		||||
        "auto": "تلقائي",
 | 
			
		||||
        "dark": "داكن",
 | 
			
		||||
        "search": "بحث",
 | 
			
		||||
        "search": "بحث (Ctrl+K)",
 | 
			
		||||
        "autoplay_video": "تشغيل تلقائي",
 | 
			
		||||
        "audio_only": "صوت فقط",
 | 
			
		||||
        "default_quality": "الجودة الأساسية",
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +53,7 @@
 | 
			
		|||
        "skip_interaction": "تخطي تذكير التفاعل (اشتراك)",
 | 
			
		||||
        "skip_non_music": "تخطي الموسيقى: قسم غير الموسيقى",
 | 
			
		||||
        "theme": "السمة",
 | 
			
		||||
        "instance_selection": "تحديد المثيل",
 | 
			
		||||
        "instance_selection": "قائمة الخوادم",
 | 
			
		||||
        "export_to_json": "تصدير إلى JSON",
 | 
			
		||||
        "show_more": "اظهار المزيد",
 | 
			
		||||
        "skip_outro": "تخطي بطاقات النهاية / الاعتمادات",
 | 
			
		||||
| 
						 | 
				
			
			@ -60,12 +63,12 @@
 | 
			
		|||
        "skip_filler_tangent": "تخطي المحتوى الغير مهم",
 | 
			
		||||
        "show_markers": "إظهار العلامات على المشغل",
 | 
			
		||||
        "buffering_goal": "هدف التخزين المؤقت (بالثواني)",
 | 
			
		||||
        "country_selection": "اختيار البلد",
 | 
			
		||||
        "country_selection": "البلد",
 | 
			
		||||
        "default_homepage": "الصفحة الرئيسية الافتراضية",
 | 
			
		||||
        "show_comments": "إظهار التعليقات",
 | 
			
		||||
        "minimize_description_default": "تصغير الوصف بشكل افتراضي",
 | 
			
		||||
        "store_watch_history": "تخزين سجل المشاهدة",
 | 
			
		||||
        "language_selection": "اختيار اللغة",
 | 
			
		||||
        "language_selection": "اللغة",
 | 
			
		||||
        "instances_list": "قائمة المثيلات",
 | 
			
		||||
        "enabled_codecs": "برامج الترميز الممكنة (متعددة)",
 | 
			
		||||
        "import_from_json": "استيراد من JSON/CSV",
 | 
			
		||||
| 
						 | 
				
			
			@ -90,7 +93,7 @@
 | 
			
		|||
        "minimize_recommendations_default": "تقليل التوصيات بشكل افتراضي",
 | 
			
		||||
        "invalidate_session": "تسجيل الخروج من جميع الأجهزة",
 | 
			
		||||
        "different_auth_instance": "استخدام مثيل مختلف للمصادقة",
 | 
			
		||||
        "instance_auth_selection": "تحديد مثيل Autentication",
 | 
			
		||||
        "instance_auth_selection": "خادم المصادقة",
 | 
			
		||||
        "clone_playlist": "استنساخ قائمة التشغيل",
 | 
			
		||||
        "clone_playlist_success": "تم استنساخها بنجاح!",
 | 
			
		||||
        "download_as_txt": "تنزيل بتنسيق .txt",
 | 
			
		||||
| 
						 | 
				
			
			@ -105,10 +108,8 @@
 | 
			
		|||
        "follow_link": "اتبع الرابط",
 | 
			
		||||
        "copy_link": "نسخ الرابط",
 | 
			
		||||
        "time_code": "رمز الوقت (بالثواني)",
 | 
			
		||||
        "rename_playlist": "إعادة تسمية قائمة التشغيل",
 | 
			
		||||
        "new_playlist_name": "اسم قائمة تشغيل جديد",
 | 
			
		||||
        "show_chapters": "الفصول",
 | 
			
		||||
        "store_search_history": "حفظ سجل البحث",
 | 
			
		||||
        "store_search_history": "تخزين سجل البحث",
 | 
			
		||||
        "documentation": "التوثيق",
 | 
			
		||||
        "status_page": "الحالة",
 | 
			
		||||
        "source_code": "شفرة المصدر",
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +121,29 @@
 | 
			
		|||
        "show_watch_on_youtube": "عرض زر مشاهدة على يوتيوب",
 | 
			
		||||
        "minimize_chapters_default": "تصغير الفصول بشكل افتراضي",
 | 
			
		||||
        "no_valid_playlists": "لا يحتوي الملف على قوائم تشغيل صالحة!",
 | 
			
		||||
        "with_playlist": "المشاركة مع قائمة التشغيل"
 | 
			
		||||
        "with_playlist": "المشاركة مع قائمة التشغيل",
 | 
			
		||||
        "bookmark_playlist": "الاشاره المرجعيه",
 | 
			
		||||
        "playlist_bookmarked": "تم وضعها في الاشارات المرجعية",
 | 
			
		||||
        "skip_button_only": "إظهار زر التخطي",
 | 
			
		||||
        "skip_automatically": "تلقائيا",
 | 
			
		||||
        "min_segment_length": "الحد الأدنى لطول الفصل (بالثواني)",
 | 
			
		||||
        "skip_segment": "تخطي الجزء",
 | 
			
		||||
        "show_less": "عرض أقل",
 | 
			
		||||
        "autoplay_next_countdown": "العد التنازلي الافتراضي حتى الفيديو التالي ( ثانية )",
 | 
			
		||||
        "dismiss": "تجاهل",
 | 
			
		||||
        "group_name": "أسم المجموعة",
 | 
			
		||||
        "create_group": "إنشاء مجموعة",
 | 
			
		||||
        "auto_display_captions": "عرض التسميات التوضيحية تلقائيا",
 | 
			
		||||
        "cancel": "إلغاء",
 | 
			
		||||
        "okay": "حسنًا",
 | 
			
		||||
        "playlist_description": "وصف قائمة التشغيل",
 | 
			
		||||
        "playlist_name": "اسم قائمة التشغيل",
 | 
			
		||||
        "edit_playlist": "تعديل قائمة التشغيل",
 | 
			
		||||
        "show_search_suggestions": "إظهار اقتراحات البحث",
 | 
			
		||||
        "chapters_layout_mobile": "تخطيط الفصول على الهاتف",
 | 
			
		||||
        "delete_automatically": "الحذف تلقائيا بعد",
 | 
			
		||||
        "enable_dearrow": "تمكين دي ارو",
 | 
			
		||||
        "generate_qrcode": "إنشاء رمز الاستجابة السريعة"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "sponsor_segments": "المقاطع الإعلانية",
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +153,13 @@
 | 
			
		|||
        "views": "{views} عدد المشاهدات",
 | 
			
		||||
        "shorts": "فديوهات قصيرة",
 | 
			
		||||
        "videos": "الفيديوات",
 | 
			
		||||
        "live": "{0} مباشر"
 | 
			
		||||
        "live": "{0} مباشر",
 | 
			
		||||
        "all": "الكل",
 | 
			
		||||
        "category": "الفئة",
 | 
			
		||||
        "chapters_vertical": "رَأسِيّ",
 | 
			
		||||
        "chapters_horizontal": "أفقي",
 | 
			
		||||
        "visibility": "الظهور",
 | 
			
		||||
        "license": "الترخيص"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "channels": "يوتيوب: القنوات",
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +170,8 @@
 | 
			
		|||
        "music_videos": "YT Music: مقاطع فيديو",
 | 
			
		||||
        "did_you_mean": "هل تقصد: {0}؟",
 | 
			
		||||
        "music_playlists": "YT Music: قوائم التشغيل",
 | 
			
		||||
        "music_albums": "YT Music: ألبومات"
 | 
			
		||||
        "music_albums": "YT Music: ألبومات",
 | 
			
		||||
        "music_artists": "YT الموسيقى: الفنانين"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "version": "الإصدار",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +190,9 @@
 | 
			
		|||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "اسم المستخدم",
 | 
			
		||||
        "password": "كلمة السر"
 | 
			
		||||
        "password": "كلمة السر",
 | 
			
		||||
        "passwords_incorrect": "كلمة المرور لم تتطابق!",
 | 
			
		||||
        "password_confirm": "تأكيد كلمة المرور"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "مشترك في: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +205,12 @@
 | 
			
		|||
        "page_not_found": "لم يتم العثور على الصفحة",
 | 
			
		||||
        "copied": "نسخ!",
 | 
			
		||||
        "cannot_copy": "لا يمكن نسخه!",
 | 
			
		||||
        "local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟"
 | 
			
		||||
        "local_storage": "يتطلب هذا الإجراء التخزين المحلي، هل يتم تمكين ملفات تعريف الارتباط؟",
 | 
			
		||||
        "register_no_email_note": "لا ينصح باستخدام البريد الإلكتروني كاسم مستخدم. المضي قدما على أي حال؟",
 | 
			
		||||
        "next_video_countdown": "تشغيل الفيديو التالي بعد { 0 } ق",
 | 
			
		||||
        "weeks": "{amount} أسبوع (أسابيع)",
 | 
			
		||||
        "hours": "{amount} ساعة (ساعات)",
 | 
			
		||||
        "months": "{amount} شهر (أشهر)",
 | 
			
		||||
        "days": "{amount} يوم (أيام)"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,15 +4,17 @@
 | 
			
		|||
        "login": "Daxil ol",
 | 
			
		||||
        "register": "Qeydiyyatdan keç",
 | 
			
		||||
        "feed": "Axın",
 | 
			
		||||
        "preferences": "Seçimlər",
 | 
			
		||||
        "preferences": "Üstünlüklər",
 | 
			
		||||
        "history": "Tarixçə",
 | 
			
		||||
        "subscriptions": "Abunəliklər",
 | 
			
		||||
        "playlists": "Pleylistlər",
 | 
			
		||||
        "playlists": "Oynatma Siyahıları",
 | 
			
		||||
        "account": "Hesab",
 | 
			
		||||
        "instance": "Nümunə",
 | 
			
		||||
        "player": "Oynadıcı",
 | 
			
		||||
        "livestreams": "Canlı yayımlar",
 | 
			
		||||
        "channels": "Kanallar"
 | 
			
		||||
        "livestreams": "Canlı Yayımlar",
 | 
			
		||||
        "channels": "Kanallar",
 | 
			
		||||
        "bookmarks": "Əlfəcinlər",
 | 
			
		||||
        "channel_groups": "Kanal qrupları"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "{0} saytında bax"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,12 +32,12 @@
 | 
			
		|||
        "uses_api_from": "API-dən istifadə edir ",
 | 
			
		||||
        "enable_sponsorblock": "SponsorBlok'u Aktivləşdir",
 | 
			
		||||
        "skip_sponsors": "Sponsorları Ötür",
 | 
			
		||||
        "skip_intro": "Fasilə/Giriş Animasiyasını Ötür",
 | 
			
		||||
        "skip_outro": "Bitiş Kartları/Kreditləri Ötür",
 | 
			
		||||
        "skip_intro": "Fasilə/Giriş Animasiyasın Ötür",
 | 
			
		||||
        "skip_outro": "Son Kartları/Kreditləri Ötür",
 | 
			
		||||
        "skip_preview": "Önbaxışı/Anonsu Ötür",
 | 
			
		||||
        "skip_interaction": "İnteraksiya Xatırladıcısını Ötür(Abunə Ol)",
 | 
			
		||||
        "skip_self_promo": "Ödənişsiz/Özünü Reklamı Ötür",
 | 
			
		||||
        "skip_non_music": "Musiqini Ötür: Musiqi Olmayan Bölmə",
 | 
			
		||||
        "skip_interaction": "Əlaqələndirmə Xatırladıcısın Ötür(Abunə Ol)",
 | 
			
		||||
        "skip_self_promo": "Ödənilməmiş/Özünü Reklamı Ötür",
 | 
			
		||||
        "skip_non_music": "Musiqini Ötür: Musiqisiz Bölmə",
 | 
			
		||||
        "skip_highlight": "Anonsu Ötür",
 | 
			
		||||
        "skip_filler_tangent": "Doldurucu Səhnələri Ötür",
 | 
			
		||||
        "theme": "Tema",
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +46,7 @@
 | 
			
		|||
        "light": "İşıqlı",
 | 
			
		||||
        "autoplay_video": "Videonu Avto-oynat",
 | 
			
		||||
        "audio_only": "Yalnız Səs",
 | 
			
		||||
        "default_quality": "Defolt Keyfiyyət",
 | 
			
		||||
        "default_quality": "Standart Keyfiyyət",
 | 
			
		||||
        "buffering_goal": "Tamponlama hədəfi (saniyələrlə)",
 | 
			
		||||
        "export_to_json": "JSON-a İxrac Et",
 | 
			
		||||
        "import_from_json": "JSON/CSV-dan İdxal Et",
 | 
			
		||||
| 
						 | 
				
			
			@ -53,61 +55,59 @@
 | 
			
		|||
        "donations": "İnkişaf ianələri",
 | 
			
		||||
        "minimize_description": "Açıqlamanı Kiçilt",
 | 
			
		||||
        "show_description": "Açıqlamanı Göstər",
 | 
			
		||||
        "minimize_recommendations": "Tövsiyələri kiçilt",
 | 
			
		||||
        "minimize_recommendations": "Tövsiyələri Kiçilt",
 | 
			
		||||
        "show_recommendations": "Tövsiyələri Göstər",
 | 
			
		||||
        "disable_lbry": "Yayım üçün LBRY-ni deaktiv et",
 | 
			
		||||
        "enable_lbry_proxy": "LBRY üçün Proksi-ni Aktivləşdir",
 | 
			
		||||
        "view_ssl_score": "SSL Nəticəsinə Bax",
 | 
			
		||||
        "search": "Axtarış",
 | 
			
		||||
        "search": "Axtarış (Ctrl+K)",
 | 
			
		||||
        "filter": "Filtr",
 | 
			
		||||
        "loading": "Yüklənir...",
 | 
			
		||||
        "clear_history": "Tarixçəni Təmizlə",
 | 
			
		||||
        "hide_replies": "Cavabları Gizlət",
 | 
			
		||||
        "load_more_replies": "Daha Çox Cavab Yüklə",
 | 
			
		||||
        "add_to_playlist": "Pleylistə Əlavə Et",
 | 
			
		||||
        "remove_from_playlist": "Pleylistdən Sil",
 | 
			
		||||
        "delete_playlist_video_confirm": "Video pleylistdən silinsin?",
 | 
			
		||||
        "create_playlist": "Pleylist Yarat",
 | 
			
		||||
        "delete_playlist": "Pleylisti Sil",
 | 
			
		||||
        "select_playlist": "Pleylist Seç",
 | 
			
		||||
        "delete_playlist_confirm": "Bu pleylist silinsin?",
 | 
			
		||||
        "please_select_playlist": "Lütfən, pleylist seç",
 | 
			
		||||
        "add_to_playlist": "Oynatma siyahısına əlavə et",
 | 
			
		||||
        "remove_from_playlist": "Oynatma siyahısından təmizlə",
 | 
			
		||||
        "delete_playlist_video_confirm": "Video oynatma siyahısından silinsin?",
 | 
			
		||||
        "create_playlist": "Oynatma Siyahısı Yarat",
 | 
			
		||||
        "delete_playlist": "Oynatma Siyahısın Sil",
 | 
			
		||||
        "select_playlist": "Oynatma Siyahısı Seç",
 | 
			
		||||
        "delete_playlist_confirm": "Bu oynatma siyahısı silinsin?",
 | 
			
		||||
        "please_select_playlist": "Xahiş edilir, oynatma siyahısı seç",
 | 
			
		||||
        "country_selection": "Ölkə Seçimi",
 | 
			
		||||
        "default_homepage": "Defolt Əsas Səhifə",
 | 
			
		||||
        "default_homepage": "Standart Əsas Səhifə",
 | 
			
		||||
        "show_comments": "Şərhləri Göstər",
 | 
			
		||||
        "instance_selection": "Nümunə Seçimi",
 | 
			
		||||
        "minimize_description_default": "Açıqlamanı Defolt Olaraq Kiçilt",
 | 
			
		||||
        "minimize_description_default": "Açıqlamanı Standart Olaraq Kiçilt",
 | 
			
		||||
        "language_selection": "Dil Seçimi",
 | 
			
		||||
        "instances_list": "Nümunələr Siyahısı",
 | 
			
		||||
        "show_more": "Daha Çox Göstər",
 | 
			
		||||
        "no": "Xeyr",
 | 
			
		||||
        "store_watch_history": "Baxış Tarixçəsini Saxla",
 | 
			
		||||
        "enabled_codecs": "Aktiv Kodeklər (Birdən çox)",
 | 
			
		||||
        "store_watch_history": "Baxış Tarixçəsin Saxla",
 | 
			
		||||
        "enabled_codecs": "Aktiv Kodlayıcılar (Çoxlu)",
 | 
			
		||||
        "yes": "Bəli",
 | 
			
		||||
        "show_markers": "Oynadıcıda Markerləri Göstər",
 | 
			
		||||
        "delete_account": "Hesabı Sil",
 | 
			
		||||
        "logout": "Bu cihazdan çıx",
 | 
			
		||||
        "minimize_recommendations_default": "Defolt olaraq Tövsiyələri kiçilt",
 | 
			
		||||
        "minimize_recommendations_default": "Standart olaraq Tövsiyələri kiçilt",
 | 
			
		||||
        "download_as_txt": ".txt kimi endir",
 | 
			
		||||
        "reset_preferences": "Seçimləri sıfırla",
 | 
			
		||||
        "reset_preferences": "Üstünlükləri sıfırla",
 | 
			
		||||
        "confirm_reset_preferences": "Seçimlərinizi sıfırlamaq istədiyinizə əminsiniz?",
 | 
			
		||||
        "backup_preferences": "Yedəkləmə seçimləri",
 | 
			
		||||
        "backup_preferences": "Nüsxələmə seçimləri",
 | 
			
		||||
        "restore_preferences": "Seçimləri bərpa et",
 | 
			
		||||
        "invalidate_session": "Bütün cihazlardan çıxın",
 | 
			
		||||
        "different_auth_instance": "Təsdiqləmə üçün fərqli nümunə istifadə et",
 | 
			
		||||
        "instance_auth_selection": "Təsdiqləmə Nümunəsi Seçimi",
 | 
			
		||||
        "clone_playlist": "Pleylisti Klonla",
 | 
			
		||||
        "clone_playlist": "Oynatma Siyahısın Klonla",
 | 
			
		||||
        "clone_playlist_success": "Uğurla klonlandı!",
 | 
			
		||||
        "rename_playlist": "Pleylist adını dəyiş",
 | 
			
		||||
        "time_code": "Vaxt kodu (saniyələrlə)",
 | 
			
		||||
        "store_search_history": "Axtarış tarixçəsini saxla",
 | 
			
		||||
        "documentation": "Sertifikatlaşdırma",
 | 
			
		||||
        "documentation": "Sənədləşdirmə",
 | 
			
		||||
        "status_page": "Vəziyyət",
 | 
			
		||||
        "source_code": "Mənbə kodu",
 | 
			
		||||
        "instance_donations": "Nümunə ianələri",
 | 
			
		||||
        "hide_watched": "Axında baxılan videoları gizlət",
 | 
			
		||||
        "show_chapters": "Bölmələr",
 | 
			
		||||
        "new_playlist_name": "Yeni pleylist adı",
 | 
			
		||||
        "share": "Paylaş",
 | 
			
		||||
        "with_timecode": "Vaxt kodu ilə paylaş",
 | 
			
		||||
        "follow_link": "Bağlantını izlə",
 | 
			
		||||
| 
						 | 
				
			
			@ -117,21 +117,40 @@
 | 
			
		|||
        "reply_count": "{count} cavab",
 | 
			
		||||
        "minimize_comments_default": "Şərhləri standart olaraq kiçilt",
 | 
			
		||||
        "minimize_comments": "Şərhləri Kiçilt",
 | 
			
		||||
        "minimize_chapters_default": "Defolt olaraq bölmələri kiçilt",
 | 
			
		||||
        "show_watch_on_youtube": "YouTube-da Baxış düyməsini göstər",
 | 
			
		||||
        "no_valid_playlists": "Faylda etibarlı pleylistlər yoxdur!",
 | 
			
		||||
        "with_playlist": "Pleylistlə paylaş"
 | 
			
		||||
        "minimize_chapters_default": "Standart olaraq bölmələri kiçilt",
 | 
			
		||||
        "show_watch_on_youtube": "YouTube-da Baxış düyməsin göstər",
 | 
			
		||||
        "no_valid_playlists": "Faylın etibarlı oynatma siyahıları yoxdur!",
 | 
			
		||||
        "with_playlist": "Oynatma siyahısıyla paylaş",
 | 
			
		||||
        "bookmark_playlist": "Əlfəcin",
 | 
			
		||||
        "playlist_bookmarked": "Əlfəcinləndi",
 | 
			
		||||
        "skip_button_only": "Ötürmə düyməsin göstər",
 | 
			
		||||
        "skip_automatically": "Avtomatik olaraq",
 | 
			
		||||
        "min_segment_length": "Minimum Seqment Uzunluğu (saniyələrlə)",
 | 
			
		||||
        "skip_segment": "Seqmenti ötür",
 | 
			
		||||
        "show_less": "Daha az göstər",
 | 
			
		||||
        "autoplay_next_countdown": "Növbəti videoya qədər standart geri sayım (saniyə)",
 | 
			
		||||
        "dismiss": "Rədd et",
 | 
			
		||||
        "create_group": "Qrup yarat",
 | 
			
		||||
        "group_name": "Qrup adı",
 | 
			
		||||
        "cancel": "Ləğv et",
 | 
			
		||||
        "edit_playlist": "Oynatma siyahısın redaktə et",
 | 
			
		||||
        "playlist_description": "Oynatma siyahısı təsviri",
 | 
			
		||||
        "okay": "Oldu!",
 | 
			
		||||
        "chapters_layout_mobile": "Mobildə Bölmələrin Tərtibatı",
 | 
			
		||||
        "playlist_name": "Oynatma siyahısı adı",
 | 
			
		||||
        "show_search_suggestions": "Axtarış təkliflərin göstər",
 | 
			
		||||
        "auto_display_captions": "Titrləri Avtomatik Göstər"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Tərəfindən Sabitləndi {author}",
 | 
			
		||||
        "disabled": "Şərhlər yükləyici tərəfindən deaktiv edilib.",
 | 
			
		||||
        "loading": "Şərhlər yüklənir...",
 | 
			
		||||
        "user_disabled": "Şərhlər tənzimləmələrdə deaktiv edilib."
 | 
			
		||||
        "pinned_by": "{author} tərəfindən sabitlənib",
 | 
			
		||||
        "disabled": "Şərhlər yükləyici tərəfindən bağlanıb.",
 | 
			
		||||
        "loading": "Şərhlər yüklənilir...",
 | 
			
		||||
        "user_disabled": "Şərhlər tənzimləmələrdə qeyri-aktiv edilib."
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_name": "Nümunə Adı",
 | 
			
		||||
        "instance_locations": "Nümunə Məkanları",
 | 
			
		||||
        "has_cdn": "CDN varmı?",
 | 
			
		||||
        "has_cdn": "CDN Varmı?",
 | 
			
		||||
        "registered_users": "Qeydiyyatdan Keçmiş İstifadəçilər",
 | 
			
		||||
        "version": "Versiya",
 | 
			
		||||
        "up_to_date": "Güncəllənib?",
 | 
			
		||||
| 
						 | 
				
			
			@ -146,21 +165,25 @@
 | 
			
		|||
        "views": "{views} baxış",
 | 
			
		||||
        "watched": "Baxılıb",
 | 
			
		||||
        "sponsor_segments": "Sponsorlar Seqmentləri",
 | 
			
		||||
        "ratings_disabled": "Reytinqlər Deaktivdir",
 | 
			
		||||
        "ratings_disabled": "Reytinqlər Qeyri-aktivdir",
 | 
			
		||||
        "chapters": "Bölmələr",
 | 
			
		||||
        "live": "{0} Canlı",
 | 
			
		||||
        "shorts": "Qısa"
 | 
			
		||||
        "shorts": "Qısa",
 | 
			
		||||
        "all": "Hamısı",
 | 
			
		||||
        "category": "Kateqoriya",
 | 
			
		||||
        "chapters_horizontal": "Üfüqi",
 | 
			
		||||
        "chapters_vertical": "Şaquli"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Bunu nəzərdə tutursunuz: {0}?",
 | 
			
		||||
        "all": "YouTube: Hamısı",
 | 
			
		||||
        "videos": "YouTube: Videolar",
 | 
			
		||||
        "channels": "YouTube: Kanallar",
 | 
			
		||||
        "playlists": "YouTube: Pleylistlər",
 | 
			
		||||
        "playlists": "YouTube: Oynatma siyahıları",
 | 
			
		||||
        "music_songs": "YT Music: Mahnılar",
 | 
			
		||||
        "music_videos": "YT Music: Videolar",
 | 
			
		||||
        "music_albums": "YT Music: Albomlar",
 | 
			
		||||
        "music_playlists": "YT Music: Pleylistlər"
 | 
			
		||||
        "music_playlists": "YT Music: Oynatma Siyahıları"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Abunə oldu: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -169,10 +192,12 @@
 | 
			
		|||
        "preferences_note": "Qeyd: seçimlər brauzerinizin yerli yaddaşında saxlanılır. Brauzer məlumatlarınızın silinməsi onları sıfırlayacaq."
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "Qeyd: Seçimlər brauzerinizin yerli yaddaşında saxlanılır. Brauzer məlumatlarınızın silinməsi onları sıfırlayacaq.",
 | 
			
		||||
        "preferences_note": "Qeyd: Seçimlər brauzerinizin öz yaddaşında saxlanılır. Brauzer məlumatınızın silinməsi onları sıfırlayacaq.",
 | 
			
		||||
        "page_not_found": "Səhifə tapılmadı",
 | 
			
		||||
        "copied": "Kopyalandı!",
 | 
			
		||||
        "cannot_copy": "Kopyalanmır!",
 | 
			
		||||
        "local_storage": "Bu fəaliyyət localStorage tələb edir, məlumat bazası aktivdir?"
 | 
			
		||||
        "copied": "Nüsxələndi!",
 | 
			
		||||
        "cannot_copy": "Nüsxələnmir!",
 | 
			
		||||
        "local_storage": "Bu fəaliyyət yerli yaddaş tələb edir, məlumat bazası aktivdir?",
 | 
			
		||||
        "register_no_email_note": "E-poçt-u istifadəçi adı kimi istifadə etmək tövsiyə edilmir. Baxmayaraq ki, davam edilsin?",
 | 
			
		||||
        "next_video_countdown": "Növbəti video {0} saniyəyə oynadılır"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										169
									
								
								src/locales/bg.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								src/locales/bg.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,169 @@
 | 
			
		|||
{
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "channels": "Канали",
 | 
			
		||||
        "login": "Вход",
 | 
			
		||||
        "register": "Регистрация",
 | 
			
		||||
        "feed": "Емисия",
 | 
			
		||||
        "history": "История",
 | 
			
		||||
        "playlists": "Плейлисти",
 | 
			
		||||
        "instance": "Инстанция",
 | 
			
		||||
        "player": "Плейър",
 | 
			
		||||
        "livestreams": "Излъчвания на живо",
 | 
			
		||||
        "bookmarks": "Отметки",
 | 
			
		||||
        "trending": "Набиращи популярност",
 | 
			
		||||
        "account": "Профил",
 | 
			
		||||
        "preferences": "Настройки",
 | 
			
		||||
        "subscriptions": "Абонаменти",
 | 
			
		||||
        "dearrow": "DeArrow",
 | 
			
		||||
        "channel_groups": "Канални групи"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "most_recent": "Най-скорошен",
 | 
			
		||||
        "unsubscribe": "Отписване - {count}",
 | 
			
		||||
        "uses_api_from": "Използва API от ",
 | 
			
		||||
        "skip_sponsors": "Пропускане на спонсори",
 | 
			
		||||
        "skip_preview": "Пропускане на преглед/обобщение",
 | 
			
		||||
        "skip_self_promo": "Пропускане на самореклама/неплатена реклама",
 | 
			
		||||
        "min_segment_length": "Минимална дължина на сегмента (в секунди)",
 | 
			
		||||
        "default_quality": "Качество по подразбиране",
 | 
			
		||||
        "minimize_comments_default": "Минимизиране на коментарите по подразбиране",
 | 
			
		||||
        "subscribe": "Абониране - {count}",
 | 
			
		||||
        "view_subscriptions": "Преглед на абонаменти",
 | 
			
		||||
        "sort_by": "Сортиране по:",
 | 
			
		||||
        "least_recent": "Най-малко скорошен",
 | 
			
		||||
        "channel_name_asc": "Име на канал (А-Я)",
 | 
			
		||||
        "channel_name_desc": "Име на канал (Я-А)",
 | 
			
		||||
        "back": "Назад",
 | 
			
		||||
        "enable_sponsorblock": "Активиране на SponsorBlock",
 | 
			
		||||
        "skip_button_only": "Показване на бутона за пропускане",
 | 
			
		||||
        "skip_automatically": "Автоматично",
 | 
			
		||||
        "skip_intro": "Пропускане на прекъсване/въвеждаща анимация",
 | 
			
		||||
        "skip_outro": "Пропускане на крайни карти/надписи",
 | 
			
		||||
        "skip_interaction": "Пропускане на напомняне за абониране",
 | 
			
		||||
        "skip_non_music": "Попускане Немузикален раздел в музика",
 | 
			
		||||
        "skip_highlight": "Пропускане на видео акцент",
 | 
			
		||||
        "show_markers": "Показване на маркери в плейъра",
 | 
			
		||||
        "skip_segment": "Пропускане на сегмент",
 | 
			
		||||
        "theme": "Тема",
 | 
			
		||||
        "auto": "Автоматично",
 | 
			
		||||
        "dark": "Тъмна",
 | 
			
		||||
        "light": "Светла",
 | 
			
		||||
        "autoplay_video": "Автоматично пускане на видео",
 | 
			
		||||
        "audio_only": "Само аудио",
 | 
			
		||||
        "buffering_goal": "Буфериране (в секунди)",
 | 
			
		||||
        "country_selection": "Избор на държава",
 | 
			
		||||
        "default_homepage": "Начална страница по подразбиране",
 | 
			
		||||
        "minimize_description_default": "Минимизиране на описанието по подразбиране",
 | 
			
		||||
        "store_watch_history": "Запазване на историята на гледане",
 | 
			
		||||
        "language_selection": "Избор на език",
 | 
			
		||||
        "instances_list": "Списък на инстанциите",
 | 
			
		||||
        "enabled_codecs": "Разрешени кодеци (множество)",
 | 
			
		||||
        "instance_selection": "Избор на инстанция",
 | 
			
		||||
        "show_more": "Покажи повече",
 | 
			
		||||
        "yes": "Да",
 | 
			
		||||
        "no": "Не",
 | 
			
		||||
        "export_to_json": "Експорт в JSON",
 | 
			
		||||
        "import_from_json": "Импорт от JSON/CSV",
 | 
			
		||||
        "loop_this_video": "Повтаряне на това видео",
 | 
			
		||||
        "auto_play_next_video": "Автоматично пускане на следващото видео",
 | 
			
		||||
        "donations": "Дарения за разработка",
 | 
			
		||||
        "minimize_comments": "Минимизиране на коментарите",
 | 
			
		||||
        "show_comments": "Показване на коментарите",
 | 
			
		||||
        "show_description": "Показване на описание",
 | 
			
		||||
        "search": "Търси",
 | 
			
		||||
        "minimize_description": "Минимизиране на описание",
 | 
			
		||||
        "filter": "Филтър",
 | 
			
		||||
        "clear_history": "Изчистване на историята",
 | 
			
		||||
        "minimize_recommendations": "Минимизиране на препоръчани",
 | 
			
		||||
        "show_recommendations": "Показване на препоръчани",
 | 
			
		||||
        "view_ssl_score": "Преглед на SSL резултат",
 | 
			
		||||
        "loading": "Зареждане...",
 | 
			
		||||
        "hide_replies": "Скрий отговорите",
 | 
			
		||||
        "load_more_replies": "Зареди още отговори",
 | 
			
		||||
        "remove_from_playlist": "Премахване от плейлист",
 | 
			
		||||
        "create_playlist": "Създаване на плейлист",
 | 
			
		||||
        "reset_preferences": "Нулиране на настройките",
 | 
			
		||||
        "with_timecode": "Сподели с текущото време",
 | 
			
		||||
        "piped_link": "Piped връзка",
 | 
			
		||||
        "documentation": "Документация",
 | 
			
		||||
        "delete_account": "Изтрий акаунта",
 | 
			
		||||
        "download_as_txt": "Изтегляне като .txt",
 | 
			
		||||
        "share": "Сподели",
 | 
			
		||||
        "follow_link": "Последвай връзката",
 | 
			
		||||
        "add_to_playlist": "Добави към плейлист",
 | 
			
		||||
        "delete_playlist_video_confirm": "Да се премахне ли видеото от плейлиста?",
 | 
			
		||||
        "show_watch_on_youtube": "Показване на бутона \"Гледай в YouTube\"",
 | 
			
		||||
        "source_code": "Изходен код",
 | 
			
		||||
        "minimize_chapters_default": "Минимизиране на разделите по подразбиране",
 | 
			
		||||
        "minimize_recommendations_default": "Минимизиране на препоръчани по подразбиране",
 | 
			
		||||
        "show_chapters": "Раздели",
 | 
			
		||||
        "logout": "Отписване от това устройство",
 | 
			
		||||
        "clone_playlist": "Клониране на плейлист",
 | 
			
		||||
        "clone_playlist_success": "Успешно клониране!",
 | 
			
		||||
        "backup_preferences": "Архивиране на настройките",
 | 
			
		||||
        "back_to_home": "Обратно към начална страница",
 | 
			
		||||
        "status_page": "Статус",
 | 
			
		||||
        "copy_link": "Копирай връзката",
 | 
			
		||||
        "time_code": "Текущо време (в секунди)",
 | 
			
		||||
        "reply_count": "{count} отговора",
 | 
			
		||||
        "restore_preferences": "Възстановяване на настройките",
 | 
			
		||||
        "invalidate_session": "Отписване от всички устройства",
 | 
			
		||||
        "different_auth_instance": "Използване на различна инстанция за удостоверяване",
 | 
			
		||||
        "store_search_history": "Запазване на историята на търсене",
 | 
			
		||||
        "instance_auth_selection": "Избор на инстанция за удостоверяване",
 | 
			
		||||
        "confirm_reset_preferences": "Сигурни ли сте, че искате да нулирате настройките?",
 | 
			
		||||
        "hide_watched": "Скриване на гледани видеоклипове в Абонаменти",
 | 
			
		||||
        "enable_dearrow": "Включи DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Гледай в {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Потребителско име",
 | 
			
		||||
        "password": "Парола"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Видеоклипове",
 | 
			
		||||
        "views": "{views} показвания",
 | 
			
		||||
        "chapters": "Раздели",
 | 
			
		||||
        "all": "Всички",
 | 
			
		||||
        "watched": "Гледани",
 | 
			
		||||
        "category": "Категория"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "version": "Версия",
 | 
			
		||||
        "registered_users": "Регистрирани потребители",
 | 
			
		||||
        "instance_locations": "Местоположения на инстанция",
 | 
			
		||||
        "instance_name": "Име на инстанция",
 | 
			
		||||
        "has_cdn": "Има ли CDN?",
 | 
			
		||||
        "up_to_date": "Актуален?",
 | 
			
		||||
        "ssl_score": "SSL резултат"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "disabled": "Коментарите са деактивирани.",
 | 
			
		||||
        "pinned_by": "Фиксиран от {author}",
 | 
			
		||||
        "loading": "Коментарите се зареждат...",
 | 
			
		||||
        "user_disabled": "Коментарите са деактивирани в настройките."
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Имахте предвид: {0}?",
 | 
			
		||||
        "all": "YouTube: Всички",
 | 
			
		||||
        "videos": "YouTube: Видеоклипове",
 | 
			
		||||
        "channels": "YouTube: Канали",
 | 
			
		||||
        "playlists": "YouTube: Плейлисти",
 | 
			
		||||
        "music_songs": "YT Music: Песни",
 | 
			
		||||
        "music_videos": "YT Music: Видеоклипове",
 | 
			
		||||
        "music_albums": "YT Music: Албуми",
 | 
			
		||||
        "music_playlists": "YT Music: Плейлисти"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Абониран за: {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "page_not_found": "Страницата не е намерена",
 | 
			
		||||
        "copied": "Копирано!",
 | 
			
		||||
        "cannot_copy": "Не може да се копира!",
 | 
			
		||||
        "local_storage": "Това действие изисква localStorage, разрешени ли са бисквитките?",
 | 
			
		||||
        "register_no_email_note": "Използването на имейл като потребителско име не се препоръчва. Продължете все пак?"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
        "audio_only": "Samo zvuk",
 | 
			
		||||
        "default_homepage": "Zadana početna stranica",
 | 
			
		||||
        "loop_this_video": "Stavite ovaj video na ponavljanje",
 | 
			
		||||
        "search": "Pretraga",
 | 
			
		||||
        "search": "Pretraga (Ctrl+K)",
 | 
			
		||||
        "skip_preview": "Preskočite pregled",
 | 
			
		||||
        "skip_non_music": "Preskočite muziku: sekcija bez muzike",
 | 
			
		||||
        "skip_self_promo": "Preskočite neplaćenu/samo-promociju",
 | 
			
		||||
| 
						 | 
				
			
			@ -77,8 +77,6 @@
 | 
			
		|||
        "minimize_chapters_default": "Smanjite poglavlja po zadanom",
 | 
			
		||||
        "show_watch_on_youtube": "Prikaži „Gledaj na YouTube-u” dugme",
 | 
			
		||||
        "different_auth_instance": "Koristite drugu instancu za autentifikaciju",
 | 
			
		||||
        "rename_playlist": "Preimenuj listu izvođenja",
 | 
			
		||||
        "new_playlist_name": "Novi naziv liste izvođenja",
 | 
			
		||||
        "with_timecode": "Podijelite s vremenskim kodom",
 | 
			
		||||
        "piped_link": "Piped poveznica",
 | 
			
		||||
        "follow_link": "Prati poveznicu",
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +100,16 @@
 | 
			
		|||
        "minimize_comments": "Minimizirajte komentare",
 | 
			
		||||
        "delete_account": "Izbriši račun",
 | 
			
		||||
        "minimize_recommendations_default": "Smanjite preporuke po zadanom",
 | 
			
		||||
        "reset_preferences": "Vrati postavke na zadano"
 | 
			
		||||
        "reset_preferences": "Vrati postavke na zadano",
 | 
			
		||||
        "bookmark_playlist": "Bilježak",
 | 
			
		||||
        "playlist_bookmarked": "Obilježeno",
 | 
			
		||||
        "show_less": "Prikaži manje",
 | 
			
		||||
        "skip_button_only": "Prikaži dugme za preskakanje",
 | 
			
		||||
        "skip_automatically": "Automatski",
 | 
			
		||||
        "min_segment_length": "Najmanja dužina segmenta (u sekundama)",
 | 
			
		||||
        "skip_segment": "Preskoči segment",
 | 
			
		||||
        "autoplay_next_countdown": "Zadano odbrojavanje do sljedećeg videa (u sekundama)",
 | 
			
		||||
        "dismiss": "Odbaci"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "register": "Registrirajte se",
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +124,8 @@
 | 
			
		|||
        "account": "Račun",
 | 
			
		||||
        "player": "Pokretnik",
 | 
			
		||||
        "channels": "Kanali",
 | 
			
		||||
        "livestreams": "Prijenosi uživo"
 | 
			
		||||
        "livestreams": "Prijenosi uživo",
 | 
			
		||||
        "bookmarks": "Bilješci"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "music_songs": "YT Music: Pjesme",
 | 
			
		||||
| 
						 | 
				
			
			@ -154,7 +162,9 @@
 | 
			
		|||
        "watched": "Pogledano",
 | 
			
		||||
        "videos": "Video zapisi",
 | 
			
		||||
        "live": "{0} Uživo",
 | 
			
		||||
        "shorts": "Kratki videi"
 | 
			
		||||
        "shorts": "Kratki videi",
 | 
			
		||||
        "category": "Kategorija",
 | 
			
		||||
        "all": "Sve"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Prikačeno od {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -170,6 +180,8 @@
 | 
			
		|||
        "cannot_copy": "Nije moguće kopirati!",
 | 
			
		||||
        "page_not_found": "Stranica nije pronađena",
 | 
			
		||||
        "copied": "Kopirano!",
 | 
			
		||||
        "local_storage": "Ova radnja zahtijeva lokalno pohranjivanje, jesu li kolačići omogućeni?"
 | 
			
		||||
        "local_storage": "Ova radnja zahtijeva lokalno pohranjivanje, jesu li kolačići omogućeni?",
 | 
			
		||||
        "register_no_email_note": "Korištenje e-maila kao korisničko ime se ne preporučuje. Svejedno nastaviti?",
 | 
			
		||||
        "next_video_countdown": "Reproduciranje sljedećeg videa u {0}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,8 @@
 | 
			
		|||
        "instance": "Instància",
 | 
			
		||||
        "player": "Reproductor",
 | 
			
		||||
        "livestreams": "Retransmissió en directe",
 | 
			
		||||
        "channels": "Canals"
 | 
			
		||||
        "channels": "Canals",
 | 
			
		||||
        "bookmarks": "Marcadors"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "channel_name_desc": "Nom del Canal (Z-A)",
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +43,7 @@
 | 
			
		|||
        "enabled_codecs": "Còdecs Habilitats (Múltiple)",
 | 
			
		||||
        "instances_list": "Llista d'Instàncies",
 | 
			
		||||
        "instance_selection": "Selecció d'Instàncies",
 | 
			
		||||
        "show_more": "Mostrar Més",
 | 
			
		||||
        "show_more": "Mostrar més",
 | 
			
		||||
        "yes": "Sí",
 | 
			
		||||
        "no": "No",
 | 
			
		||||
        "export_to_json": "Exportar a JSON",
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +103,6 @@
 | 
			
		|||
        "time_code": "Moment (en segons)",
 | 
			
		||||
        "copy_link": "Copiar l'enllaç",
 | 
			
		||||
        "follow_link": "Vés a l'enllaç",
 | 
			
		||||
        "rename_playlist": "Canviar el nom de la llista de reproducció",
 | 
			
		||||
        "new_playlist_name": "Nom nou de la llista de reproducció",
 | 
			
		||||
        "store_search_history": "Emmagatzema l'historial de cerca",
 | 
			
		||||
        "instance_donations": "Donacions a instàncies",
 | 
			
		||||
        "hide_watched": "Amaga els vídeos vistos de Continguts",
 | 
			
		||||
| 
						 | 
				
			
			@ -114,7 +113,17 @@
 | 
			
		|||
        "show_watch_on_youtube": "Mostra el botó \"Veure a Youtube\"",
 | 
			
		||||
        "reply_count": "{count} respostes",
 | 
			
		||||
        "minimize_comments_default": "Minimitzar els comentaris per defecte",
 | 
			
		||||
        "minimize_comments": "Minimitza els comentaris"
 | 
			
		||||
        "minimize_comments": "Minimitza els comentaris",
 | 
			
		||||
        "no_valid_playlists": "L'arxiu no conté llistes de reproducció vàlides!",
 | 
			
		||||
        "bookmark_playlist": "Marcador",
 | 
			
		||||
        "playlist_bookmarked": "Afegit a marcadors",
 | 
			
		||||
        "minimize_chapters_default": "Minimitzar capítols per defecte",
 | 
			
		||||
        "skip_button_only": "Mostra el botó de saltar",
 | 
			
		||||
        "skip_automatically": "Automàticament",
 | 
			
		||||
        "min_segment_length": "Longitud de segment mínima (en segons)",
 | 
			
		||||
        "skip_segment": "Saltar segment",
 | 
			
		||||
        "with_playlist": "Comparteix amb llista de reproducció",
 | 
			
		||||
        "show_less": "Mostrar menys"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Fixat per {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +148,9 @@
 | 
			
		|||
        "live": "{0} En Directe",
 | 
			
		||||
        "videos": "Vídeos",
 | 
			
		||||
        "views": "{views} visualitzacions",
 | 
			
		||||
        "shorts": "Curts"
 | 
			
		||||
        "shorts": "Curts",
 | 
			
		||||
        "all": "Tot",
 | 
			
		||||
        "category": "Categoria"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Volies dir: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -169,6 +180,8 @@
 | 
			
		|||
        "preferences_note": "Nota: les preferències es desen a l'emmagatzematge local del navegador. Si elimineu les dades del navegador, es restabliran.",
 | 
			
		||||
        "page_not_found": "No s'ha torbat la pàgina",
 | 
			
		||||
        "copied": "Copiat!",
 | 
			
		||||
        "cannot_copy": "No es pot copiar!"
 | 
			
		||||
        "cannot_copy": "No es pot copiar!",
 | 
			
		||||
        "local_storage": "Aquesta acció requereix emmagatzematge local, estan les cookies habilitades?",
 | 
			
		||||
        "register_no_email_note": "Utilitzar un correu elextrònic com a usuari no és recomanable. Continuar de totes maneres?"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,10 @@
 | 
			
		|||
        "instance": "Instance",
 | 
			
		||||
        "player": "Přehrávač",
 | 
			
		||||
        "livestreams": "Živé přenosy",
 | 
			
		||||
        "channels": "Kanály"
 | 
			
		||||
        "channels": "Kanály",
 | 
			
		||||
        "bookmarks": "Záložky",
 | 
			
		||||
        "channel_groups": "Skupiny kanálů",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "loop_this_video": "Přehrávat video ve smyčce",
 | 
			
		||||
| 
						 | 
				
			
			@ -35,15 +38,15 @@
 | 
			
		|||
        "audio_only": "Pouze zvuk",
 | 
			
		||||
        "default_quality": "Výchozí kvalita",
 | 
			
		||||
        "buffering_goal": "Ukládání do vyrovnávací paměti (v sekundách)",
 | 
			
		||||
        "country_selection": "Výběr země",
 | 
			
		||||
        "country_selection": "Země",
 | 
			
		||||
        "default_homepage": "Výchozí domovská stránka",
 | 
			
		||||
        "show_comments": "Zobrazit komentáře",
 | 
			
		||||
        "minimize_description_default": "Automaticky minimalizovat popis",
 | 
			
		||||
        "store_watch_history": "Ukládat historii sledování",
 | 
			
		||||
        "language_selection": "Výběr jazyka",
 | 
			
		||||
        "language_selection": "Jazyk",
 | 
			
		||||
        "instances_list": "Seznam instancí",
 | 
			
		||||
        "enabled_codecs": "Povolené kodeky (několik)",
 | 
			
		||||
        "instance_selection": "Výběr instance",
 | 
			
		||||
        "instance_selection": "Instance",
 | 
			
		||||
        "show_more": "Zobrazit více",
 | 
			
		||||
        "yes": "Ano",
 | 
			
		||||
        "no": "Ne",
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +62,7 @@
 | 
			
		|||
        "disable_lbry": "Zakázat LBRY pro streamování",
 | 
			
		||||
        "enable_lbry_proxy": "Povolit proxy pro LBRY",
 | 
			
		||||
        "view_ssl_score": "Zobrazit stav SSL",
 | 
			
		||||
        "search": "Vyhledat",
 | 
			
		||||
        "search": "Vyhledávání (Ctrl+K)",
 | 
			
		||||
        "filter": "Filtr",
 | 
			
		||||
        "loading": "Načítání...",
 | 
			
		||||
        "clear_history": "Smazat historii",
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +90,7 @@
 | 
			
		|||
        "minimize_recommendations_default": "Ve výchozím nastavení minimalizovat doporučení",
 | 
			
		||||
        "invalidate_session": "Odhlásit se ze všech zařízení",
 | 
			
		||||
        "different_auth_instance": "Použít jinou instanci pro autentizaci",
 | 
			
		||||
        "instance_auth_selection": "Výběr autentizační instance",
 | 
			
		||||
        "instance_auth_selection": "Autentizační instance",
 | 
			
		||||
        "clone_playlist": "Duplikovat playlist",
 | 
			
		||||
        "clone_playlist_success": "Úspěšně duplikováno!",
 | 
			
		||||
        "download_as_txt": "Stáhnout jako .txt",
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +105,6 @@
 | 
			
		|||
        "follow_link": "Otevřít odkaz",
 | 
			
		||||
        "copy_link": "Kopírovat odkaz",
 | 
			
		||||
        "time_code": "Časový kód (v sekundách)",
 | 
			
		||||
        "rename_playlist": "Přejmenovat playlist",
 | 
			
		||||
        "new_playlist_name": "Nový název playlistu",
 | 
			
		||||
        "show_chapters": "Kapitoly",
 | 
			
		||||
        "store_search_history": "Ukládat historii vyhledávání",
 | 
			
		||||
        "hide_watched": "Skrýt sledovaná videa v kanálu",
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +118,29 @@
 | 
			
		|||
        "show_watch_on_youtube": "Zobrazit tlačítko Sledovat na YouTube",
 | 
			
		||||
        "minimize_chapters_default": "Ve výchozím nastavení skrýt kapitoly",
 | 
			
		||||
        "no_valid_playlists": "Soubor neobsahuje platné playlisty!",
 | 
			
		||||
        "with_playlist": "Sdílet s playlistem"
 | 
			
		||||
        "with_playlist": "Sdílet s playlistem",
 | 
			
		||||
        "bookmark_playlist": "Záložka",
 | 
			
		||||
        "playlist_bookmarked": "Uloženo",
 | 
			
		||||
        "skip_automatically": "Automaticky",
 | 
			
		||||
        "skip_segment": "Přeskočit segment",
 | 
			
		||||
        "skip_button_only": "Zobrazit tlačítko přeskočení",
 | 
			
		||||
        "min_segment_length": "Minimální délka segmentu (v sekundách)",
 | 
			
		||||
        "show_less": "Zobrazit méně",
 | 
			
		||||
        "autoplay_next_countdown": "Výchozí odpočet do dalšího videa (v sekundách)",
 | 
			
		||||
        "dismiss": "Zavřít",
 | 
			
		||||
        "group_name": "Název skupiny",
 | 
			
		||||
        "create_group": "Vytvořit skupinu",
 | 
			
		||||
        "auto_display_captions": "Automatické zobrazení titulků",
 | 
			
		||||
        "playlist_name": "Název playlistu",
 | 
			
		||||
        "cancel": "Zrušit",
 | 
			
		||||
        "edit_playlist": "Upravit playlist",
 | 
			
		||||
        "playlist_description": "Popis playlistu",
 | 
			
		||||
        "okay": "Okay",
 | 
			
		||||
        "show_search_suggestions": "Zobrazit našeptávání ve vyhledávání",
 | 
			
		||||
        "chapters_layout_mobile": "Rozložení kapitol na mobilu",
 | 
			
		||||
        "enable_dearrow": "Povolit DeArrow",
 | 
			
		||||
        "delete_automatically": "Automaticky odstranit po",
 | 
			
		||||
        "generate_qrcode": "Vygenerovat QR kód"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Sledovat na {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +162,9 @@
 | 
			
		|||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Uživatelské jméno",
 | 
			
		||||
        "password": "Heslo"
 | 
			
		||||
        "password": "Heslo",
 | 
			
		||||
        "password_confirm": "Potvrzení hesla",
 | 
			
		||||
        "passwords_incorrect": "Hesla se neshodují!"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Videa",
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +174,13 @@
 | 
			
		|||
        "ratings_disabled": "Hodnocení zakázáno",
 | 
			
		||||
        "chapters": "Kapitoly",
 | 
			
		||||
        "live": "{0} Živě",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Vše",
 | 
			
		||||
        "category": "Kategorie",
 | 
			
		||||
        "chapters_horizontal": "Horizontální",
 | 
			
		||||
        "chapters_vertical": "Vertikální",
 | 
			
		||||
        "license": "Licence",
 | 
			
		||||
        "visibility": "Viditelnost"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Mysleli jste: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +191,8 @@
 | 
			
		|||
        "playlists": "YouTube: Playlisty",
 | 
			
		||||
        "music_videos": "YT Music: Videa",
 | 
			
		||||
        "music_albums": "YT Music: Alba",
 | 
			
		||||
        "music_playlists": "YT Music: Playlisty"
 | 
			
		||||
        "music_playlists": "YT Music: Playlisty",
 | 
			
		||||
        "music_artists": "YT Music: Umělci"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Přihlášeno k odběru: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +205,12 @@
 | 
			
		|||
        "page_not_found": "Stránka nenalezena",
 | 
			
		||||
        "copied": "Zkopírováno!",
 | 
			
		||||
        "cannot_copy": "Nelze zkopírovat!",
 | 
			
		||||
        "local_storage": "Tato akce vyžaduje localStorage, jsou povoleny cookies?"
 | 
			
		||||
        "local_storage": "Tato akce vyžaduje localStorage, jsou povoleny cookies?",
 | 
			
		||||
        "register_no_email_note": "Použití e-mailu jako uživatelského jména se nedoporučuje. Chcete přesto pokračovat?",
 | 
			
		||||
        "next_video_countdown": "Přehrávání dalšího videa za {0}s",
 | 
			
		||||
        "hours": "{amount} hodin",
 | 
			
		||||
        "days": "{amount} dnů",
 | 
			
		||||
        "weeks": "{amount} týdnů",
 | 
			
		||||
        "months": "{amount} měsíců"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,17 @@
 | 
			
		|||
{
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "skip_outro": "Abspann überspringen",
 | 
			
		||||
        "skip_non_music": "Musik überspringen: Nicht-Musik-Bereich",
 | 
			
		||||
        "skip_outro": "Endkarten und Abspann überspringen",
 | 
			
		||||
        "skip_non_music": "Musik: Nicht-Musik-Abschnitte überspringen",
 | 
			
		||||
        "skip_self_promo": "Unbezahlte Werbung/Eigenwerbung überspringen",
 | 
			
		||||
        "skip_interaction": "Interaktionserinnerung überspringen (Abonnieren)",
 | 
			
		||||
        "skip_preview": "Vorschau/Rückschau überspringen",
 | 
			
		||||
        "skip_interaction": "Interaktionserinnerungen überspringen (Daumen hoch, abonnieren, ...)",
 | 
			
		||||
        "skip_preview": "Vorschau und Rückblick überspringen",
 | 
			
		||||
        "instances_list": "Liste der Instanzen",
 | 
			
		||||
        "language_selection": "Sprachauswahl",
 | 
			
		||||
        "language_selection": "Sprache",
 | 
			
		||||
        "store_watch_history": "Wiedergabeverlauf speichern",
 | 
			
		||||
        "minimize_description_default": "Beschreibung standardmäßig minimieren",
 | 
			
		||||
        "show_comments": "Kommentare anzeigen",
 | 
			
		||||
        "default_homepage": "Standard-Startseite",
 | 
			
		||||
        "country_selection": "Länderauswahl",
 | 
			
		||||
        "default_homepage": "Startseite",
 | 
			
		||||
        "country_selection": "Land",
 | 
			
		||||
        "buffering_goal": "Pufferungsziel (in Sekunden)",
 | 
			
		||||
        "default_quality": "Standardqualität",
 | 
			
		||||
        "audio_only": "Nur Audio",
 | 
			
		||||
| 
						 | 
				
			
			@ -20,9 +20,9 @@
 | 
			
		|||
        "dark": "Dunkel",
 | 
			
		||||
        "auto": "Automatisch",
 | 
			
		||||
        "theme": "Farbschema",
 | 
			
		||||
        "skip_intro": "Pausen-/Intro-Animation überspringen",
 | 
			
		||||
        "skip_sponsors": "Sponsoren überspringen",
 | 
			
		||||
        "enable_sponsorblock": "Sponsorblock einschalten",
 | 
			
		||||
        "skip_intro": "Unterbrechungen und Intro-Animation überspringen",
 | 
			
		||||
        "skip_sponsors": "Gesponsorte Videoabschnitte überspringen",
 | 
			
		||||
        "enable_sponsorblock": "SponsorBlock verwenden",
 | 
			
		||||
        "uses_api_from": "Verwendet die API von ",
 | 
			
		||||
        "back": "Zurück",
 | 
			
		||||
        "channel_name_desc": "Kanalname (Z-A)",
 | 
			
		||||
| 
						 | 
				
			
			@ -30,18 +30,18 @@
 | 
			
		|||
        "least_recent": "Am wenigsten neu",
 | 
			
		||||
        "most_recent": "Am Neuesten",
 | 
			
		||||
        "sort_by": "Sortieren nach:",
 | 
			
		||||
        "view_subscriptions": "Abonnements anzeigen",
 | 
			
		||||
        "view_subscriptions": "Abos anzeigen",
 | 
			
		||||
        "unsubscribe": "Deabonnieren - {count}",
 | 
			
		||||
        "subscribe": "Abonnieren - {count}",
 | 
			
		||||
        "enabled_codecs": "Aktivierte Codecs (mehrere)",
 | 
			
		||||
        "enabled_codecs": "Aktivierte Codecs (Auswahl mehrerer Codecs möglich)",
 | 
			
		||||
        "enable_lbry_proxy": "Proxy für LBRY einschalten",
 | 
			
		||||
        "disable_lbry": "LBRY für Streaming deaktivieren",
 | 
			
		||||
        "instance_selection": "Instanzauswahl",
 | 
			
		||||
        "instance_selection": "Instanz",
 | 
			
		||||
        "show_description": "Beschreibung anzeigen",
 | 
			
		||||
        "minimize_description": "Beschreibung minimieren",
 | 
			
		||||
        "show_recommendations": "Empfehlungen anzeigen",
 | 
			
		||||
        "minimize_recommendations": "Empfehlungen minimieren",
 | 
			
		||||
        "donations": "Spenden für die Entwickler",
 | 
			
		||||
        "donations": "Spenden",
 | 
			
		||||
        "auto_play_next_video": "Nächstes Video automatisch abspielen",
 | 
			
		||||
        "loop_this_video": "Dieses Video wiederholen",
 | 
			
		||||
        "import_from_json": "Aus JSON/CSV importieren",
 | 
			
		||||
| 
						 | 
				
			
			@ -51,32 +51,30 @@
 | 
			
		|||
        "yes": "Ja",
 | 
			
		||||
        "loading": "Wird geladen…",
 | 
			
		||||
        "filter": "Filtern",
 | 
			
		||||
        "search": "Suchen",
 | 
			
		||||
        "search": "Suchen (Strg+K)",
 | 
			
		||||
        "view_ssl_score": "SSL-Bewertung anzeigen",
 | 
			
		||||
        "clear_history": "Verlauf löschen",
 | 
			
		||||
        "hide_replies": "Antworten ausblenden",
 | 
			
		||||
        "load_more_replies": "Mehr Antworten laden",
 | 
			
		||||
        "skip_highlight": "Höhepunkt überspringen",
 | 
			
		||||
        "skip_filler_tangent": "Lückenfüller überspringen",
 | 
			
		||||
        "delete_playlist_confirm": "Diese Wiedergabeliste löschen?",
 | 
			
		||||
        "remove_from_playlist": "Aus Wiedergabeliste entfernen",
 | 
			
		||||
        "add_to_playlist": "Zur Wiedergabeliste hinzufügen",
 | 
			
		||||
        "create_playlist": "Wiedergabeliste erstellen",
 | 
			
		||||
        "delete_playlist_video_confirm": "Video aus Wiedergabeliste entfernen?",
 | 
			
		||||
        "delete_playlist": "Wiedergabeliste löschen",
 | 
			
		||||
        "please_select_playlist": "Bitte wählen Sie eine Wiedergabeliste",
 | 
			
		||||
        "select_playlist": "Wählen Sie eine Wiedergabeliste",
 | 
			
		||||
        "delete_playlist_confirm": "Diese Playlist löschen?",
 | 
			
		||||
        "remove_from_playlist": "Aus Playlist entfernen",
 | 
			
		||||
        "add_to_playlist": "Zur Playlist hinzufügen",
 | 
			
		||||
        "create_playlist": "Playlist erstellen",
 | 
			
		||||
        "delete_playlist_video_confirm": "Video aus Playlist entfernen?",
 | 
			
		||||
        "delete_playlist": "Playlist löschen",
 | 
			
		||||
        "please_select_playlist": "Bitte wähle eine Playlist",
 | 
			
		||||
        "select_playlist": "Wähle eine Playlist",
 | 
			
		||||
        "show_markers": "Markierungen auf dem Player anzeigen",
 | 
			
		||||
        "delete_account": "Konto löschen",
 | 
			
		||||
        "logout": "Von diesem Gerät abmelden",
 | 
			
		||||
        "minimize_recommendations_default": "Empfehlungen standardmäßig minimieren",
 | 
			
		||||
        "invalidate_session": "Von allen Geräte abmelden",
 | 
			
		||||
        "invalidate_session": "Von allen Geräten abmelden",
 | 
			
		||||
        "different_auth_instance": "Eine andere Instanz für die Authentifizierung verwenden",
 | 
			
		||||
        "instance_auth_selection": "Auswahl der Autentifizierungsinstanz",
 | 
			
		||||
        "clone_playlist": "Wiedergabeliste klonen",
 | 
			
		||||
        "clone_playlist_success": "Erfolgreich geklont!",
 | 
			
		||||
        "rename_playlist": "Wiedergabeliste umbenennen",
 | 
			
		||||
        "new_playlist_name": "Neuer Name der Wiedergabeliste",
 | 
			
		||||
        "instance_auth_selection": "Authentifizierungsinstanz",
 | 
			
		||||
        "clone_playlist": "Playlist duplizieren",
 | 
			
		||||
        "clone_playlist_success": "Erfolgreich dupliziert!",
 | 
			
		||||
        "piped_link": "Piped-Link",
 | 
			
		||||
        "download_as_txt": "Als .txt herunterladen",
 | 
			
		||||
        "back_to_home": "Zurück zur Startseite",
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +82,7 @@
 | 
			
		|||
        "with_timecode": "Mit Zeitstempel teilen",
 | 
			
		||||
        "follow_link": "Link öffnen",
 | 
			
		||||
        "copy_link": "Link kopieren",
 | 
			
		||||
        "time_code": "Zeitstempel (in sekunden)",
 | 
			
		||||
        "time_code": "Zeitstempel (in Sekunden)",
 | 
			
		||||
        "reset_preferences": "Einstellungen zurücksetzen",
 | 
			
		||||
        "confirm_reset_preferences": "Bist du sicher, dass du deine Einstellungen zurücksetzen möchtest?",
 | 
			
		||||
        "backup_preferences": "Einstellungen sichern",
 | 
			
		||||
| 
						 | 
				
			
			@ -92,17 +90,39 @@
 | 
			
		|||
        "show_chapters": "Kapitel",
 | 
			
		||||
        "source_code": "Quellcode",
 | 
			
		||||
        "store_search_history": "Suchverlauf speichern",
 | 
			
		||||
        "hide_watched": "Gesehene Videos im Feed ausblenden",
 | 
			
		||||
        "hide_watched": "Gesehene Videos im Abo-Feed ausblenden",
 | 
			
		||||
        "reply_count": "{count} Antworten",
 | 
			
		||||
        "instance_donations": "Instanz-Spenden",
 | 
			
		||||
        "documentation": "Dokumentation",
 | 
			
		||||
        "status_page": "Status",
 | 
			
		||||
        "minimize_chapters_default": "Kapitel standardmäßig minimieren",
 | 
			
		||||
        "minimize_comments_default": "Kommentare automatisch minimieren",
 | 
			
		||||
        "minimize_comments_default": "Kommentare standardmäßig minimieren",
 | 
			
		||||
        "minimize_comments": "Kommentare minimieren",
 | 
			
		||||
        "no_valid_playlists": "Die Datei enthält keine gültigen Wiedergabelisten!",
 | 
			
		||||
        "no_valid_playlists": "Die Datei enthält keine gültigen Playlists!",
 | 
			
		||||
        "show_watch_on_youtube": "Schaltfläche „Auf YouTube ansehen“ anzeigen",
 | 
			
		||||
        "with_playlist": "Mit Wiedergabeliste teilen"
 | 
			
		||||
        "with_playlist": "Mit Playlist teilen",
 | 
			
		||||
        "playlist_bookmarked": "Markiert",
 | 
			
		||||
        "bookmark_playlist": "Lesezeichen",
 | 
			
		||||
        "skip_segment": "Abschnitt überspringen",
 | 
			
		||||
        "skip_automatically": "Automatisch",
 | 
			
		||||
        "min_segment_length": "Minimale Abschnittlänge (in Sekunden)",
 | 
			
		||||
        "skip_button_only": "Überspringen-Schaltfläche anzeigen",
 | 
			
		||||
        "show_less": "Weniger anzeigen",
 | 
			
		||||
        "autoplay_next_countdown": "Anzahl der Sekunden bis das nächste Video automatisch startet",
 | 
			
		||||
        "dismiss": "Ablehnen",
 | 
			
		||||
        "group_name": "Gruppenname",
 | 
			
		||||
        "create_group": "Gruppe erstellen",
 | 
			
		||||
        "auto_display_captions": "Untertitel automatisch anzeigen",
 | 
			
		||||
        "cancel": "Abbrechen",
 | 
			
		||||
        "okay": "Okay",
 | 
			
		||||
        "edit_playlist": "Playlist bearbeiten",
 | 
			
		||||
        "playlist_name": "Name der Playlist",
 | 
			
		||||
        "playlist_description": "Beschreibung der Playlist",
 | 
			
		||||
        "show_search_suggestions": "Suchvorschläge anzeigen",
 | 
			
		||||
        "chapters_layout_mobile": "Kapitel-Layout auf Mobilgeräten",
 | 
			
		||||
        "delete_automatically": "Automatisch löschen nach",
 | 
			
		||||
        "enable_dearrow": "DeArrow verwenden",
 | 
			
		||||
        "generate_qrcode": "QR-Code generieren"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Auf {0} ansehen"
 | 
			
		||||
| 
						 | 
				
			
			@ -110,27 +130,36 @@
 | 
			
		|||
    "titles": {
 | 
			
		||||
        "history": "Verlauf",
 | 
			
		||||
        "preferences": "Einstellungen",
 | 
			
		||||
        "feed": "Abonnements",
 | 
			
		||||
        "feed": "Abos",
 | 
			
		||||
        "register": "Registrieren",
 | 
			
		||||
        "login": "Anmelden",
 | 
			
		||||
        "trending": "Trends",
 | 
			
		||||
        "subscriptions": "Abonnements",
 | 
			
		||||
        "playlists": "Wiedergabelisten",
 | 
			
		||||
        "subscriptions": "Abos",
 | 
			
		||||
        "playlists": "Playlists",
 | 
			
		||||
        "account": "Konto",
 | 
			
		||||
        "player": "Player",
 | 
			
		||||
        "instance": "Instanz",
 | 
			
		||||
        "livestreams": "Livestreams",
 | 
			
		||||
        "channels": "Kanäle"
 | 
			
		||||
        "channels": "Kanäle",
 | 
			
		||||
        "bookmarks": "Lesezeichen",
 | 
			
		||||
        "channel_groups": "Kanalgruppen",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "sponsor_segments": "Sponsoren-Segmente",
 | 
			
		||||
        "sponsor_segments": "Sponsoren-Abschnitte",
 | 
			
		||||
        "watched": "Angesehen",
 | 
			
		||||
        "views": "{views} Aufrufe",
 | 
			
		||||
        "videos": "Videos",
 | 
			
		||||
        "ratings_disabled": "Bewertungen deaktiviert",
 | 
			
		||||
        "live": "{0} Live",
 | 
			
		||||
        "chapters": "Kapitel",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Alle",
 | 
			
		||||
        "category": "Kategorie",
 | 
			
		||||
        "chapters_horizontal": "Horizontal",
 | 
			
		||||
        "chapters_vertical": "Vertikal",
 | 
			
		||||
        "license": "Lizenz",
 | 
			
		||||
        "visibility": "Sichtbarkeit"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "SSL-Bewertung",
 | 
			
		||||
| 
						 | 
				
			
			@ -145,31 +174,40 @@
 | 
			
		|||
        "pinned_by": "Angeheftet von {author}",
 | 
			
		||||
        "user_disabled": "Kommentare wurden in den Einstellungen deaktiviert.",
 | 
			
		||||
        "disabled": "Kommentare wurden vom Autor deaktiviert.",
 | 
			
		||||
        "loading": "Kommentare werden geladen …"
 | 
			
		||||
        "loading": "Kommentare werden geladen…"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "password": "Passwort",
 | 
			
		||||
        "username": "Anmeldename"
 | 
			
		||||
        "username": "Benutzername",
 | 
			
		||||
        "password_confirm": "Passwort bestätigen",
 | 
			
		||||
        "passwords_incorrect": "Passwörter stimmen nicht überein!"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Hast du gemeint: {0}?",
 | 
			
		||||
        "all": "YouTube: Alle",
 | 
			
		||||
        "videos": "YouTube: Videos",
 | 
			
		||||
        "channels": "YouTube: Kanäle",
 | 
			
		||||
        "playlists": "YouTube: Wiedergabelisten",
 | 
			
		||||
        "playlists": "YouTube: Playlists",
 | 
			
		||||
        "music_songs": "YT Music: Lieder",
 | 
			
		||||
        "music_videos": "YT Music: Videos",
 | 
			
		||||
        "music_albums": "YT Music: Alben",
 | 
			
		||||
        "music_playlists": "YT Music: Wiedergabelisten"
 | 
			
		||||
        "music_playlists": "YT Music: Playlists",
 | 
			
		||||
        "music_artists": "YT Music: Künstler:innen"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Aboniert bei: {0}"
 | 
			
		||||
        "subscribed_channels_count": "Anzahl Abos: {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "Achtung: Einstellung werden lokal in deinem Browser gespeichert. Wenn du deine Browserdaten löschst werden sie auch gelöscht.",
 | 
			
		||||
        "preferences_note": "Achtung: Die Einstellungen werden lokal in deinem Browser gespeichert. Wenn du deine Browserdaten löschst, werden auch deine Einstellungen zurückgesetzt.",
 | 
			
		||||
        "page_not_found": "Seite nicht gefunden",
 | 
			
		||||
        "copied": "Kopiert!",
 | 
			
		||||
        "cannot_copy": "Kopieren nicht möglich!",
 | 
			
		||||
        "local_storage": "Diese Aktion erfordert „localStorage“, sind Cookies aktiviert?"
 | 
			
		||||
        "local_storage": "Diese Aktion erfordert „localStorage“, sind Cookies aktiviert?",
 | 
			
		||||
        "register_no_email_note": "Es wird nicht empfohlen, eine E-Mail als Benutzernamen zu verwenden. Trotzdem fortfahren?",
 | 
			
		||||
        "next_video_countdown": "Nächstes Video startet in {0}s",
 | 
			
		||||
        "weeks": "{amount} Woche(n)",
 | 
			
		||||
        "months": "{amount} Monat(en)",
 | 
			
		||||
        "hours": "{amount} Stunde(n)",
 | 
			
		||||
        "days": "{amount} Tag(e)"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,10 @@
 | 
			
		|||
        "instance": "Instance",
 | 
			
		||||
        "player": "Player",
 | 
			
		||||
        "livestreams": "Livestreams",
 | 
			
		||||
        "channels": "Channels"
 | 
			
		||||
        "channels": "Channels",
 | 
			
		||||
        "bookmarks": "Bookmarks",
 | 
			
		||||
        "channel_groups": "Channel groups",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Watch on {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +32,8 @@
 | 
			
		|||
        "back": "Back",
 | 
			
		||||
        "uses_api_from": "Uses the API from ",
 | 
			
		||||
        "enable_sponsorblock": "Enable Sponsorblock",
 | 
			
		||||
        "skip_button_only": "Show skip button",
 | 
			
		||||
        "skip_automatically": "Automatically",
 | 
			
		||||
        "skip_sponsors": "Skip Sponsors",
 | 
			
		||||
        "skip_intro": "Skip Intermission/Intro Animation",
 | 
			
		||||
        "skip_outro": "Skip Endcards/Credits",
 | 
			
		||||
| 
						 | 
				
			
			@ -39,23 +44,27 @@
 | 
			
		|||
        "skip_highlight": "Skip Highlight",
 | 
			
		||||
        "skip_filler_tangent": "Skip Filler Tangent",
 | 
			
		||||
        "show_markers": "Show Markers on Player",
 | 
			
		||||
        "min_segment_length": "Minimum Segment Length (in seconds)",
 | 
			
		||||
        "skip_segment": "Skip Segment",
 | 
			
		||||
        "enable_dearrow": "Enable DeArrow",
 | 
			
		||||
        "theme": "Theme",
 | 
			
		||||
        "auto": "Auto",
 | 
			
		||||
        "dark": "Dark",
 | 
			
		||||
        "light": "Light",
 | 
			
		||||
        "autoplay_video": "Autoplay Video",
 | 
			
		||||
        "autoplay_next_countdown": "Default Countdown until next video (in seconds)",
 | 
			
		||||
        "audio_only": "Audio Only",
 | 
			
		||||
        "default_quality": "Default Quality",
 | 
			
		||||
        "buffering_goal": "Buffering Goal (in seconds)",
 | 
			
		||||
        "country_selection": "Country Selection",
 | 
			
		||||
        "country_selection": "Country",
 | 
			
		||||
        "default_homepage": "Default Homepage",
 | 
			
		||||
        "minimize_comments_default": "Minimize Comments by default",
 | 
			
		||||
        "minimize_description_default": "Minimize Description by default",
 | 
			
		||||
        "store_watch_history": "Store Watch History",
 | 
			
		||||
        "language_selection": "Language Selection",
 | 
			
		||||
        "language_selection": "Language",
 | 
			
		||||
        "instances_list": "Instances List",
 | 
			
		||||
        "enabled_codecs": "Enabled Codecs (Multiple)",
 | 
			
		||||
        "instance_selection": "Instance Selection",
 | 
			
		||||
        "instance_selection": "Instance",
 | 
			
		||||
        "show_more": "Show More",
 | 
			
		||||
        "yes": "Yes",
 | 
			
		||||
        "no": "No",
 | 
			
		||||
| 
						 | 
				
			
			@ -63,6 +72,7 @@
 | 
			
		|||
        "import_from_json": "Import from JSON/CSV",
 | 
			
		||||
        "loop_this_video": "Loop this Video",
 | 
			
		||||
        "auto_play_next_video": "Auto Play next Video",
 | 
			
		||||
        "auto_display_captions": "Auto Display Captions",
 | 
			
		||||
        "donations": "Development donations",
 | 
			
		||||
        "minimize_comments": "Minimize Comments",
 | 
			
		||||
        "show_comments": "Comments",
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +83,7 @@
 | 
			
		|||
        "disable_lbry": "Disable LBRY for Streaming",
 | 
			
		||||
        "enable_lbry_proxy": "Enable Proxy for LBRY",
 | 
			
		||||
        "view_ssl_score": "View SSL Score",
 | 
			
		||||
        "search": "Search",
 | 
			
		||||
        "search": "Search (Ctrl+K)",
 | 
			
		||||
        "filter": "Filter",
 | 
			
		||||
        "loading": "Loading...",
 | 
			
		||||
        "clear_history": "Clear History",
 | 
			
		||||
| 
						 | 
				
			
			@ -91,10 +101,11 @@
 | 
			
		|||
        "logout": "Logout from this device",
 | 
			
		||||
        "minimize_recommendations_default": "Minimize Recommendations by default",
 | 
			
		||||
        "minimize_chapters_default": "Minimize Chapters by default",
 | 
			
		||||
        "chapters_layout_mobile": "Chapters Layout On Mobile",
 | 
			
		||||
        "show_watch_on_youtube": "Show Watch on YouTube button",
 | 
			
		||||
        "invalidate_session": "Logout all devices",
 | 
			
		||||
        "different_auth_instance": "Use a different instance for authentication",
 | 
			
		||||
        "instance_auth_selection": "Autentication Instance Selection",
 | 
			
		||||
        "instance_auth_selection": "Authentication Instance",
 | 
			
		||||
        "clone_playlist": "Clone Playlist",
 | 
			
		||||
        "clone_playlist_success": "Successfully cloned!",
 | 
			
		||||
        "download_as_txt": "Download as .txt",
 | 
			
		||||
| 
						 | 
				
			
			@ -103,8 +114,9 @@
 | 
			
		|||
        "backup_preferences": "Backup preferences",
 | 
			
		||||
        "restore_preferences": "Restore preferences",
 | 
			
		||||
        "back_to_home": "Back to home",
 | 
			
		||||
        "rename_playlist": "Rename",
 | 
			
		||||
        "new_playlist_name": "New playlist name",
 | 
			
		||||
        "edit_playlist": "Edit",
 | 
			
		||||
        "playlist_name": "Playlist name",
 | 
			
		||||
        "playlist_description": "Playlist description",
 | 
			
		||||
        "share": "Share",
 | 
			
		||||
        "with_timecode": "Share with time code",
 | 
			
		||||
        "piped_link": "Piped link",
 | 
			
		||||
| 
						 | 
				
			
			@ -112,7 +124,7 @@
 | 
			
		|||
        "copy_link": "Copy link",
 | 
			
		||||
        "time_code": "Time code (in seconds)",
 | 
			
		||||
        "show_chapters": "Chapters",
 | 
			
		||||
        "store_search_history": "Store Search history",
 | 
			
		||||
        "store_search_history": "Store Search History",
 | 
			
		||||
        "hide_watched": "Hide watched videos in the feed",
 | 
			
		||||
        "documentation": "Documentation",
 | 
			
		||||
        "status_page": "Status",
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +132,18 @@
 | 
			
		|||
        "instance_donations": "Instance donations",
 | 
			
		||||
        "reply_count": "{count} replies",
 | 
			
		||||
        "no_valid_playlists": "The file doesn't contain valid playlists!",
 | 
			
		||||
        "with_playlist": "Share with playlist"
 | 
			
		||||
        "with_playlist": "Share with playlist",
 | 
			
		||||
        "bookmark_playlist": "Bookmark",
 | 
			
		||||
        "playlist_bookmarked": "Bookmarked",
 | 
			
		||||
        "dismiss": "Dismiss",
 | 
			
		||||
        "show_less": "Show less",
 | 
			
		||||
        "create_group": "Create group",
 | 
			
		||||
        "group_name": "Group name",
 | 
			
		||||
        "cancel": "Cancel",
 | 
			
		||||
        "okay": "Okay",
 | 
			
		||||
        "show_search_suggestions": "Show search suggestions",
 | 
			
		||||
        "delete_automatically": "Delete automatically after",
 | 
			
		||||
        "generate_qrcode": "Generate QR Code"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Pinned by {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +162,9 @@
 | 
			
		|||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Username",
 | 
			
		||||
        "password": "Password"
 | 
			
		||||
        "password": "Password",
 | 
			
		||||
        "password_confirm": "Confirm password",
 | 
			
		||||
        "passwords_incorrect": "Passwords don't match!"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Videos",
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +174,13 @@
 | 
			
		|||
        "ratings_disabled": "Ratings Disabled",
 | 
			
		||||
        "chapters": "Chapters",
 | 
			
		||||
        "live": "{0} Live",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "All",
 | 
			
		||||
        "category": "Category",
 | 
			
		||||
        "license": "License",
 | 
			
		||||
        "visibility": "Visibility",
 | 
			
		||||
        "chapters_horizontal": "Horizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Did you mean: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +191,8 @@
 | 
			
		|||
        "music_songs": "YT Music: Songs",
 | 
			
		||||
        "music_videos": "YT Music: Videos",
 | 
			
		||||
        "music_albums": "YT Music: Albums",
 | 
			
		||||
        "music_playlists": "YT Music: Playlists"
 | 
			
		||||
        "music_playlists": "YT Music: Playlists",
 | 
			
		||||
        "music_artists": "YT Music: Artists"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Subscribed to: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -170,6 +202,12 @@
 | 
			
		|||
        "page_not_found": "Page not found",
 | 
			
		||||
        "copied": "Copied!",
 | 
			
		||||
        "cannot_copy": "Can't copy!",
 | 
			
		||||
        "local_storage": "This action requires localStorage, are cookies enabled?"
 | 
			
		||||
        "local_storage": "This action requires localStorage, are cookies enabled?",
 | 
			
		||||
        "register_no_email_note": "Using an e-mail as username is not recommended. Proceed anyways?",
 | 
			
		||||
        "next_video_countdown": "Playing next video in {0}s",
 | 
			
		||||
        "hours": "{amount} hour(s)",
 | 
			
		||||
        "days": "{amount} day(s)",
 | 
			
		||||
        "weeks": "{amount} week(s)",
 | 
			
		||||
        "months": "{amount} month(s)"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,10 @@
 | 
			
		|||
        "player": "Ludilo",
 | 
			
		||||
        "instance": "Nodo",
 | 
			
		||||
        "channels": "Kanaloj",
 | 
			
		||||
        "livestreams": "Tujelsendoj"
 | 
			
		||||
        "livestreams": "Tujelsendoj",
 | 
			
		||||
        "bookmarks": "Legosignoj",
 | 
			
		||||
        "channel_groups": "Kanalaroj",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Vidi en {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -38,22 +41,21 @@
 | 
			
		|||
        "light": "Hela",
 | 
			
		||||
        "autoplay_video": "Aŭtomate Ludi Videon",
 | 
			
		||||
        "audio_only": "Nur Sono",
 | 
			
		||||
        "default_quality": "Defaŭlta Kvalito",
 | 
			
		||||
        "country_selection": "Landa Elekto",
 | 
			
		||||
        "default_homepage": "Defaŭlta Ĉefpaĝo",
 | 
			
		||||
        "default_quality": "Implicita Kvalito",
 | 
			
		||||
        "country_selection": "Lando",
 | 
			
		||||
        "default_homepage": "Implicita Ĉefpaĝo",
 | 
			
		||||
        "show_comments": "Montri Komentojn",
 | 
			
		||||
        "language_selection": "Lingva Elekto",
 | 
			
		||||
        "language_selection": "Lingvo",
 | 
			
		||||
        "donations": "Donacoj por programado",
 | 
			
		||||
        "show_more": "Montri Pli",
 | 
			
		||||
        "show_more": "Montri pli",
 | 
			
		||||
        "yes": "Jes",
 | 
			
		||||
        "no": "Ne",
 | 
			
		||||
        "show_chapters": "Sekcioj",
 | 
			
		||||
        "filter": "Filtri",
 | 
			
		||||
        "search": "Serĉi",
 | 
			
		||||
        "search": "Serĉi (Ctrl+K)",
 | 
			
		||||
        "hide_replies": "Kaŝi Respondojn",
 | 
			
		||||
        "add_to_playlist": "Aldoni al ludlisto",
 | 
			
		||||
        "delete_playlist": "Forigi Ludliston",
 | 
			
		||||
        "rename_playlist": "Renomi ludliston",
 | 
			
		||||
        "download_as_txt": "Elŝuti kiel .txt",
 | 
			
		||||
        "piped_link": "Piped-ligilo",
 | 
			
		||||
        "copy_link": "Kopii ligilon",
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +68,6 @@
 | 
			
		|||
        "remove_from_playlist": "Forigi el ludlisto",
 | 
			
		||||
        "create_playlist": "Krei Ludliston",
 | 
			
		||||
        "delete_account": "Forigi Konton",
 | 
			
		||||
        "new_playlist_name": "Nomo de nova ludlisto",
 | 
			
		||||
        "reply_count": "{count} respondoj",
 | 
			
		||||
        "load_more_replies": "Ŝargi pli da Respondoj",
 | 
			
		||||
        "share": "Konigi",
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +84,7 @@
 | 
			
		|||
        "auto_play_next_video": "Aŭtomate Ludi sekvan Videon",
 | 
			
		||||
        "show_recommendations": "Montri Rekomendojn",
 | 
			
		||||
        "reset_preferences": "Restarigi agordojn",
 | 
			
		||||
        "instance_selection": "Noda Elekto",
 | 
			
		||||
        "instance_selection": "Nodo",
 | 
			
		||||
        "view_ssl_score": "Vidu SSL-Poentaron",
 | 
			
		||||
        "backup_preferences": "Savkopii agordojn",
 | 
			
		||||
        "disable_lbry": "Malebligi LBRY-n por Elsendfluo",
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +92,7 @@
 | 
			
		|||
        "store_search_history": "Konservi Ŝerĉhistorion",
 | 
			
		||||
        "hide_watched": "Kaŝi viditajn videojn en la fluo",
 | 
			
		||||
        "minimize_recommendations": "Plejetigi Rekomendojn",
 | 
			
		||||
        "instance_auth_selection": "Elekto de Aŭtentokontrola Nodo",
 | 
			
		||||
        "instance_auth_selection": "Aŭtentokontrola nodo",
 | 
			
		||||
        "restore_preferences": "Restarigi agordojn",
 | 
			
		||||
        "status_page": "Stato",
 | 
			
		||||
        "please_select_playlist": "Bonvolu elekti ludliston",
 | 
			
		||||
| 
						 | 
				
			
			@ -113,14 +114,36 @@
 | 
			
		|||
        "skip_interaction": "Preterpasi Interagan Memorigon (Aboni)",
 | 
			
		||||
        "store_watch_history": "Konservi Vidhistorion",
 | 
			
		||||
        "logout": "Elsaluti el ĉi tiu aparato",
 | 
			
		||||
        "minimize_description_default": "Defaŭlte Plejetigi Priskribon",
 | 
			
		||||
        "minimize_recommendations_default": "Defaŭlte Plejetigi Rekomendojn",
 | 
			
		||||
        "minimize_comments_default": "Defaŭlte Plejetigi Komentojn",
 | 
			
		||||
        "minimize_description_default": "Implicite Plejetigi Priskribon",
 | 
			
		||||
        "minimize_recommendations_default": "Implicite Plejetigi Rekomendojn",
 | 
			
		||||
        "minimize_comments_default": "Implicite Plejetigi Komentojn",
 | 
			
		||||
        "minimize_comments": "Plejetigi Komentojn",
 | 
			
		||||
        "show_watch_on_youtube": "Montri «Vidi en Youtube»-butonon",
 | 
			
		||||
        "minimize_chapters_default": "Defaŭlte plejetigi ĉapitrojn",
 | 
			
		||||
        "minimize_chapters_default": "Implicite plejetigi ĉapitrojn",
 | 
			
		||||
        "no_valid_playlists": "La dosiero ne enhavas validajn ludlistojn!",
 | 
			
		||||
        "with_playlist": "Konigi kun ludlisto"
 | 
			
		||||
        "with_playlist": "Konigi kun ludlisto",
 | 
			
		||||
        "playlist_bookmarked": "Legosignita",
 | 
			
		||||
        "bookmark_playlist": "Legosigno",
 | 
			
		||||
        "skip_automatically": "Aŭtomate",
 | 
			
		||||
        "skip_button_only": "Montri preterpasi-butonon",
 | 
			
		||||
        "min_segment_length": "Minimuma Segmenta Daŭro (en sekundoj)",
 | 
			
		||||
        "skip_segment": "Preterpasi Segmenton",
 | 
			
		||||
        "show_less": "Montri malpli",
 | 
			
		||||
        "dismiss": "Nuligi",
 | 
			
		||||
        "autoplay_next_countdown": "Implicita retronombrado ĝis sekva video (en sekundoj)",
 | 
			
		||||
        "group_name": "Nomo de la aro",
 | 
			
		||||
        "create_group": "Krei aron",
 | 
			
		||||
        "auto_display_captions": "Aŭtomate montri subtekstojn",
 | 
			
		||||
        "playlist_name": "Nomo de la ludlisto",
 | 
			
		||||
        "edit_playlist": "Redakti ludliston",
 | 
			
		||||
        "okay": "Bone",
 | 
			
		||||
        "playlist_description": "Priskribo de la ludlisto",
 | 
			
		||||
        "cancel": "Nuligi",
 | 
			
		||||
        "show_search_suggestions": "Montri serĉ-sugestojn",
 | 
			
		||||
        "chapters_layout_mobile": "Aranĝo de ĉapitroj en poŝtelefono",
 | 
			
		||||
        "delete_automatically": "Aŭtomate forigi post",
 | 
			
		||||
        "enable_dearrow": "Ebligi DeArrow",
 | 
			
		||||
        "generate_qrcode": "Generi QR-kodon"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "chapters": "Sekcioj",
 | 
			
		||||
| 
						 | 
				
			
			@ -128,9 +151,15 @@
 | 
			
		|||
        "live": "{0} Realtempe",
 | 
			
		||||
        "views": "{views} spektoj",
 | 
			
		||||
        "sponsor_segments": "Sponsoraj Segmentoj",
 | 
			
		||||
        "watched": "Viditaj",
 | 
			
		||||
        "watched": "Spektita",
 | 
			
		||||
        "ratings_disabled": "Taksadoj Malebligitaj",
 | 
			
		||||
        "shorts": "Mallongaj"
 | 
			
		||||
        "shorts": "Mallongaj",
 | 
			
		||||
        "all": "Ĉiuj",
 | 
			
		||||
        "category": "Kategorio",
 | 
			
		||||
        "chapters_horizontal": "Horizontala",
 | 
			
		||||
        "chapters_vertical": "Vertikala",
 | 
			
		||||
        "license": "Permesilo",
 | 
			
		||||
        "visibility": "Videbleco"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "music_albums": "YT Music: Albumoj",
 | 
			
		||||
| 
						 | 
				
			
			@ -141,18 +170,27 @@
 | 
			
		|||
        "music_videos": "YT Music: Videoj",
 | 
			
		||||
        "music_songs": "YT Music: Muzikaĵoj",
 | 
			
		||||
        "all": "YouTube: Ĉio",
 | 
			
		||||
        "did_you_mean": "Ĉu vi volis diri «{0}»?"
 | 
			
		||||
        "did_you_mean": "Ĉu vi volis diri «{0}»?",
 | 
			
		||||
        "music_artists": "YT Music: Artistoj"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "copied": "Kopiita!",
 | 
			
		||||
        "cannot_copy": "Ne povas kopii!",
 | 
			
		||||
        "preferences_note": "Noto: la agordoj estas konservitaj en la loka memoro de via retumilo. Forigi la datumojn de via retumilo restarigos ilin.",
 | 
			
		||||
        "page_not_found": "Paĝo ne trovita",
 | 
			
		||||
        "local_storage": "Ĉi tiu ago postulas localStorage, ĉu kuketoj estas ebligitaj?"
 | 
			
		||||
        "local_storage": "Ĉi tiu ago postulas localStorage, ĉu kuketoj estas ebligitaj?",
 | 
			
		||||
        "register_no_email_note": "Uzi retadreson kiel uzantnomon ne estas rekomendita. Ĉu daŭrigi ĉiuokaze?",
 | 
			
		||||
        "next_video_countdown": "Oni ludos la sekvan videon post {0}s",
 | 
			
		||||
        "hours": "{amount} horo(j)",
 | 
			
		||||
        "days": "{amount} tago(j)",
 | 
			
		||||
        "weeks": "{amount} semajno(j)",
 | 
			
		||||
        "months": "{amount} monato(j)"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Uzantnomo",
 | 
			
		||||
        "password": "Pasvorto"
 | 
			
		||||
        "password": "Pasvorto",
 | 
			
		||||
        "password_confirm": "Konfirmu la pasvorton",
 | 
			
		||||
        "passwords_incorrect": "Pasvortoj ne kongruas!"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "version": "Versio",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,13 @@
 | 
			
		|||
        "ratings_disabled": "Valoraciones Desactivadas",
 | 
			
		||||
        "chapters": "Capítulos",
 | 
			
		||||
        "live": "{0} Directo",
 | 
			
		||||
        "shorts": "Cortos"
 | 
			
		||||
        "shorts": "Cortos",
 | 
			
		||||
        "all": "Todos",
 | 
			
		||||
        "category": "Categoría",
 | 
			
		||||
        "chapters_horizontal": "Horizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical",
 | 
			
		||||
        "license": "Licencia",
 | 
			
		||||
        "visibility": "Visibilidad"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "Puntuación SSL",
 | 
			
		||||
| 
						 | 
				
			
			@ -37,15 +43,15 @@
 | 
			
		|||
        "no": "No",
 | 
			
		||||
        "yes": "Sí",
 | 
			
		||||
        "show_more": "Mostrar más",
 | 
			
		||||
        "instance_selection": "Selección de instancias",
 | 
			
		||||
        "instance_selection": "Instancia",
 | 
			
		||||
        "enabled_codecs": "Códecs habilitados (múltiples)",
 | 
			
		||||
        "instances_list": "Lista de instancias",
 | 
			
		||||
        "language_selection": "Selección de lenguajes",
 | 
			
		||||
        "language_selection": "Idioma",
 | 
			
		||||
        "store_watch_history": "Recordar historial de visualización",
 | 
			
		||||
        "minimize_description_default": "Minimizar la descripción por defecto",
 | 
			
		||||
        "show_comments": "Mostrar comentarios",
 | 
			
		||||
        "default_homepage": "Página de inicio predeterminada",
 | 
			
		||||
        "country_selection": "Selección de países",
 | 
			
		||||
        "country_selection": "País",
 | 
			
		||||
        "buffering_goal": "Objetivo de amortiguación (en segundos)",
 | 
			
		||||
        "default_quality": "Calidad predeterminada",
 | 
			
		||||
        "audio_only": "Sólo audio",
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +80,7 @@
 | 
			
		|||
        "subscribe": "Suscribirme - {count}",
 | 
			
		||||
        "loading": "Cargando…",
 | 
			
		||||
        "filter": "Filtrar",
 | 
			
		||||
        "search": "Buscar",
 | 
			
		||||
        "search": "Buscar (Ctrl+K)",
 | 
			
		||||
        "view_ssl_score": "Ver la puntuación SSL",
 | 
			
		||||
        "minimize_recommendations": "Minimizar recomendaciones",
 | 
			
		||||
        "show_recommendations": "Mostrar recomendaciones",
 | 
			
		||||
| 
						 | 
				
			
			@ -99,10 +105,8 @@
 | 
			
		|||
        "logout": "Cerrar sesión en este dispositivo",
 | 
			
		||||
        "minimize_recommendations_default": "Minimizar Recomendaciones por defecto",
 | 
			
		||||
        "invalidate_session": "Cerrar sesión en todos los dispositivos",
 | 
			
		||||
        "instance_auth_selection": "Selección de la Instancia de Autentificación",
 | 
			
		||||
        "instance_auth_selection": "Instancia de autenticación",
 | 
			
		||||
        "download_as_txt": "Descargar como .txt",
 | 
			
		||||
        "rename_playlist": "Cambiar el nombre de la lista de reproducción",
 | 
			
		||||
        "new_playlist_name": "Nuevo nombre de la lista de reproducción",
 | 
			
		||||
        "share": "Compartir",
 | 
			
		||||
        "with_timecode": "Compartir con código de tiempo",
 | 
			
		||||
        "piped_link": "Enlace de Piped",
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +119,7 @@
 | 
			
		|||
        "restore_preferences": "Restablecer las preferencias",
 | 
			
		||||
        "back_to_home": "Volver a la página de inicio",
 | 
			
		||||
        "show_chapters": "Capítulos",
 | 
			
		||||
        "store_search_history": "Guardar historial de búsqueda",
 | 
			
		||||
        "store_search_history": "Guardar el historial de las búsquedas",
 | 
			
		||||
        "source_code": "Código fuente",
 | 
			
		||||
        "documentation": "Documentación",
 | 
			
		||||
        "instance_donations": "Donaciones para instancia",
 | 
			
		||||
| 
						 | 
				
			
			@ -127,10 +131,32 @@
 | 
			
		|||
        "show_watch_on_youtube": "Mostrar botón Ver en YouTube",
 | 
			
		||||
        "minimize_chapters_default": "Minimiza capítulos por defecto",
 | 
			
		||||
        "no_valid_playlists": "¡El archivo no contiene listas de reproducción válidas!",
 | 
			
		||||
        "with_playlist": "Compartir con lista de reproducción"
 | 
			
		||||
        "with_playlist": "Compartir con lista de reproducción",
 | 
			
		||||
        "playlist_bookmarked": "Marcado",
 | 
			
		||||
        "bookmark_playlist": "Marcador",
 | 
			
		||||
        "skip_button_only": "Muestra botón de saltar",
 | 
			
		||||
        "skip_automatically": "Automáticamente",
 | 
			
		||||
        "min_segment_length": "Mínima Duración de Segmento (en segundos)",
 | 
			
		||||
        "skip_segment": "Saltar Segmento",
 | 
			
		||||
        "show_less": "Mostrar menos",
 | 
			
		||||
        "autoplay_next_countdown": "Cuenta atrás predeterminada antes del siguiente vídeo (en segundos)",
 | 
			
		||||
        "dismiss": "Cancelar",
 | 
			
		||||
        "group_name": "Nombre del grupo",
 | 
			
		||||
        "create_group": "Crear grupo",
 | 
			
		||||
        "auto_display_captions": "Mostrar automáticamente subtítulos",
 | 
			
		||||
        "edit_playlist": "Editar lista de reproducción",
 | 
			
		||||
        "okay": "Vale",
 | 
			
		||||
        "playlist_name": "Nombre de la lista de reproducción",
 | 
			
		||||
        "playlist_description": "Descripción de la lista de reproducción",
 | 
			
		||||
        "cancel": "Cancelar",
 | 
			
		||||
        "show_search_suggestions": "Mostrar sugerencias de búsqueda",
 | 
			
		||||
        "chapters_layout_mobile": "Disposición de capítulos en móvil",
 | 
			
		||||
        "delete_automatically": "Borrar automáticamente después de",
 | 
			
		||||
        "enable_dearrow": "Activar DeArrow",
 | 
			
		||||
        "generate_qrcode": "Generar código QR"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "feed": "Fuente web",
 | 
			
		||||
        "feed": "Contenido",
 | 
			
		||||
        "subscriptions": "Suscripciones",
 | 
			
		||||
        "history": "Historial",
 | 
			
		||||
        "trending": "En tendencias",
 | 
			
		||||
| 
						 | 
				
			
			@ -142,14 +168,19 @@
 | 
			
		|||
        "instance": "Instancia",
 | 
			
		||||
        "player": "Reproductor",
 | 
			
		||||
        "livestreams": "Directos",
 | 
			
		||||
        "channels": "Canales"
 | 
			
		||||
        "channels": "Canales",
 | 
			
		||||
        "bookmarks": "Marcadores",
 | 
			
		||||
        "channel_groups": "Grupos de canales",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Ver en {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "password": "Contraseña",
 | 
			
		||||
        "username": "Nombre de usuario"
 | 
			
		||||
        "username": "Nombre de usuario",
 | 
			
		||||
        "passwords_incorrect": "¡Las contraseñas no coinciden!",
 | 
			
		||||
        "password_confirm": "Confirma la contraseña"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "¿Quisiste decir {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +191,8 @@
 | 
			
		|||
        "videos": "YouTube: Vídeos",
 | 
			
		||||
        "channels": "YouTube: Canales",
 | 
			
		||||
        "playlists": "YouTube: Listas de reproducción",
 | 
			
		||||
        "music_albums": "YT Music: Álbumes"
 | 
			
		||||
        "music_albums": "YT Music: Álbumes",
 | 
			
		||||
        "music_artists": "YT Music: Artistas"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Suscrito a: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -170,6 +202,12 @@
 | 
			
		|||
        "page_not_found": "Página no encontrada",
 | 
			
		||||
        "copied": "¡Copiado!",
 | 
			
		||||
        "cannot_copy": "¡No se puede copiar!",
 | 
			
		||||
        "local_storage": "Esta acción requiere «localStorage», ¿están activadas las «cookies»?"
 | 
			
		||||
        "local_storage": "Esta acción requiere «localStorage», ¿están activadas las «cookies»?",
 | 
			
		||||
        "register_no_email_note": "No se recomienda usar un correo electrónico como nombre de usuario. ¿Continuar de todos modos?",
 | 
			
		||||
        "next_video_countdown": "El próximo vídeo se reproducirá en {0}s",
 | 
			
		||||
        "hours": "{amount} hora(s)",
 | 
			
		||||
        "days": "{amount} día(s)",
 | 
			
		||||
        "weeks": "{amount} semana(s)",
 | 
			
		||||
        "months": "{amount} mes(es)"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,7 +45,7 @@
 | 
			
		|||
        "import_from_json": "وارد کردن از JSON/CSV",
 | 
			
		||||
        "export_to_json": "دادن خروجی با فرمت JSON",
 | 
			
		||||
        "show_description": "نمایش توضیحات ویدئو",
 | 
			
		||||
        "donations": "کمک های مالی",
 | 
			
		||||
        "donations": "کمک های مالی برای توسعه",
 | 
			
		||||
        "clear_history": "تخلیه تاریخچه",
 | 
			
		||||
        "loop_this_video": "تکرار شدن این ویدئو",
 | 
			
		||||
        "auto_play_next_video": "پخش خودکار ویدئو بعدی",
 | 
			
		||||
| 
						 | 
				
			
			@ -57,16 +57,43 @@
 | 
			
		|||
        "hide_replies": "پنهان کردن پاسخ ها",
 | 
			
		||||
        "load_more_replies": "بارگذاری پاسخ های بیشتر",
 | 
			
		||||
        "disable_lbry": "غیرفعال کردن LBRY برای استریم ها",
 | 
			
		||||
        "enable_lbry_proxy": "فعال کردن پراکسی برای LBRY"
 | 
			
		||||
        "enable_lbry_proxy": "فعال کردن پراکسی برای LBRY",
 | 
			
		||||
        "delete_playlist": "حذف لیست پخش",
 | 
			
		||||
        "show_markers": "نشانگر را روی پخش کننده نشان دهد",
 | 
			
		||||
        "select_playlist": "انتخاب لیست پخش",
 | 
			
		||||
        "add_to_playlist": "افزودن به لیست پخش",
 | 
			
		||||
        "create_playlist": "ایجاد لیست پخش",
 | 
			
		||||
        "delete_playlist_confirm": "این لیست پخش حذف شود؟",
 | 
			
		||||
        "delete_account": "حذف اکانت",
 | 
			
		||||
        "logout": "خروج از این دستگاه",
 | 
			
		||||
        "delete_playlist_video_confirm": "ویدیو از لیست پخش حذف شود؟",
 | 
			
		||||
        "autoplay_next_countdown": "پیشفرض شمارشمعکوس پخش ویدیوی بعدی ( به ثانیه )",
 | 
			
		||||
        "please_select_playlist": "انتخاب لیست پخش",
 | 
			
		||||
        "minimize_comments_default": "کوچک کردن کامنتها به صورت پیش فرض",
 | 
			
		||||
        "minimize_comments": "کوچک کردن کامنت (نظرات)",
 | 
			
		||||
        "remove_from_playlist": "حذف از لیست پخش",
 | 
			
		||||
        "skip_button_only": "دکمهی گذر را نشان بده",
 | 
			
		||||
        "skip_automatically": "خودکار",
 | 
			
		||||
        "skip_segment": "گذر از بخش",
 | 
			
		||||
        "minimize_recommendations_default": "به طور پیش فرض پیشنهادها را حداقل کنید.",
 | 
			
		||||
        "min_segment_length": "حداقل طول بخش (به ثانیه)"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "history": "سابقه",
 | 
			
		||||
        "history": "تاریخچه",
 | 
			
		||||
        "preferences": "تنظیمات",
 | 
			
		||||
        "feed": "خوراک",
 | 
			
		||||
        "register": "ثبت نام",
 | 
			
		||||
        "login": "ورود",
 | 
			
		||||
        "trending": "محبوب",
 | 
			
		||||
        "subscriptions": "اشتراکها"
 | 
			
		||||
        "subscriptions": "اشتراکها",
 | 
			
		||||
        "account": "حساب کاربری",
 | 
			
		||||
        "instance": "نمونه",
 | 
			
		||||
        "playlists": "لیست پخش",
 | 
			
		||||
        "player": "پخش کننده",
 | 
			
		||||
        "bookmarks": "نشاندارها",
 | 
			
		||||
        "livestreams": "پخش زنده",
 | 
			
		||||
        "channels": "کانالها",
 | 
			
		||||
        "channel_groups": "گروه های کانال"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "تماشا روی {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +113,9 @@
 | 
			
		|||
        "instance_locations": "محل های سرویس",
 | 
			
		||||
        "has_cdn": "CDN دارد؟",
 | 
			
		||||
        "ssl_score": "امتیاز SSL",
 | 
			
		||||
        "instance_name": "نام سرویس"
 | 
			
		||||
        "instance_name": "نام سرویس",
 | 
			
		||||
        "version": "نسخه",
 | 
			
		||||
        "registered_users": "کاربران تایید شده"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "سنجاق شده توسط {author}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -101,7 +101,6 @@
 | 
			
		|||
        "show_watch_on_youtube": "Näytä Katso YouTubessa -painike",
 | 
			
		||||
        "different_auth_instance": "Käytä eri instanssia todennukseen",
 | 
			
		||||
        "download_as_txt": "Lataa .txt-tiedostona",
 | 
			
		||||
        "rename_playlist": "Nimeä soittolista uudelleen",
 | 
			
		||||
        "show_chapters": "Luvut",
 | 
			
		||||
        "minimize_comments": "Minimoi kommentit",
 | 
			
		||||
        "minimize_comments_default": "Minimoi kommentit oletusarvoisesti",
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +117,6 @@
 | 
			
		|||
        "hide_watched": "Piilota katsotut videot syötteessä",
 | 
			
		||||
        "time_code": "Aikakoodi (sekunteina)",
 | 
			
		||||
        "follow_link": "Avaa linkki",
 | 
			
		||||
        "new_playlist_name": "Soittolistan uusi nimi",
 | 
			
		||||
        "invalidate_session": "Kirjaudu ulos kaikista laitteista",
 | 
			
		||||
        "logout": "Kirjaudu ulos tästä laitteesta",
 | 
			
		||||
        "backup_preferences": "Varmuuskopiointiasetukset",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,9 @@
 | 
			
		|||
        "instance": "Instance",
 | 
			
		||||
        "player": "Lecteur",
 | 
			
		||||
        "livestreams": "Diffusions en direct",
 | 
			
		||||
        "channels": "Chaînes"
 | 
			
		||||
        "channels": "Chaînes",
 | 
			
		||||
        "bookmarks": "Marque-pages",
 | 
			
		||||
        "channel_groups": "Groupes de chaînes"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "subscribe": "S'abonner - {count}",
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +68,7 @@
 | 
			
		|||
        "yes": "Oui",
 | 
			
		||||
        "loading": "Chargement…",
 | 
			
		||||
        "filter": "Filtrer",
 | 
			
		||||
        "search": "Rechercher",
 | 
			
		||||
        "search": "Rechercher (Ctrl+K)",
 | 
			
		||||
        "view_ssl_score": "Afficher le score SSL",
 | 
			
		||||
        "clear_history": "Effacer l'historique",
 | 
			
		||||
        "load_more_replies": "Charger plus de réponses",
 | 
			
		||||
| 
						 | 
				
			
			@ -102,8 +104,6 @@
 | 
			
		|||
        "piped_link": "Lien vers Piped",
 | 
			
		||||
        "follow_link": "Ouvrir le lien",
 | 
			
		||||
        "time_code": "Horodatage (en secondes)",
 | 
			
		||||
        "rename_playlist": "Renommer la liste de lecture",
 | 
			
		||||
        "new_playlist_name": "Nouveau nom de la liste de lecture",
 | 
			
		||||
        "show_chapters": "Chapitres",
 | 
			
		||||
        "store_search_history": "Mémoriser l'historique de recherche",
 | 
			
		||||
        "documentation": "Documentation",
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +116,27 @@
 | 
			
		|||
        "minimize_comments": "Minimiser les commentaires",
 | 
			
		||||
        "show_watch_on_youtube": "Afficher le bouton Regarder sur YouTube",
 | 
			
		||||
        "minimize_chapters_default": "Minimiser les chapitres par défaut",
 | 
			
		||||
        "no_valid_playlists": "Le fichier ne contient pas de listes de lecture valides !"
 | 
			
		||||
        "no_valid_playlists": "Le fichier ne contient pas de listes de lecture valides !",
 | 
			
		||||
        "bookmark_playlist": "Marque-page",
 | 
			
		||||
        "playlist_bookmarked": "Dans les marque-pages",
 | 
			
		||||
        "with_playlist": "Partager avec la liste de lecture",
 | 
			
		||||
        "skip_button_only": "Afficher le bouton de saut",
 | 
			
		||||
        "skip_automatically": "Automatiquement",
 | 
			
		||||
        "min_segment_length": "Longueur minimale du segment (en secondes)",
 | 
			
		||||
        "skip_segment": "Sauter le segment",
 | 
			
		||||
        "show_less": "Afficher moins",
 | 
			
		||||
        "okay": "OK",
 | 
			
		||||
        "edit_playlist": "Éditer la liste de lecture",
 | 
			
		||||
        "playlist_name": "Nom de la liste de lecture",
 | 
			
		||||
        "auto_display_captions": "Afficher sous-titres automatiquement",
 | 
			
		||||
        "dismiss": "Rejeter",
 | 
			
		||||
        "cancel": "Annuler",
 | 
			
		||||
        "playlist_description": "Description de la liste de lecture",
 | 
			
		||||
        "create_group": "Créer un groupe",
 | 
			
		||||
        "group_name": "Nom du groupe",
 | 
			
		||||
        "autoplay_next_countdown": "Temps par défaut avant la prochaine vidéo (en secondes)",
 | 
			
		||||
        "chapters_layout_mobile": "Format des chapitres sur mobile",
 | 
			
		||||
        "show_search_suggestions": "Afficher les suggestions de recherche"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Regarder sur {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +149,11 @@
 | 
			
		|||
        "ratings_disabled": "Évaluations désactivées",
 | 
			
		||||
        "chapters": "Chapitres",
 | 
			
		||||
        "live": "{0} en direct",
 | 
			
		||||
        "shorts": "Courtes"
 | 
			
		||||
        "shorts": "Courtes",
 | 
			
		||||
        "all": "Tout",
 | 
			
		||||
        "category": "Catégorie",
 | 
			
		||||
        "chapters_horizontal": "Horizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "Score SSL",
 | 
			
		||||
| 
						 | 
				
			
			@ -172,6 +196,8 @@
 | 
			
		|||
        "page_not_found": "Page non trouvée",
 | 
			
		||||
        "copied": "Copié !",
 | 
			
		||||
        "cannot_copy": "Impossible de copier !",
 | 
			
		||||
        "local_storage": "Cette action nécessite localStorage, les cookies sont-ils activés ?"
 | 
			
		||||
        "local_storage": "Cette action nécessite localStorage, les cookies sont-ils activés ?",
 | 
			
		||||
        "register_no_email_note": "Il n'est pas recommandé d'utiliser une adresse courriel omme nom d'utilisateur. Continuer quand même ?",
 | 
			
		||||
        "next_video_countdown": "Lecture de la prochaine vidéo dans {0}s"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,10 @@
 | 
			
		|||
        "playlists": "רשימות נגינה",
 | 
			
		||||
        "instance": "עותק",
 | 
			
		||||
        "livestreams": "שידורים חיים",
 | 
			
		||||
        "channels": "ערוצים"
 | 
			
		||||
        "channels": "ערוצים",
 | 
			
		||||
        "bookmarks": "סימניות",
 | 
			
		||||
        "channel_groups": "קבוצות ערוץ",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "לצפות ב־{0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -43,16 +46,16 @@
 | 
			
		|||
        "audio_only": "שמע בלבד",
 | 
			
		||||
        "default_quality": "איכות ברירת מחדל",
 | 
			
		||||
        "buffering_goal": "יעד שמירה למטמון (בשניות)",
 | 
			
		||||
        "country_selection": "בחירת מדינה",
 | 
			
		||||
        "country_selection": "מדינה",
 | 
			
		||||
        "default_homepage": "עמוד הבית כברירת מחדל",
 | 
			
		||||
        "show_comments": "הצגת תגובות",
 | 
			
		||||
        "minimize_description_default": "מזעור התגובות כברירת מחדל",
 | 
			
		||||
        "store_watch_history": "שחזור היסטוריית הצפייה",
 | 
			
		||||
        "language_selection": "בחירת שפה",
 | 
			
		||||
        "language_selection": "שפה",
 | 
			
		||||
        "instances_list": "רשימת עותקים",
 | 
			
		||||
        "enabled_codecs": "מפענחים פעילים (מגוון)",
 | 
			
		||||
        "instance_selection": "בחירת עותק",
 | 
			
		||||
        "show_more": "להציג עוד",
 | 
			
		||||
        "instance_selection": "עותק",
 | 
			
		||||
        "show_more": "להציג יותר",
 | 
			
		||||
        "yes": "כן",
 | 
			
		||||
        "no": "לא",
 | 
			
		||||
        "export_to_json": "ייצוא ל־JSON",
 | 
			
		||||
| 
						 | 
				
			
			@ -77,7 +80,7 @@
 | 
			
		|||
        "logout": "יציאה מהחשבון במכשיר הזה",
 | 
			
		||||
        "minimize_recommendations_default": "מזעור המלצות כברירת מחדל",
 | 
			
		||||
        "invalidate_session": "להוציא את כל המכשירים מהחשבון",
 | 
			
		||||
        "instance_auth_selection": "בחירת עותק אימות",
 | 
			
		||||
        "instance_auth_selection": "עותק אימות",
 | 
			
		||||
        "clone_playlist": "שכפול רשימת נגינה",
 | 
			
		||||
        "clone_playlist_success": "שוכפל בהצלחה!",
 | 
			
		||||
        "download_as_txt": "הורדה כ־.txt",
 | 
			
		||||
| 
						 | 
				
			
			@ -99,11 +102,9 @@
 | 
			
		|||
        "disable_lbry": "השבתת הזרמה עם LBRY",
 | 
			
		||||
        "enable_lbry_proxy": "הפעלת מתווך ל־LBRY",
 | 
			
		||||
        "view_ssl_score": "הצגת דירוג SSL",
 | 
			
		||||
        "search": "חיפוש",
 | 
			
		||||
        "search": "חיפוש (Ctrl+K)",
 | 
			
		||||
        "loop_this_video": "ניגון הסרטון בלולאה",
 | 
			
		||||
        "minimize_recommendations": "מזעור המלצות",
 | 
			
		||||
        "rename_playlist": "שינוי שם רשימת נגינה",
 | 
			
		||||
        "new_playlist_name": "שם לרשימת נגינה חדשה",
 | 
			
		||||
        "show_chapters": "פרקים",
 | 
			
		||||
        "skip_intro": "דילוג על הפוגה/הנפשת הקדמה",
 | 
			
		||||
        "skip_outro": "דילוג על כרטיסי סיום/קרדיטים",
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +121,29 @@
 | 
			
		|||
        "minimize_chapters_default": "מזעור הפרקים כברירת מחדל",
 | 
			
		||||
        "show_watch_on_youtube": "הצגת כפתור לצפייה ב־YouTube",
 | 
			
		||||
        "no_valid_playlists": "הקובץ לא מכיל רשימות נגינה תקפות!",
 | 
			
		||||
        "with_playlist": "שיתוף עם רשימת נגינה"
 | 
			
		||||
        "with_playlist": "שיתוף עם רשימת נגינה",
 | 
			
		||||
        "playlist_bookmarked": "נוסף לסימניות",
 | 
			
		||||
        "bookmark_playlist": "סימנייה",
 | 
			
		||||
        "skip_button_only": "הצגת כפתור דילוג",
 | 
			
		||||
        "min_segment_length": "אורך מקטע מזערי (בשניות)",
 | 
			
		||||
        "skip_segment": "דילוג על מקטע",
 | 
			
		||||
        "skip_automatically": "אוטומטית",
 | 
			
		||||
        "show_less": "להציג פחות",
 | 
			
		||||
        "autoplay_next_countdown": "ספירה לאחור כברירת מחדל עד לסרטון הבא (בשניות)",
 | 
			
		||||
        "dismiss": "התעלמות",
 | 
			
		||||
        "create_group": "יצירת קבוצה",
 | 
			
		||||
        "group_name": "שם הקבוצה",
 | 
			
		||||
        "auto_display_captions": "הצגת כתוביות אוטומטית",
 | 
			
		||||
        "cancel": "ביטול",
 | 
			
		||||
        "edit_playlist": "עריכת רשימת נגינה",
 | 
			
		||||
        "playlist_name": "שם רשימת הנגינה",
 | 
			
		||||
        "playlist_description": "תיאור רשימת הנגינה",
 | 
			
		||||
        "okay": "אישור",
 | 
			
		||||
        "show_search_suggestions": "הצגת הצעות חיפוש",
 | 
			
		||||
        "chapters_layout_mobile": "פריסת פרקים בנייד",
 | 
			
		||||
        "delete_automatically": "למחוק אוטומטית לאחר",
 | 
			
		||||
        "enable_dearrow": "הפעלת DeArrow",
 | 
			
		||||
        "generate_qrcode": "יצירת קוד QR"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "ננעץ על ידי {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +172,13 @@
 | 
			
		|||
        "ratings_disabled": "הדירוגים מושבתים",
 | 
			
		||||
        "chapters": "פרקים",
 | 
			
		||||
        "live": "{0} בשידור חי",
 | 
			
		||||
        "shorts": "קצרצרים"
 | 
			
		||||
        "shorts": "קצרצרים",
 | 
			
		||||
        "all": "הכול",
 | 
			
		||||
        "category": "קטגוריה",
 | 
			
		||||
        "chapters_horizontal": "אופקית",
 | 
			
		||||
        "chapters_vertical": "אנכית",
 | 
			
		||||
        "visibility": "חשיפה",
 | 
			
		||||
        "license": "רישיון"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "האם התכוונת לביטוי {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,14 +189,21 @@
 | 
			
		|||
        "music_songs": "YT Music: שירים",
 | 
			
		||||
        "music_videos": "YT Music: סרטונים",
 | 
			
		||||
        "music_albums": "YT Music: אלבומים",
 | 
			
		||||
        "music_playlists": "YT Music: רשימות נגינה"
 | 
			
		||||
        "music_playlists": "YT Music: רשימות נגינה",
 | 
			
		||||
        "music_artists": "YT Music: אומנים"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "לתשומת לבך: ההעדפות נשמרות באחסון המקומי של הדפדפן שלך. מחיקת נתוני הדפדפן שלך תאפס אותם.",
 | 
			
		||||
        "page_not_found": "העמוד לא נמצא",
 | 
			
		||||
        "copied": "הועתק!",
 | 
			
		||||
        "cannot_copy": "לא ניתן להעתיק!",
 | 
			
		||||
        "local_storage": "פעולה זו דורשת אחסון מקומי (localStorage), האם עוגיות פעילות?"
 | 
			
		||||
        "local_storage": "פעולה זו דורשת אחסון מקומי (localStorage), האם עוגיות פעילות?",
 | 
			
		||||
        "register_no_email_note": "לא מומלץ להשתמש בכתובת דוא״ל כשם משתמש. להמשיך בכל זאת?",
 | 
			
		||||
        "next_video_countdown": "הסרטון הבא יתנגן בעוד {0} שניות",
 | 
			
		||||
        "days": "{amount} ימים",
 | 
			
		||||
        "weeks": "{amount} שבועות",
 | 
			
		||||
        "months": "{amount} חודשים",
 | 
			
		||||
        "hours": "{amount} שעות"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "נרשמת אל: {0}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,10 @@
 | 
			
		|||
        "feed": "फ़ीड",
 | 
			
		||||
        "playlists": "प्लेलिस्ट",
 | 
			
		||||
        "livestreams": "लाइव स्ट्रीम",
 | 
			
		||||
        "channels": "चैनल"
 | 
			
		||||
        "channels": "चैनल",
 | 
			
		||||
        "player": "चालक",
 | 
			
		||||
        "account": "खाता",
 | 
			
		||||
        "instance": "इंस्टैंस"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "subscribe": "सदस्यता लें - {count}",
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +20,7 @@
 | 
			
		|||
        "unsubscribe": "सदस्यता ले ली है - {count}",
 | 
			
		||||
        "no": "नहीं",
 | 
			
		||||
        "hide_replies": "जवाब छिपाएं",
 | 
			
		||||
        "search": "खोजें",
 | 
			
		||||
        "search": "खोजें (Ctrl+K)",
 | 
			
		||||
        "loop_this_video": "इस वीडियो को लूप करें",
 | 
			
		||||
        "loading": "लोड हो रहा है...",
 | 
			
		||||
        "show_description": "विवरण दिखाएं",
 | 
			
		||||
| 
						 | 
				
			
			@ -38,17 +41,17 @@
 | 
			
		|||
        "autoplay_video": "ऑटोप्ले वीडियो",
 | 
			
		||||
        "audio_only": "सिर्फ़ ध्वनि",
 | 
			
		||||
        "default_quality": "डिफ़ॉल्ट गुणवत्ता",
 | 
			
		||||
        "country_selection": "देश चयन",
 | 
			
		||||
        "country_selection": "देश",
 | 
			
		||||
        "show_comments": "टिप्पणियाँ दिखाएँ",
 | 
			
		||||
        "store_watch_history": "स्टोर देखने का इतिहास",
 | 
			
		||||
        "language_selection": "भाषा चयन",
 | 
			
		||||
        "language_selection": "भाषा",
 | 
			
		||||
        "instances_list": "इंस्टेंस सूची",
 | 
			
		||||
        "instance_selection": "इंस्टेंस चयन",
 | 
			
		||||
        "instance_selection": "इंस्टेंस",
 | 
			
		||||
        "show_more": "और दिखाओ",
 | 
			
		||||
        "export_to_json": "JSON में निर्यात करें",
 | 
			
		||||
        "import_from_json": "JSON/CSV से आयात करें",
 | 
			
		||||
        "auto_play_next_video": "अगला वीडियो ऑटोप्ले करें",
 | 
			
		||||
        "donations": "दान",
 | 
			
		||||
        "donations": "विकास दान",
 | 
			
		||||
        "minimize_recommendations": "सिफारिशों को कम करें",
 | 
			
		||||
        "show_recommendations": "सिफारिशें दिखाएं",
 | 
			
		||||
        "disable_lbry": "स्ट्रीमिंग के लिए LBRY अक्षम करें",
 | 
			
		||||
| 
						 | 
				
			
			@ -59,19 +62,24 @@
 | 
			
		|||
        "load_more_replies": "और जवाब लोड करें",
 | 
			
		||||
        "enabled_codecs": "सक्षम कोडेक्स (एकाधिक)",
 | 
			
		||||
        "buffering_goal": "बफरिंग गोल (सेकंड में)",
 | 
			
		||||
        "delete_playlist_confirm": "क्या आप वाकई इस प्लेलिस्ट को हटाना चाहते हैं?",
 | 
			
		||||
        "delete_playlist_confirm": "प्लेलिस्ट को मिटाना है?",
 | 
			
		||||
        "add_to_playlist": "प्लेलिस्ट में जोड़ें",
 | 
			
		||||
        "remove_from_playlist": "प्लेलिस्ट से निकाले",
 | 
			
		||||
        "delete_playlist_video_confirm": "क्या आप वाकई इस प्लेलिस्ट से इस वीडियो को निकालना चाहेंगे?",
 | 
			
		||||
        "delete_playlist_video_confirm": "वीडियो को प्लेलिस्ट से निकालना है?",
 | 
			
		||||
        "create_playlist": "प्लेलिस्ट बनायें",
 | 
			
		||||
        "select_playlist": "एक प्लेलिस्ट चुनें",
 | 
			
		||||
        "please_select_playlist": "कृपया एक प्लेलिस्ट चुनें",
 | 
			
		||||
        "delete_playlist": "प्लेलिस्ट हटाएं"
 | 
			
		||||
        "delete_playlist": "प्लेलिस्ट हटाएं",
 | 
			
		||||
        "enable_sponsorblock": "विज्ञापन प्रतिबंध करें",
 | 
			
		||||
        "default_homepage": "स्वतः निर्धारित मुख्यपृष्ठ",
 | 
			
		||||
        "sort_by": "वर्गीकरण:",
 | 
			
		||||
        "skip_automatically": "स्वतः",
 | 
			
		||||
        "delete_account": "खाता मिटाएँ"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "views": "{views} बार देखा गया",
 | 
			
		||||
        "videos": "वीडियो",
 | 
			
		||||
        "watched": "पहले ही देखा",
 | 
			
		||||
        "watched": "पहले ही देखा हुआ",
 | 
			
		||||
        "ratings_disabled": "रेटिंग अक्षम",
 | 
			
		||||
        "chapters": "चैप्टर",
 | 
			
		||||
        "live": "{0} लाइव"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,10 +4,16 @@
 | 
			
		|||
        "watched": "Gledano",
 | 
			
		||||
        "views": "{views} gledanja",
 | 
			
		||||
        "videos": "Videa",
 | 
			
		||||
        "ratings_disabled": "Ocjenjivanje isključeno",
 | 
			
		||||
        "ratings_disabled": "Ocjene su isključene",
 | 
			
		||||
        "chapters": "Poglavlja",
 | 
			
		||||
        "live": "{0} uživo",
 | 
			
		||||
        "shorts": "Kratka videa"
 | 
			
		||||
        "shorts": "Kratka videa",
 | 
			
		||||
        "all": "Sva",
 | 
			
		||||
        "category": "Kategorija",
 | 
			
		||||
        "chapters_horizontal": "Vodoravno",
 | 
			
		||||
        "chapters_vertical": "Okomito",
 | 
			
		||||
        "license": "Licenca",
 | 
			
		||||
        "visibility": "Vidljivost"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "SSL ocjena",
 | 
			
		||||
| 
						 | 
				
			
			@ -20,13 +26,13 @@
 | 
			
		|||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Prikvačio korisnik {author}",
 | 
			
		||||
        "disabled": "Prijenosnik onemogućuje komentare.",
 | 
			
		||||
        "disabled": "Prijenosnik je isključio komentare.",
 | 
			
		||||
        "loading": "Učitavanje komentara...",
 | 
			
		||||
        "user_disabled": "Komentari su isključeni u postavkama."
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "enable_lbry_proxy": "Uključi proxy za LBRY",
 | 
			
		||||
        "disable_lbry": "Onemogući LBRY za prijenos",
 | 
			
		||||
        "disable_lbry": "Isključi LBRY za prijenos",
 | 
			
		||||
        "minimize_description_default": "Standardno sakrij opis",
 | 
			
		||||
        "minimize_description": "Sakrij opis",
 | 
			
		||||
        "show_description": "Prikaži opis",
 | 
			
		||||
| 
						 | 
				
			
			@ -40,14 +46,14 @@
 | 
			
		|||
        "no": "Ne",
 | 
			
		||||
        "yes": "Da",
 | 
			
		||||
        "show_more": "Prikaži više",
 | 
			
		||||
        "instance_selection": "Izbor instance",
 | 
			
		||||
        "enabled_codecs": "Uključeni kodeki (višestruki)",
 | 
			
		||||
        "instance_selection": "Instanca",
 | 
			
		||||
        "enabled_codecs": "Uključeni kodeki (moguće je odabrati nekoliko kodeka)",
 | 
			
		||||
        "instances_list": "Popis instanci",
 | 
			
		||||
        "language_selection": "Izbor jezika",
 | 
			
		||||
        "language_selection": "Jezik",
 | 
			
		||||
        "store_watch_history": "Spremi povijest gledanja",
 | 
			
		||||
        "show_comments": "Prikaži komentare",
 | 
			
		||||
        "default_homepage": "Standardna početna stranica",
 | 
			
		||||
        "country_selection": "Izbor zemlje",
 | 
			
		||||
        "country_selection": "Zemlja",
 | 
			
		||||
        "buffering_goal": "Cilj međuspremnika (u sekundama)",
 | 
			
		||||
        "default_quality": "Standardna kvaliteta",
 | 
			
		||||
        "audio_only": "Samo zvuk",
 | 
			
		||||
| 
						 | 
				
			
			@ -69,36 +75,36 @@
 | 
			
		|||
        "view_subscriptions": "Pogledaj pretplate",
 | 
			
		||||
        "unsubscribe": "Otkaži pretplatu – {count}",
 | 
			
		||||
        "subscribe": "Pretplati se – {count}",
 | 
			
		||||
        "skip_interaction": "Preskoči podsjetnik za interakciju (zahtijeva pretplatu)",
 | 
			
		||||
        "skip_interaction": "Preskoči podsjetnik za interakciju (pretplata)",
 | 
			
		||||
        "skip_outro": "Preskoči odjavnu špicu",
 | 
			
		||||
        "skip_intro": "Preskoči pauzu i uvodnu animaciju",
 | 
			
		||||
        "skip_sponsors": "Preskoči sponzore",
 | 
			
		||||
        "enable_sponsorblock": "Uključi blok sponsora",
 | 
			
		||||
        "loading": "Učitavanje…",
 | 
			
		||||
        "filter": "Filtar",
 | 
			
		||||
        "search": "Pretraga",
 | 
			
		||||
        "search": "Pretraga (Ctrl+K)",
 | 
			
		||||
        "view_ssl_score": "Pogledaj SSL ocjenu",
 | 
			
		||||
        "hide_replies": "Sakrij odgovore",
 | 
			
		||||
        "load_more_replies": "Prikaži više odgovora",
 | 
			
		||||
        "clear_history": "Obriši povijest",
 | 
			
		||||
        "skip_highlight": "Preskoči isticanje",
 | 
			
		||||
        "skip_filler_tangent": "Preskoči nebitne međudijelove",
 | 
			
		||||
        "delete_playlist_confirm": "Izbrisati ovaj popis snimaka?",
 | 
			
		||||
        "remove_from_playlist": "Ukloni iz popisa snimaka",
 | 
			
		||||
        "create_playlist": "Stvori popis snimaka",
 | 
			
		||||
        "delete_playlist": "Izbriši popis snimaka",
 | 
			
		||||
        "add_to_playlist": "Dodaj u popis snimaka",
 | 
			
		||||
        "select_playlist": "Odaberi popis snimaka",
 | 
			
		||||
        "please_select_playlist": "Odaberi popis snimaka",
 | 
			
		||||
        "delete_playlist_video_confirm": "Ukloniti video iz popisa snimaka?",
 | 
			
		||||
        "show_markers": "Prikaži oznake na Pokretaču",
 | 
			
		||||
        "skip_filler_tangent": "Preskoči prazne umetke",
 | 
			
		||||
        "delete_playlist_confirm": "Izbrisati ovu playlistu?",
 | 
			
		||||
        "remove_from_playlist": "Ukloni iz playliste",
 | 
			
		||||
        "create_playlist": "Stvori playlistu",
 | 
			
		||||
        "delete_playlist": "Izbriši playlistu",
 | 
			
		||||
        "add_to_playlist": "Dodaj u playlistu",
 | 
			
		||||
        "select_playlist": "Odaberi playlistu",
 | 
			
		||||
        "please_select_playlist": "Odaberi playlistu",
 | 
			
		||||
        "delete_playlist_video_confirm": "Ukloniti video iz playliste?",
 | 
			
		||||
        "show_markers": "Prikaži oznake na playeru",
 | 
			
		||||
        "delete_account": "Izbriši račun",
 | 
			
		||||
        "logout": "Odjavi se s ovog uređaja",
 | 
			
		||||
        "minimize_recommendations_default": "Standardno sakrij preporuke",
 | 
			
		||||
        "invalidate_session": "Odjavi sve uređaje",
 | 
			
		||||
        "different_auth_instance": "Koristi drugu instancu za autentifikaciju",
 | 
			
		||||
        "instance_auth_selection": "Odabir instance autentifikacije",
 | 
			
		||||
        "clone_playlist": "Dupliciraj popis snimaka",
 | 
			
		||||
        "instance_auth_selection": "Instanca autentifikacije",
 | 
			
		||||
        "clone_playlist": "Dupliciraj playlistu",
 | 
			
		||||
        "clone_playlist_success": "Dupliciranje uspjelo!",
 | 
			
		||||
        "download_as_txt": "Preuzmi kao .txt",
 | 
			
		||||
        "reset_preferences": "Resetiraj postavke",
 | 
			
		||||
| 
						 | 
				
			
			@ -111,14 +117,12 @@
 | 
			
		|||
        "confirm_reset_preferences": "Stvarno želiš resetirati tvoje postavke?",
 | 
			
		||||
        "backup_preferences": "Spremi sigurnosnu kopiju postavki",
 | 
			
		||||
        "with_timecode": "Dijeli s vremenskim kodom",
 | 
			
		||||
        "rename_playlist": "Preimenuj popis snimaka",
 | 
			
		||||
        "new_playlist_name": "Ime novog popisa snimaka",
 | 
			
		||||
        "share": "Dijeli",
 | 
			
		||||
        "show_chapters": "Poglavlja",
 | 
			
		||||
        "documentation": "Dokumentacija",
 | 
			
		||||
        "source_code": "Izvorni kod",
 | 
			
		||||
        "instance_donations": "Donacije instance",
 | 
			
		||||
        "store_search_history": "Spremi povijest pretrage",
 | 
			
		||||
        "store_search_history": "Povijest pretrage trgovine",
 | 
			
		||||
        "hide_watched": "Sakrij gledana videa u novostima",
 | 
			
		||||
        "status_page": "Stanje",
 | 
			
		||||
        "reply_count": "{count} odgovora",
 | 
			
		||||
| 
						 | 
				
			
			@ -126,8 +130,30 @@
 | 
			
		|||
        "minimize_comments": "Sakrij komentare",
 | 
			
		||||
        "show_watch_on_youtube": "Prikaži gumb „Gledaj na YouTubeu”",
 | 
			
		||||
        "minimize_chapters_default": "Standardno sakrij poglavlja",
 | 
			
		||||
        "no_valid_playlists": "Datoteka ne sadrži ispravne popise snimaka!",
 | 
			
		||||
        "with_playlist": "Dijeli s popisom snimaka"
 | 
			
		||||
        "no_valid_playlists": "Datoteka ne sadrži ispravne playliste!",
 | 
			
		||||
        "with_playlist": "Dijeli s playlistom",
 | 
			
		||||
        "playlist_bookmarked": "Zabilježeno",
 | 
			
		||||
        "bookmark_playlist": "Zabilježi",
 | 
			
		||||
        "skip_button_only": "Prikaži gumb za preskakanje",
 | 
			
		||||
        "skip_automatically": "Automatski",
 | 
			
		||||
        "skip_segment": "Preskoči segment",
 | 
			
		||||
        "min_segment_length": "Najmanja duljina segmenta (u sekundama)",
 | 
			
		||||
        "show_less": "Prikaži manje",
 | 
			
		||||
        "autoplay_next_countdown": "Standardno odbrojavanje do sljedećeg videa (u sekundama)",
 | 
			
		||||
        "dismiss": "Odbaci",
 | 
			
		||||
        "create_group": "Stvori grupu",
 | 
			
		||||
        "group_name": "Ime grupe",
 | 
			
		||||
        "auto_display_captions": "Automatski prikaži titlove",
 | 
			
		||||
        "cancel": "Odustani",
 | 
			
		||||
        "okay": "U redu",
 | 
			
		||||
        "edit_playlist": "Uredi playlistu",
 | 
			
		||||
        "playlist_name": "Ime playliste",
 | 
			
		||||
        "playlist_description": "Opis playliste",
 | 
			
		||||
        "chapters_layout_mobile": "Raspored poglavlja na mobilnim uređajima",
 | 
			
		||||
        "show_search_suggestions": "Prikaži prijedloge pretrage",
 | 
			
		||||
        "delete_automatically": "Automatski izbriši nakon",
 | 
			
		||||
        "enable_dearrow": "Aktiviraj DeArrow",
 | 
			
		||||
        "generate_qrcode": "Generiraj QR kod"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Gledaj na {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -140,12 +166,15 @@
 | 
			
		|||
        "register": "Registracija",
 | 
			
		||||
        "login": "Prijava",
 | 
			
		||||
        "trending": "U trendu",
 | 
			
		||||
        "playlists": "Popisi snimaka",
 | 
			
		||||
        "playlists": "Playliste",
 | 
			
		||||
        "account": "Račun",
 | 
			
		||||
        "instance": "Instanca",
 | 
			
		||||
        "player": "Pokretač",
 | 
			
		||||
        "player": "Player",
 | 
			
		||||
        "channels": "Kanali",
 | 
			
		||||
        "livestreams": "Prijenosi uživo"
 | 
			
		||||
        "livestreams": "Prijenosi uživo",
 | 
			
		||||
        "bookmarks": "Zabilješke",
 | 
			
		||||
        "channel_groups": "Grupe kanala",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "password": "Lozinka",
 | 
			
		||||
| 
						 | 
				
			
			@ -156,14 +185,15 @@
 | 
			
		|||
        "all": "YouTube: Sve",
 | 
			
		||||
        "videos": "YouTube: Videa",
 | 
			
		||||
        "channels": "YouTube: Kanali",
 | 
			
		||||
        "playlists": "YouTube: Popisi snimaka",
 | 
			
		||||
        "playlists": "YouTube: Playliste",
 | 
			
		||||
        "music_songs": "YT Music: Pjesme",
 | 
			
		||||
        "music_videos": "YT Music: Videa",
 | 
			
		||||
        "music_albums": "YT Music: Albumi",
 | 
			
		||||
        "music_playlists": "YT Music: Popisi snimaka"
 | 
			
		||||
        "music_playlists": "YT Music: Playliste",
 | 
			
		||||
        "music_artists": "YT Music: Izvođači"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Pretplata na: {0}"
 | 
			
		||||
        "subscribed_channels_count": "Broj pretplata: {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "information": {
 | 
			
		||||
        "preferences_note": "Napomena: postavke se spremaju u lokalno spremište preglednika. Brisanje podataka preglednika resetira postavke."
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +203,12 @@
 | 
			
		|||
        "page_not_found": "Stranica nije pronađena",
 | 
			
		||||
        "copied": "Kopirano!",
 | 
			
		||||
        "cannot_copy": "Nije moguće kopirati!",
 | 
			
		||||
        "local_storage": "Ova radnja zahtijeva lokalno spremište. Jesu li kolačići uključeni?"
 | 
			
		||||
        "local_storage": "Ova radnja zahtijeva lokalno spremište. Jesu li kolačići uključeni?",
 | 
			
		||||
        "register_no_email_note": "Korištenje e-mail adrese kao korisničkog imena se ne preporučuje. Svejedno nastaviti?",
 | 
			
		||||
        "next_video_countdown": "Reprodukcija sljedećeg videa za {0} s",
 | 
			
		||||
        "hours": "{amount} h",
 | 
			
		||||
        "days": "{amount} dan(a)",
 | 
			
		||||
        "weeks": "{amount} tj",
 | 
			
		||||
        "months": "{amount} mj"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,11 @@
 | 
			
		|||
        "subscriptions": "Feliratkozások",
 | 
			
		||||
        "playlists": "Lejátszási listák",
 | 
			
		||||
        "trending": "Felkapott",
 | 
			
		||||
        "account": "Fiók"
 | 
			
		||||
        "account": "Fiók",
 | 
			
		||||
        "player": "Lejátszó",
 | 
			
		||||
        "instance": "Szerver",
 | 
			
		||||
        "livestreams": "Élő adások",
 | 
			
		||||
        "channels": "Csatornák"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "subscribe": "Feliratkozás - {count}",
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +55,7 @@
 | 
			
		|||
        "instance_selection": "Példány kiválasztása",
 | 
			
		||||
        "skip_filler_tangent": "Témától eltérő töltelék/viccek",
 | 
			
		||||
        "loop_this_video": "Videó ismétlése",
 | 
			
		||||
        "donations": "Támogatások",
 | 
			
		||||
        "donations": "Fejlesztési támogatások",
 | 
			
		||||
        "minimize_description": "Leírás minimalizálása",
 | 
			
		||||
        "show_recommendations": "Javaslatok megjelenítése",
 | 
			
		||||
        "enable_lbry_proxy": "Proxy engedélyezése a LBRY számára",
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +89,33 @@
 | 
			
		|||
        "different_auth_instance": "Másik példány használata a hitelesítéshez",
 | 
			
		||||
        "instance_auth_selection": "Autentikációs példány kiválasztása",
 | 
			
		||||
        "clone_playlist": "Lejátszási lista klónozása",
 | 
			
		||||
        "clone_playlist_success": "Sikeresen klónozva!"
 | 
			
		||||
        "clone_playlist_success": "Sikeresen klónozva!",
 | 
			
		||||
        "reset_preferences": "Alaphelyzetbe állítás",
 | 
			
		||||
        "restore_preferences": "Beállítások betöltése fájlból",
 | 
			
		||||
        "instance_donations": "Szerver adományozások",
 | 
			
		||||
        "piped_link": "Piped link",
 | 
			
		||||
        "time_code": "Idő kód (másodpercekben)",
 | 
			
		||||
        "show_chapters": "Fejezetek",
 | 
			
		||||
        "download_as_txt": "Letöltés szövegdokumentumként",
 | 
			
		||||
        "source_code": "A szoftver kódja",
 | 
			
		||||
        "reply_count": "{count} hozzászólások",
 | 
			
		||||
        "documentation": "Dokumentáció",
 | 
			
		||||
        "minimize_chapters_default": "Mindig tüntesd el a fejezeteket",
 | 
			
		||||
        "hide_watched": "Ne mutassa a látott videókat a felíratkozásoknál",
 | 
			
		||||
        "show_watch_on_youtube": "Mutasd a \"Lejátszás Youtube-on\" gombot",
 | 
			
		||||
        "confirm_reset_preferences": "Biztos alaphelyzetbe állítod?",
 | 
			
		||||
        "backup_preferences": "Beállítások mentése",
 | 
			
		||||
        "share": "Megosztás",
 | 
			
		||||
        "with_timecode": "Megosztás & videó kezdés ettől a ponttól",
 | 
			
		||||
        "store_search_history": "Mentse a keresési előzményeket",
 | 
			
		||||
        "follow_link": "Követések link",
 | 
			
		||||
        "copy_link": "Link másolása",
 | 
			
		||||
        "status_page": "Státusz",
 | 
			
		||||
        "no_valid_playlists": "Nincs a fájlban egy valós lejátszási lista se!",
 | 
			
		||||
        "with_playlist": "Megosztás lejátszási listával",
 | 
			
		||||
        "minimize_comments_default": "Mindig tüntesd el a kommenteket",
 | 
			
		||||
        "minimize_comments": "Kommentek eltüntetése",
 | 
			
		||||
        "back_to_home": "Vissza a főoldalra"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "ratings_disabled": "Értékelések Letiltva",
 | 
			
		||||
| 
						 | 
				
			
			@ -129,5 +159,15 @@
 | 
			
		|||
        "disabled": "A hozzászólásokat a feltöltő letiltotta.",
 | 
			
		||||
        "user_disabled": "A beállításoknál a megjegyzések le vannak tiltva.",
 | 
			
		||||
        "loading": "Kommentek betöltése..."
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Feliratkozva: {0} csatornára"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "Figyelem: A beállításaid a böngésződ tárhelyére vannak mentve. Ha törlöd őket el fognak tűnni a beállításaid.",
 | 
			
		||||
        "copied": "Másolva!",
 | 
			
		||||
        "local_storage": "Ennek a beállításnak szüksége van a \"lokális tárhely\" funkcióra, be vannak a sütik kapcsolva?",
 | 
			
		||||
        "cannot_copy": "Nem lehet másolni!",
 | 
			
		||||
        "page_not_found": "Oldnal nem található"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										190
									
								
								src/locales/hy.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								src/locales/hy.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,190 @@
 | 
			
		|||
{
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "skip_automatically": "Ավտոմատվաց",
 | 
			
		||||
        "subscribe": "Բաժանորդ - {count}",
 | 
			
		||||
        "uses_api_from": "Օգտագործում է API  -ից ",
 | 
			
		||||
        "enable_sponsorblock": "Միացնել հովանավորների արգելափակումը",
 | 
			
		||||
        "skip_intro": "Բաց թողնել ընդմիջում/ներածական անիմացիա",
 | 
			
		||||
        "skip_outro": "Բաց թողնել վերջնական քարտերը/վարկերը",
 | 
			
		||||
        "skip_preview": "Բաց թողնել նախադիտումը/վերանայումը",
 | 
			
		||||
        "skip_interaction": "Բաց թողնել փոխազդեցության հիշեցումը (բաժանորդագրվել)",
 | 
			
		||||
        "skip_self_promo": "Բաց թողնել չվճարված/ինքնագովազդ",
 | 
			
		||||
        "skip_highlight": "Բաց թողնել ընդգծումը",
 | 
			
		||||
        "skip_filler_tangent": "Բաց թողնել ավելորդ շոշափող",
 | 
			
		||||
        "show_markers": "Ցույց տալ մարկերները նվագարկչի վրա",
 | 
			
		||||
        "skip_segment": "Բաց թողնել հատվածը",
 | 
			
		||||
        "theme": "Թեմա",
 | 
			
		||||
        "auto": "Ավտոմատ",
 | 
			
		||||
        "dark": "Մութ",
 | 
			
		||||
        "audio_only": "Միայն աուդիո",
 | 
			
		||||
        "buffering_goal": "Բուֆերային նպատակ (վայրկյաններով)",
 | 
			
		||||
        "country_selection": "Երկրի ընտրություն",
 | 
			
		||||
        "default_homepage": "Կանխադրված գլխավոր էջ",
 | 
			
		||||
        "minimize_comments_default": "Նվազացնել մեկնաբանությունները",
 | 
			
		||||
        "channel_name_asc": "Ալիքի անունը (A-Z)",
 | 
			
		||||
        "unsubscribe": "Չեղարկել",
 | 
			
		||||
        "view_subscriptions": "Դիտել բաժանորդագրությունները",
 | 
			
		||||
        "least_recent": "Նվազագույնը վերջին",
 | 
			
		||||
        "sort_by": "Դասավորել ըստ:",
 | 
			
		||||
        "most_recent": "Ամենավերջին",
 | 
			
		||||
        "back": "Ետ",
 | 
			
		||||
        "channel_name_desc": "Ալիքի անունը (Z-A)",
 | 
			
		||||
        "autoplay_video": "Տեսանյութի ավտոմատ նվագարկում",
 | 
			
		||||
        "autoplay_next_countdown": "Կանխադրված հետհաշվարկ մինչև հաջորդ տեսանյութը (վայրկյանների ընթացքում)",
 | 
			
		||||
        "skip_button_only": "Ցույց տալ բաց թողնել կոճակը",
 | 
			
		||||
        "skip_sponsors": "Բաց թողնել հովանավորներին",
 | 
			
		||||
        "min_segment_length": "Սեգմենտի նվազագույն երկարությունը (վայրկյաններով)",
 | 
			
		||||
        "skip_non_music": "Բաց թողնել երաժշտությունը. ոչ երաժշտական բաժին",
 | 
			
		||||
        "default_quality": "Կանխադրված որակ",
 | 
			
		||||
        "light": "Լույս",
 | 
			
		||||
        "store_watch_history": "Խանութի դիտումների պատմությունը",
 | 
			
		||||
        "language_selection": "Լեզվի ընտրություն",
 | 
			
		||||
        "instances_list": "Դեպքերի ցուցակ",
 | 
			
		||||
        "minimize_description_default": "Նվազեցնել նկարագրությունը",
 | 
			
		||||
        "enabled_codecs": "Միացված կոդեկներ (բազմաթիվ)",
 | 
			
		||||
        "show_more": "Ցույց տալ ավելին",
 | 
			
		||||
        "yes": "Այո՛",
 | 
			
		||||
        "no": "Ոչ",
 | 
			
		||||
        "loop_this_video": "Կրկնել այս տեսանյութը",
 | 
			
		||||
        "auto_play_next_video": "Ավտոմատ նվագարկել հաջորդ տեսանյութը",
 | 
			
		||||
        "donations": "Զարգացման նվիրատվություններ",
 | 
			
		||||
        "show_comments": "Ցույց տալ մեկնաբանությունները",
 | 
			
		||||
        "minimize_description": "Նվազեգնել նկարագրությունը",
 | 
			
		||||
        "show_description": "Ցույց տալ նկարագրությունը",
 | 
			
		||||
        "show_recommendations": "Նվազեցնել առաջարկությունները",
 | 
			
		||||
        "disable_lbry": "Անջատել LBRY-ն եթերի համար",
 | 
			
		||||
        "enable_lbry_proxy": "Միացնել պռոքսի LBRY-ի համար",
 | 
			
		||||
        "view_ssl_score": "Դիտեք SSL միավորը",
 | 
			
		||||
        "search": "Որոնում (Ctrl+K)",
 | 
			
		||||
        "hide_replies": "Թաքցնել պատասխանները",
 | 
			
		||||
        "add_to_playlist": "Ավելացնել տեսացանկին",
 | 
			
		||||
        "remove_from_playlist": "Հեռացնել տեսացանկից",
 | 
			
		||||
        "delete_playlist_video_confirm": "Հեռացնե՞լ տեսանյութը տեսացանկից:",
 | 
			
		||||
        "create_playlist": "Ստեղծել տեսացանկ",
 | 
			
		||||
        "delete_playlist": "Ջնջել տեսացանկը",
 | 
			
		||||
        "delete_playlist_confirm": "Ջնջե՞լ այս տեսացանկը:",
 | 
			
		||||
        "please_select_playlist": "Խնդրում ենք ընտրել տեսացանկ",
 | 
			
		||||
        "delete_account": "Հաշիվը ջնջել",
 | 
			
		||||
        "logout": "Դուրս գալ այս սարքից",
 | 
			
		||||
        "minimize_chapters_default": "Նվազեցնել գլուխները",
 | 
			
		||||
        "show_watch_on_youtube": "Ցույց տալ <<դիտել YouTube-ում >> կոճակը",
 | 
			
		||||
        "invalidate_session": "Դուրս գալ բոլոր սարքերից",
 | 
			
		||||
        "instance_auth_selection": "Նույնականացման օրինակի ընտրություն",
 | 
			
		||||
        "clone_playlist_success": "Հաջողությամբ կլոնավորվեց:",
 | 
			
		||||
        "download_as_txt": "Ներբեռնեք որպես .txt",
 | 
			
		||||
        "reset_preferences": "Վերակայել նախապատվությունները",
 | 
			
		||||
        "confirm_reset_preferences": "Իսկապե՞ս ուզում եք վերակայել ձեր նախապատվությունները:",
 | 
			
		||||
        "backup_preferences": "Պահուստային նախապատվություններ",
 | 
			
		||||
        "restore_preferences": "Վերականգնել նախապատվությունները",
 | 
			
		||||
        "follow_link": "Հետևել հղմանը",
 | 
			
		||||
        "instance_donations": "Օրինակների նվիրատվություններ",
 | 
			
		||||
        "reply_count": "{count} պատասխան",
 | 
			
		||||
        "no_valid_playlists": "Ֆայլը վավեր տեսացանկկեր չի պարունակում:",
 | 
			
		||||
        "with_playlist": "Կիսվեք տեսացանկով",
 | 
			
		||||
        "bookmark_playlist": "Էջանիշ",
 | 
			
		||||
        "playlist_bookmarked": "Էջանշված",
 | 
			
		||||
        "show_less": "Ցույց տալ ավելի քիչ",
 | 
			
		||||
        "create_group": "Ստեղծել խումբ",
 | 
			
		||||
        "minimize_recommendations": "Նվազեցնել առաջարկությունները",
 | 
			
		||||
        "filter": "Զտել",
 | 
			
		||||
        "instance_selection": "Օրինակի ընտրություն",
 | 
			
		||||
        "import_from_json": "Ներմուծում JSON/CSV-ից",
 | 
			
		||||
        "export_to_json": "Արտահանել JSON",
 | 
			
		||||
        "minimize_comments": "Նվազագույնի հասցնել մեկնաբանությունները",
 | 
			
		||||
        "clear_history": "Մաքրել պատմություն",
 | 
			
		||||
        "loading": "Բեռնվում է...",
 | 
			
		||||
        "with_timecode": "Կիսվեք ժամանակի կոդով",
 | 
			
		||||
        "load_more_replies": "Բեռնել ավելի շատ պատասխաններ",
 | 
			
		||||
        "select_playlist": "Ընտրեք տեսացանկ",
 | 
			
		||||
        "minimize_recommendations_default": "Նվազեցնել առաջարկությունները",
 | 
			
		||||
        "clone_playlist": "Կլոնավորել տեսացանկը",
 | 
			
		||||
        "back_to_home": "Վերադարձ դեպի հիմնական էջ",
 | 
			
		||||
        "different_auth_instance": "Նույնականացման համար օգտագործեք այլ օրինակ",
 | 
			
		||||
        "share": "Կիսվել",
 | 
			
		||||
        "copy_link": "Պատճենել հղումը",
 | 
			
		||||
        "dismiss": "Հեռացնել",
 | 
			
		||||
        "piped_link": "Piped հղում",
 | 
			
		||||
        "time_code": "Ժամանակի կոդը (վայրկյաններով)",
 | 
			
		||||
        "store_search_history": "Խանութի որոնման պատմություն",
 | 
			
		||||
        "show_chapters": "Գլուխներ",
 | 
			
		||||
        "hide_watched": "Թաքցնել դիտված տեսանյութերը լրահոսում",
 | 
			
		||||
        "status_page": "Կարգավիճակ",
 | 
			
		||||
        "documentation": "Փաստաթղթեր",
 | 
			
		||||
        "source_code": "Աղբյուրի կոդը",
 | 
			
		||||
        "group_name": "Խմբի անվանումը"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "trending": "Թրենդային",
 | 
			
		||||
        "login": "Մտնել",
 | 
			
		||||
        "register": "Գրանցվել",
 | 
			
		||||
        "feed": "Սկիզբ",
 | 
			
		||||
        "preferences": "Նախապատվություններ",
 | 
			
		||||
        "history": "Պատմություն",
 | 
			
		||||
        "subscriptions": "Հետևում եմ",
 | 
			
		||||
        "playlists": "Տեսացանկեր",
 | 
			
		||||
        "account": "Հաշիվ",
 | 
			
		||||
        "instance": "Օրինակ",
 | 
			
		||||
        "player": "նվագարկիչ",
 | 
			
		||||
        "livestreams": "Ուղիղ Եթերներ",
 | 
			
		||||
        "channels": "Ալիքներ",
 | 
			
		||||
        "bookmarks": "Էջանիշեր",
 | 
			
		||||
        "channel_groups": "Ալիքի խմբեր"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Դիտեք {0}-ով"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "registered_users": "Գրանցված օգտվողներ",
 | 
			
		||||
        "version": "Տարբերակ",
 | 
			
		||||
        "up_to_date": "Մինչ օրս",
 | 
			
		||||
        "ssl_score": "SSL միավոր",
 | 
			
		||||
        "instance_locations": "Օրինակների տեղադրություններ",
 | 
			
		||||
        "instance_name": "Օրինակի անվանումը",
 | 
			
		||||
        "has_cdn": "Ունի CDN:"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Օգտագործողի անունը",
 | 
			
		||||
        "password": "Գաղտնաբառ"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Տեսանյութեր",
 | 
			
		||||
        "views": "{views} դիտում",
 | 
			
		||||
        "watched": "Դիտվաց",
 | 
			
		||||
        "sponsor_segments": "Հովանավորների հատվածներ",
 | 
			
		||||
        "ratings_disabled": "Վարկանիշներն անջատված են",
 | 
			
		||||
        "chapters": "Գլուխներ",
 | 
			
		||||
        "live": "{0} Եթեր",
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Բոլորը",
 | 
			
		||||
        "category": "Կարգ"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Դուք նկատի ունեիք՝ {0}:",
 | 
			
		||||
        "all": "YouTube: Բոլորը",
 | 
			
		||||
        "videos": "YouTube: Տեսանյութեր",
 | 
			
		||||
        "channels": "YouTube: Ալիքներ",
 | 
			
		||||
        "playlists": "YouTube՝ տեսացանկ",
 | 
			
		||||
        "music_songs": "YT Երաժշտություն. երգեր",
 | 
			
		||||
        "music_videos": "YT Երաժշտություն. Տեսանյութեր",
 | 
			
		||||
        "music_albums": "YT Երաժշտություն. Ալբոմներ",
 | 
			
		||||
        "music_playlists": "YT Երաժշտություն. տեսացանկ"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Բաժանորդագրված է՝ {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "loading": "Մեկնաբանությունների բեռնում...",
 | 
			
		||||
        "pinned_by": "Ամրացված է {author}-ի կողմից",
 | 
			
		||||
        "disabled": "Մեկնաբանություններն անջատված են վերբեռնողի կողմից:",
 | 
			
		||||
        "user_disabled": "Մեկնաբանություններն անջատված են կարգավորումներում:"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "Նշում. նախապատվությունները պահվում են ձեր բրաուզերի տեղական պահեստում: Ձեր բրաուզերի տվյալները ջնջելով դրանք կվերակայվեն:",
 | 
			
		||||
        "page_not_found": "Էջը չի գտնվել",
 | 
			
		||||
        "copied": "Պատճենվել է",
 | 
			
		||||
        "cannot_copy": "Հնարավոր չէ պատճենել:",
 | 
			
		||||
        "local_storage": "Այս գործողության համար պահանջվում է տեղական պահեստ, քուքի ֆայլերը միացվա՞ծ են:",
 | 
			
		||||
        "register_no_email_note": "Էլեկտրոնային փոստի օգտագործումը որպես օգտվողի անուն խորհուրդ չի տրվում: Շարունակե՞լ, այնուամենայնիվ:",
 | 
			
		||||
        "next_video_countdown": "Հաջորդ տեսանյութը նվագարկվում է {0} վրկ-ում"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,10 @@
 | 
			
		|||
        "account": "Akun",
 | 
			
		||||
        "player": "Pemain",
 | 
			
		||||
        "livestreams": "Siaran Langsung",
 | 
			
		||||
        "channels": "Saluran"
 | 
			
		||||
        "channels": "Saluran",
 | 
			
		||||
        "bookmarks": "Markah",
 | 
			
		||||
        "channel_groups": "Grup saluran",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Tonton di {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -41,14 +44,14 @@
 | 
			
		|||
        "audio_only": "Audio Saja",
 | 
			
		||||
        "default_quality": "Kualitas Bawaan",
 | 
			
		||||
        "buffering_goal": "Tujuan Buffering (dalam detik)",
 | 
			
		||||
        "country_selection": "Pemilihan Negara",
 | 
			
		||||
        "country_selection": "Negara",
 | 
			
		||||
        "default_homepage": "Halaman Beranda Bawaan",
 | 
			
		||||
        "show_comments": "Tampilkan Komentar",
 | 
			
		||||
        "minimize_description_default": "Kecilkan Deskripsi secara default",
 | 
			
		||||
        "language_selection": "Pemilihan Bahasa",
 | 
			
		||||
        "language_selection": "Bahasa",
 | 
			
		||||
        "instances_list": "Daftar Instansi",
 | 
			
		||||
        "enabled_codecs": "Kodek yang Diaktifkan (Beberapa)",
 | 
			
		||||
        "instance_selection": "Pemilihan Instansi",
 | 
			
		||||
        "instance_selection": "Instansi",
 | 
			
		||||
        "show_more": "Tampilkan Lebih Banyak",
 | 
			
		||||
        "yes": "Iya",
 | 
			
		||||
        "no": "Tidak",
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +66,7 @@
 | 
			
		|||
        "disable_lbry": "Nonaktifkan LBRY untuk Streaming",
 | 
			
		||||
        "enable_lbry_proxy": "Aktifkan Proksi untuk LBRY",
 | 
			
		||||
        "view_ssl_score": "Tampilkan Skor SSL",
 | 
			
		||||
        "search": "Telusuri",
 | 
			
		||||
        "search": "Telusuri (Ctrl+K)",
 | 
			
		||||
        "filter": "Saring",
 | 
			
		||||
        "loading": "Memuat...",
 | 
			
		||||
        "clear_history": "Hapus Riwayat",
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +92,7 @@
 | 
			
		|||
        "logout": "Keluar dari perangkat ini",
 | 
			
		||||
        "minimize_recommendations_default": "Kecilkan Rekomendasi secara bawaan",
 | 
			
		||||
        "invalidate_session": "Keluarkan semua perangkat",
 | 
			
		||||
        "instance_auth_selection": "Pemilihan Instansi Otentikasi",
 | 
			
		||||
        "instance_auth_selection": "Instance Otentikasi",
 | 
			
		||||
        "different_auth_instance": "Gunakan instansi lain untuk otentikasi",
 | 
			
		||||
        "clone_playlist_success": "Berhasil disalin!",
 | 
			
		||||
        "clone_playlist": "Salin Daftar Putar",
 | 
			
		||||
| 
						 | 
				
			
			@ -98,8 +101,6 @@
 | 
			
		|||
        "restore_preferences": "Pulihkan preferensi",
 | 
			
		||||
        "confirm_reset_preferences": "Apakah Anda yakin ingin mengatur ulang preferensi Anda?",
 | 
			
		||||
        "backup_preferences": "Cadangkan preferensi",
 | 
			
		||||
        "rename_playlist": "Ubah nama daftar putar",
 | 
			
		||||
        "new_playlist_name": "Nama daftar putar baru",
 | 
			
		||||
        "share": "Bagikan",
 | 
			
		||||
        "with_timecode": "Bagikan dengan kode waktu",
 | 
			
		||||
        "piped_link": "Tautan Piped",
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +121,29 @@
 | 
			
		|||
        "show_watch_on_youtube": "Tampilkan tombol Tonton di YouTube",
 | 
			
		||||
        "minimize_chapters_default": "Kecilkan Bab secara bawaan",
 | 
			
		||||
        "no_valid_playlists": "Berkas ini tidak berisi daftar putar yang valid!",
 | 
			
		||||
        "with_playlist": "Bagikan dengan daftar putar"
 | 
			
		||||
        "with_playlist": "Bagikan dengan daftar putar",
 | 
			
		||||
        "playlist_bookmarked": "Dimarkahi",
 | 
			
		||||
        "bookmark_playlist": "Markahi",
 | 
			
		||||
        "skip_button_only": "Tampilkan tombol lewati",
 | 
			
		||||
        "skip_automatically": "Secara otomatis",
 | 
			
		||||
        "min_segment_length": "Panjang Segmen Minimum (dalam detik)",
 | 
			
		||||
        "skip_segment": "Lewati Segmen",
 | 
			
		||||
        "show_less": "Tampilkan lebih sedikit",
 | 
			
		||||
        "autoplay_next_countdown": "Hitungan mundur bawaan sebelum video berikutnya (dalam detik)",
 | 
			
		||||
        "dismiss": "Abaikan",
 | 
			
		||||
        "create_group": "Buat grup",
 | 
			
		||||
        "group_name": "Nama grup",
 | 
			
		||||
        "auto_display_captions": "Tampilkan Takarir Secara Otomatis",
 | 
			
		||||
        "cancel": "Batal",
 | 
			
		||||
        "playlist_description": "Deskripsi daftar putar",
 | 
			
		||||
        "edit_playlist": "Sunting daftar putar",
 | 
			
		||||
        "playlist_name": "Nama daftar putar",
 | 
			
		||||
        "okay": "Oke",
 | 
			
		||||
        "show_search_suggestions": "Tampilkan saran pencarian",
 | 
			
		||||
        "chapters_layout_mobile": "Tata Letak Bab di Ponsel",
 | 
			
		||||
        "delete_automatically": "Hapus secara otomatis setelah",
 | 
			
		||||
        "enable_dearrow": "Aktifkan DeArrow",
 | 
			
		||||
        "generate_qrcode": "Buat Kode QR"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Dipasangi pin oleh {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +162,9 @@
 | 
			
		|||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Nama Pengguna",
 | 
			
		||||
        "password": "Kata Sandi"
 | 
			
		||||
        "password": "Kata Sandi",
 | 
			
		||||
        "password_confirm": "Konfirmasi kata sandi",
 | 
			
		||||
        "passwords_incorrect": "Kata sandi tidak cocok!"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Video",
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +174,13 @@
 | 
			
		|||
        "ratings_disabled": "Penilaian Dinonaktifkan",
 | 
			
		||||
        "chapters": "Bagian",
 | 
			
		||||
        "live": "{0} Langsung",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Semua",
 | 
			
		||||
        "category": "Kategori",
 | 
			
		||||
        "chapters_horizontal": "Horisontal",
 | 
			
		||||
        "chapters_vertical": "Vertikal",
 | 
			
		||||
        "license": "Lisensi",
 | 
			
		||||
        "visibility": "Visibilitas"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Apakah Anda bermaksud: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +191,8 @@
 | 
			
		|||
        "music_videos": "YT Music: Video",
 | 
			
		||||
        "music_albums": "YT Music: Album",
 | 
			
		||||
        "music_playlists": "YT Music: Daftar Putar",
 | 
			
		||||
        "all": "YouTube: Semua"
 | 
			
		||||
        "all": "YouTube: Semua",
 | 
			
		||||
        "music_artists": "YT Music: Artis"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Berlangganan ke: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +205,12 @@
 | 
			
		|||
        "preferences_note": "Catatan: preferensi disimpan dalam penyimpanan lokal peramban Anda. Menghapus data peramban Anda akan mengatur ulang.",
 | 
			
		||||
        "copied": "Disalin!",
 | 
			
		||||
        "cannot_copy": "Tidak dapat menyalin!",
 | 
			
		||||
        "local_storage": "Tindakan ini membutuhkan localStorage, apakah kuki diaktifkan?"
 | 
			
		||||
        "local_storage": "Tindakan ini membutuhkan localStorage, apakah kuki diaktifkan?",
 | 
			
		||||
        "register_no_email_note": "Menggunakan surel sebagai nama pengguna tidak disarankan. Lanjut?",
 | 
			
		||||
        "next_video_countdown": "Memutar video berikutnya dalam {0} detik",
 | 
			
		||||
        "weeks": "{amount} minggu",
 | 
			
		||||
        "hours": "{amount} jam",
 | 
			
		||||
        "days": "{amount} hari",
 | 
			
		||||
        "months": "{amount} bulan"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,9 @@
 | 
			
		|||
        "account": "Reikningur",
 | 
			
		||||
        "instance": "Tilvik",
 | 
			
		||||
        "livestreams": "Útsendingar í beinni",
 | 
			
		||||
        "channels": "Rásir"
 | 
			
		||||
        "channels": "Rásir",
 | 
			
		||||
        "bookmarks": "Bókamerki",
 | 
			
		||||
        "channel_groups": "Rásarhópar"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "sort_by": "Raða eftir:",
 | 
			
		||||
| 
						 | 
				
			
			@ -94,8 +96,6 @@
 | 
			
		|||
        "instance_donations": "Framlög til netþjóns",
 | 
			
		||||
        "status_page": "Staða",
 | 
			
		||||
        "source_code": "Frumkóði",
 | 
			
		||||
        "rename_playlist": "Endurnefna spilunarlista",
 | 
			
		||||
        "new_playlist_name": "Nýtt heiti spilunarlista",
 | 
			
		||||
        "share": "Deila",
 | 
			
		||||
        "with_timecode": "Deilа með tímakóða",
 | 
			
		||||
        "piped_link": "Hlekkur Piped",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
{
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "instances_list": "Elenco delle istanze",
 | 
			
		||||
        "language_selection": "Selezione della lingua",
 | 
			
		||||
        "language_selection": "Lingua",
 | 
			
		||||
        "store_watch_history": "Conserva la cronologia di visualizzazione",
 | 
			
		||||
        "minimize_description_default": "Minimizza la descrizione per impostazione predefinita",
 | 
			
		||||
        "show_comments": "Mostra i commenti",
 | 
			
		||||
        "default_homepage": "Pagina iniziale predefinita",
 | 
			
		||||
        "country_selection": "Selezione del paese",
 | 
			
		||||
        "country_selection": "Paese",
 | 
			
		||||
        "buffering_goal": "Obiettivo di buffering (in secondi)",
 | 
			
		||||
        "default_quality": "Qualità predefinita",
 | 
			
		||||
        "audio_only": "Solo audio",
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +37,7 @@
 | 
			
		|||
        "enable_lbry_proxy": "Abilita il proxy per LBRY",
 | 
			
		||||
        "disable_lbry": "Disabilita LBRY per lo streaming",
 | 
			
		||||
        "show_description": "Mostra la descrizione",
 | 
			
		||||
        "minimize_description": "Minimizza la descrizione per impostazione predefinita",
 | 
			
		||||
        "minimize_description": "Minimizza descrizione",
 | 
			
		||||
        "minimize_recommendations": "Minimizza consigli",
 | 
			
		||||
        "show_recommendations": "Mostra consigli",
 | 
			
		||||
        "donations": "Donazioni per lo sviluppo",
 | 
			
		||||
| 
						 | 
				
			
			@ -48,10 +48,10 @@
 | 
			
		|||
        "no": "No",
 | 
			
		||||
        "yes": "Sì",
 | 
			
		||||
        "show_more": "Mostra di più",
 | 
			
		||||
        "instance_selection": "Selezione dell'istanza",
 | 
			
		||||
        "instance_selection": "Istanza",
 | 
			
		||||
        "loading": "Caricamento…",
 | 
			
		||||
        "filter": "Filtra",
 | 
			
		||||
        "search": "Cerca",
 | 
			
		||||
        "search": "Cerca (Ctrl+K)",
 | 
			
		||||
        "view_ssl_score": "Visualizza il punteggio SSL",
 | 
			
		||||
        "clear_history": "Cancella la cronologia",
 | 
			
		||||
        "load_more_replies": "Carica più risposte",
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +72,7 @@
 | 
			
		|||
        "minimize_recommendations_default": "Riduci al minimo le raccomandazioni per impostazione predefinita",
 | 
			
		||||
        "different_auth_instance": "Usa un'istanza diversa per l'autenticazione",
 | 
			
		||||
        "invalidate_session": "Disconnetti su tutti i dispositivi",
 | 
			
		||||
        "instance_auth_selection": "Selezione dell'istanza di autenticazione",
 | 
			
		||||
        "instance_auth_selection": "Istanza di autenticazione",
 | 
			
		||||
        "clone_playlist_success": "Clonato con successo!",
 | 
			
		||||
        "clone_playlist": "Clona la playlist",
 | 
			
		||||
        "download_as_txt": "Scarica come .txt",
 | 
			
		||||
| 
						 | 
				
			
			@ -87,8 +87,6 @@
 | 
			
		|||
        "time_code": "Tempo (in secondi)",
 | 
			
		||||
        "follow_link": "Apri il collegamento",
 | 
			
		||||
        "with_timecode": "Condividi con marca temporale",
 | 
			
		||||
        "new_playlist_name": "Nuovo nome dalla playlist",
 | 
			
		||||
        "rename_playlist": "Rinomina la playlist",
 | 
			
		||||
        "show_chapters": "Capitoli",
 | 
			
		||||
        "store_search_history": "Memorizza la cronologia delle ricerche",
 | 
			
		||||
        "status_page": "Stato",
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +99,25 @@
 | 
			
		|||
        "minimize_comments_default": "Minimizza i commenti per impostazione predefinita",
 | 
			
		||||
        "minimize_comments": "Minimizza i commenti",
 | 
			
		||||
        "minimize_chapters_default": "Minimizza i capitoli per impostazione predefinita",
 | 
			
		||||
        "no_valid_playlists": "Il file non contiene playlist valide!"
 | 
			
		||||
        "no_valid_playlists": "Il file non contiene playlist valide!",
 | 
			
		||||
        "bookmark_playlist": "Segnalibro",
 | 
			
		||||
        "with_playlist": "Condividi con la playlist",
 | 
			
		||||
        "playlist_bookmarked": "Nei segnalibri",
 | 
			
		||||
        "min_segment_length": "Lunghezza minima del segmento (in secondi)",
 | 
			
		||||
        "skip_automatically": "Automaticamente",
 | 
			
		||||
        "skip_button_only": "Mostra pulsante di salto",
 | 
			
		||||
        "skip_segment": "Salta segmento",
 | 
			
		||||
        "show_less": "Mostra meno",
 | 
			
		||||
        "autoplay_next_countdown": "Conto alla rovescia predefinito prima del video successivo (in secondi)",
 | 
			
		||||
        "cancel": "Annulla",
 | 
			
		||||
        "okay": "Va bene",
 | 
			
		||||
        "edit_playlist": "Modifica playlist",
 | 
			
		||||
        "playlist_name": "Nome playilist",
 | 
			
		||||
        "playlist_description": "Descrizione playlist",
 | 
			
		||||
        "group_name": "Nome gruppo",
 | 
			
		||||
        "create_group": "Crea gruppo",
 | 
			
		||||
        "show_search_suggestions": "Mostra suggerimenti di ricerca",
 | 
			
		||||
        "dismiss": "Chiudi"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Guarda su {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -119,7 +135,8 @@
 | 
			
		|||
        "instance": "Istanza",
 | 
			
		||||
        "player": "Riproduttore",
 | 
			
		||||
        "livestreams": "Streaming live",
 | 
			
		||||
        "channels": "Canali"
 | 
			
		||||
        "channels": "Canali",
 | 
			
		||||
        "bookmarks": "Segnalibri"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "sponsor_segments": "Segmenti sponsor",
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +146,11 @@
 | 
			
		|||
        "ratings_disabled": "Valutazioni disabilitate",
 | 
			
		||||
        "live": "{0} Diretta",
 | 
			
		||||
        "chapters": "Capitoli",
 | 
			
		||||
        "shorts": "Short"
 | 
			
		||||
        "shorts": "Short",
 | 
			
		||||
        "all": "Tutti",
 | 
			
		||||
        "category": "Categoria",
 | 
			
		||||
        "chapters_horizontal": "Orizzontale",
 | 
			
		||||
        "chapters_vertical": "Verticale"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "Valutazione SSL",
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +180,8 @@
 | 
			
		|||
        "channels": "YouTube: Canali",
 | 
			
		||||
        "music_songs": "YT Music: Canzoni",
 | 
			
		||||
        "all": "YouTube: Tutto",
 | 
			
		||||
        "videos": "YouTube: Video"
 | 
			
		||||
        "videos": "YouTube: Video",
 | 
			
		||||
        "music_artists": "YT Music: Artisti"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Abbonato a: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -172,6 +194,8 @@
 | 
			
		|||
        "preferences_note": "Nota: le preferenze sono salvate nella memoria locale del tuo browser. L'eliminazione dei dati del tuo browser le ripristinerà.",
 | 
			
		||||
        "copied": "Copiato!",
 | 
			
		||||
        "cannot_copy": "Impossibile copiare!",
 | 
			
		||||
        "local_storage": "Questa azione richiede localStorage, i cookie sono abilitati?"
 | 
			
		||||
        "local_storage": "Questa azione richiede localStorage, i cookie sono abilitati?",
 | 
			
		||||
        "register_no_email_note": "L'utilizzo di un indirizzo e-mail come nome utente è sconsigliato. Continuare comunque?",
 | 
			
		||||
        "next_video_countdown": "Riproduzione prossimo video tra {0}s"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,10 +12,13 @@
 | 
			
		|||
        "player": "プレイヤー",
 | 
			
		||||
        "instance": "インスタンス",
 | 
			
		||||
        "channels": "チャンネル",
 | 
			
		||||
        "livestreams": "ライブ配信"
 | 
			
		||||
        "livestreams": "ライブ配信",
 | 
			
		||||
        "bookmarks": "ブックマーク",
 | 
			
		||||
        "channel_groups": "グループ",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "{0}で見る"
 | 
			
		||||
        "watch_on": "{0}で視聴"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "subscribe": "チャンネル登録 - {count}",
 | 
			
		||||
| 
						 | 
				
			
			@ -27,32 +30,32 @@
 | 
			
		|||
        "channel_name_asc": "チャンネル名 (AからZ)",
 | 
			
		||||
        "channel_name_desc": "チャンネル名 (ZからA)",
 | 
			
		||||
        "back": "戻る",
 | 
			
		||||
        "uses_api_from": "API使用元 ",
 | 
			
		||||
        "enable_sponsorblock": "SponsorBlockを有効化",
 | 
			
		||||
        "uses_api_from": "API 提供元 ",
 | 
			
		||||
        "enable_sponsorblock": "SponsorBlock を使用",
 | 
			
		||||
        "skip_sponsors": "広告をスキップ",
 | 
			
		||||
        "skip_intro": "インターミッション/イントロアニメーションをスキップする",
 | 
			
		||||
        "skip_outro": "エンドカード/クレジットをスキップする",
 | 
			
		||||
        "skip_preview": "プレビュー/要約をスキップ",
 | 
			
		||||
        "skip_interaction": "インタラクションリマインダーをスキップする (チャンネル登録)",
 | 
			
		||||
        "skip_self_promo": "無償/自己プロモーションをスキップ",
 | 
			
		||||
        "skip_intro": "合間/導入アニメをスキップ",
 | 
			
		||||
        "skip_outro": "終了シーン/クレジットをスキップ",
 | 
			
		||||
        "skip_preview": "予告/あらすじをスキップ",
 | 
			
		||||
        "skip_interaction": "登録など操作を頼む自己宣伝をスキップ",
 | 
			
		||||
        "skip_self_promo": "無償または自己の宣伝をスキップ",
 | 
			
		||||
        "skip_non_music": "音楽: 非音楽部分をスキップ",
 | 
			
		||||
        "theme": "テーマ",
 | 
			
		||||
        "auto": "自動",
 | 
			
		||||
        "dark": "ダーク",
 | 
			
		||||
        "light": "ライト",
 | 
			
		||||
        "autoplay_video": "動画を自動再生",
 | 
			
		||||
        "audio_only": "音声のみ",
 | 
			
		||||
        "default_quality": "デフォルトの画質",
 | 
			
		||||
        "audio_only": "音声のみのモード",
 | 
			
		||||
        "default_quality": "標準の画質",
 | 
			
		||||
        "buffering_goal": "バッファリング目標値 (秒)",
 | 
			
		||||
        "country_selection": "国の選択",
 | 
			
		||||
        "default_homepage": "デフォルトのホームページ",
 | 
			
		||||
        "country_selection": "国",
 | 
			
		||||
        "default_homepage": "ホームに表示するページ",
 | 
			
		||||
        "show_comments": "コメントを表示",
 | 
			
		||||
        "minimize_description_default": "デフォルトで詳細を最小化する",
 | 
			
		||||
        "store_watch_history": "再生履歴を保存する",
 | 
			
		||||
        "language_selection": "言語の選択",
 | 
			
		||||
        "minimize_description_default": "最初から説明を最小化",
 | 
			
		||||
        "store_watch_history": "再生履歴を保存",
 | 
			
		||||
        "language_selection": "言語",
 | 
			
		||||
        "instances_list": "インスタンス一覧",
 | 
			
		||||
        "enabled_codecs": "コーデックの有効化 (複数選択)",
 | 
			
		||||
        "instance_selection": "インスタンスを選択",
 | 
			
		||||
        "instance_selection": "インスタンス",
 | 
			
		||||
        "show_more": "もっと見る",
 | 
			
		||||
        "yes": "はい",
 | 
			
		||||
        "no": "いいえ",
 | 
			
		||||
| 
						 | 
				
			
			@ -66,60 +69,81 @@
 | 
			
		|||
        "minimize_recommendations": "おすすめを最小化",
 | 
			
		||||
        "show_recommendations": "おすすめを見る",
 | 
			
		||||
        "disable_lbry": "ストリーミングのLBRYを無効化",
 | 
			
		||||
        "enable_lbry_proxy": "LBRYプロキシをオン",
 | 
			
		||||
        "view_ssl_score": "SSLスコアを見る",
 | 
			
		||||
        "search": "検索",
 | 
			
		||||
        "enable_lbry_proxy": "LBRYにプロキシを使用",
 | 
			
		||||
        "view_ssl_score": "SSLの評価を表示",
 | 
			
		||||
        "search": "検索 (Ctrl+K)",
 | 
			
		||||
        "filter": "フィルター",
 | 
			
		||||
        "loading": "読み込み中…",
 | 
			
		||||
        "clear_history": "再生履歴を削除",
 | 
			
		||||
        "hide_replies": "返信を非表示",
 | 
			
		||||
        "load_more_replies": "リプライをもっと見る",
 | 
			
		||||
        "skip_filler_tangent": "無関係なコンテンツをスキップ",
 | 
			
		||||
        "load_more_replies": "返信をもっと見る",
 | 
			
		||||
        "skip_filler_tangent": "無関係な談話をスキップ",
 | 
			
		||||
        "skip_highlight": "ハイライトをスキップ",
 | 
			
		||||
        "add_to_playlist": "再生リストに追加する",
 | 
			
		||||
        "add_to_playlist": "再生リストに追加",
 | 
			
		||||
        "create_playlist": "再生リストを作成",
 | 
			
		||||
        "remove_from_playlist": "再生リストから削除",
 | 
			
		||||
        "delete_playlist_video_confirm": "再生リストからこの動画を削除しますか?",
 | 
			
		||||
        "delete_playlist": "再生リストを削除",
 | 
			
		||||
        "please_select_playlist": "再生リストを選択してください",
 | 
			
		||||
        "show_markers": "プレイヤーにマーカーを表示",
 | 
			
		||||
        "show_markers": "プレイヤーに目印の区切りを表示",
 | 
			
		||||
        "select_playlist": "再生リストを選択",
 | 
			
		||||
        "delete_playlist_confirm": "再生リストを削除しますか?",
 | 
			
		||||
        "delete_account": "アカウントを削除する",
 | 
			
		||||
        "store_search_history": "検索履歴を保存する",
 | 
			
		||||
        "delete_account": "アカウントを削除",
 | 
			
		||||
        "store_search_history": "検索履歴を保存",
 | 
			
		||||
        "show_chapters": "チャプター",
 | 
			
		||||
        "status_page": "状態",
 | 
			
		||||
        "source_code": "ソースコード",
 | 
			
		||||
        "instance_donations": "インスタンスに寄付",
 | 
			
		||||
        "minimize_comments": "コメントを最小化する",
 | 
			
		||||
        "minimize_comments": "コメントを最小化",
 | 
			
		||||
        "share": "共有",
 | 
			
		||||
        "with_timecode": "タイムコード付きで共有",
 | 
			
		||||
        "different_auth_instance": "認証に別のインスタンスを使う",
 | 
			
		||||
        "with_timecode": "時間指定",
 | 
			
		||||
        "different_auth_instance": "認証に別のインスタンスを使用",
 | 
			
		||||
        "download_as_txt": ".txtでダウンロード",
 | 
			
		||||
        "logout": "このデバイスでログアウト",
 | 
			
		||||
        "minimize_recommendations_default": "デフォルトでおすすめを最小化する",
 | 
			
		||||
        "logout": "この端末からログアウト",
 | 
			
		||||
        "minimize_recommendations_default": "最初からおすすめを最小化",
 | 
			
		||||
        "hide_watched": "再生済みの動画をフィードに表示しない",
 | 
			
		||||
        "minimize_chapters_default": "デフォルトでチャプターを最小化する",
 | 
			
		||||
        "show_watch_on_youtube": "\"YouTubeで見る\"ボタンを表示する",
 | 
			
		||||
        "invalidate_session": "すべてのデバイスでログアウトする",
 | 
			
		||||
        "instance_auth_selection": "認証インスタンスの選択",
 | 
			
		||||
        "minimize_chapters_default": "最初からチャプターを最小化",
 | 
			
		||||
        "show_watch_on_youtube": "「YouTubeで視聴」ボタンを表示",
 | 
			
		||||
        "invalidate_session": "すべての端末からログアウト",
 | 
			
		||||
        "instance_auth_selection": "認証用インスタンス",
 | 
			
		||||
        "clone_playlist_success": "複製に成功しました!",
 | 
			
		||||
        "backup_preferences": "設定をバックアップする",
 | 
			
		||||
        "restore_preferences": "設定を復元する",
 | 
			
		||||
        "backup_preferences": "設定をバックアップ",
 | 
			
		||||
        "restore_preferences": "設定を復元",
 | 
			
		||||
        "back_to_home": "ホームに戻る",
 | 
			
		||||
        "copy_link": "リンクをコピーする",
 | 
			
		||||
        "copy_link": "リンクコピー",
 | 
			
		||||
        "time_code": "タイムコード (秒)",
 | 
			
		||||
        "documentation": "ドキュメント",
 | 
			
		||||
        "reset_preferences": "設定をリセット",
 | 
			
		||||
        "reset_preferences": "設定を初期化",
 | 
			
		||||
        "confirm_reset_preferences": "設定をリセットしますか?",
 | 
			
		||||
        "rename_playlist": "プレイリスト名を変更する",
 | 
			
		||||
        "piped_link": "Pipedリンク",
 | 
			
		||||
        "new_playlist_name": "新しいプレイリスト名",
 | 
			
		||||
        "follow_link": "リンクに従う",
 | 
			
		||||
        "follow_link": "リンクを開く",
 | 
			
		||||
        "reply_count": "{count} 件の返信",
 | 
			
		||||
        "clone_playlist": "プレイリストを複製",
 | 
			
		||||
        "minimize_comments_default": "デフォルトでコメントを最小化する",
 | 
			
		||||
        "no_valid_playlists": "ファイルに有効なプレイリストが含まれていません。"
 | 
			
		||||
        "clone_playlist": "再生リストを複製",
 | 
			
		||||
        "minimize_comments_default": "最初からコメントを最小化",
 | 
			
		||||
        "no_valid_playlists": "このファイルは有効な再生リストではありません!",
 | 
			
		||||
        "playlist_bookmarked": "ブックマーク完了",
 | 
			
		||||
        "bookmark_playlist": "ブックマーク",
 | 
			
		||||
        "with_playlist": "再生リストで共有",
 | 
			
		||||
        "skip_automatically": "自動",
 | 
			
		||||
        "skip_button_only": "スキップボタン表示",
 | 
			
		||||
        "skip_segment": "ここをスキップ",
 | 
			
		||||
        "min_segment_length": "最小の区切りの長さ (秒)",
 | 
			
		||||
        "show_less": "少なく見る",
 | 
			
		||||
        "autoplay_next_countdown": "次の動画の再生までの時間 (秒)",
 | 
			
		||||
        "dismiss": "却下",
 | 
			
		||||
        "create_group": "グループ作成",
 | 
			
		||||
        "group_name": "グループ名",
 | 
			
		||||
        "auto_display_captions": "自動で字幕を表示",
 | 
			
		||||
        "okay": "OK",
 | 
			
		||||
        "cancel": "キャンセル",
 | 
			
		||||
        "edit_playlist": "再生リストを編集",
 | 
			
		||||
        "playlist_name": "再生リスト名",
 | 
			
		||||
        "playlist_description": "再生リストの説明",
 | 
			
		||||
        "chapters_layout_mobile": "モバイル端末でのチャプターの配置",
 | 
			
		||||
        "show_search_suggestions": "検索語句の候補を表示",
 | 
			
		||||
        "delete_automatically": "指定時間後に自動削除",
 | 
			
		||||
        "enable_dearrow": "DeArrow を使用",
 | 
			
		||||
        "generate_qrcode": "QRコードの生成"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "{author} によって固定",
 | 
			
		||||
| 
						 | 
				
			
			@ -130,9 +154,9 @@
 | 
			
		|||
    "preferences": {
 | 
			
		||||
        "instance_name": "インスタンス名",
 | 
			
		||||
        "instance_locations": "インスタンスの場所",
 | 
			
		||||
        "has_cdn": "CDNは存在する?",
 | 
			
		||||
        "ssl_score": "SSLスコア",
 | 
			
		||||
        "registered_users": "登録済みユーザー",
 | 
			
		||||
        "has_cdn": "CDNの有無",
 | 
			
		||||
        "ssl_score": "SSLの評価",
 | 
			
		||||
        "registered_users": "登録利用者数",
 | 
			
		||||
        "version": "バージョン",
 | 
			
		||||
        "up_to_date": "最新?"
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -144,11 +168,17 @@
 | 
			
		|||
        "videos": "動画",
 | 
			
		||||
        "views": "{views} 回再生",
 | 
			
		||||
        "watched": "再生済み",
 | 
			
		||||
        "sponsor_segments": "スポンサーによる広告",
 | 
			
		||||
        "sponsor_segments": "広告シーン数",
 | 
			
		||||
        "ratings_disabled": "評価は無効化されています",
 | 
			
		||||
        "chapters": "チャプター",
 | 
			
		||||
        "live": "{0} ライブ配信",
 | 
			
		||||
        "shorts": "ショート"
 | 
			
		||||
        "shorts": "ショート",
 | 
			
		||||
        "all": "すべて",
 | 
			
		||||
        "category": "分類",
 | 
			
		||||
        "chapters_horizontal": "横方向",
 | 
			
		||||
        "chapters_vertical": "縦方向",
 | 
			
		||||
        "visibility": "公開状態",
 | 
			
		||||
        "license": "ライセンス"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "もしかして: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -159,14 +189,21 @@
 | 
			
		|||
        "music_songs": "YT Music: 音楽",
 | 
			
		||||
        "music_videos": "YT Music: 動画",
 | 
			
		||||
        "music_albums": "YT Music: アルバム",
 | 
			
		||||
        "music_playlists": "YT Music: 再生リスト"
 | 
			
		||||
        "music_playlists": "YT Music: 再生リスト",
 | 
			
		||||
        "music_artists": "YT Music: アーティスト"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "page_not_found": "ページが見つかりません",
 | 
			
		||||
        "copied": "コピーしました!",
 | 
			
		||||
        "cannot_copy": "コピーできません!",
 | 
			
		||||
        "preferences_note": "注意: 設定はブラウザのローカルストレージに保存されます。ブラウザのデータを削除するとリセットされます。",
 | 
			
		||||
        "local_storage": "この操作にはlocalStorageが必要です。Cookieは有効ですか?"
 | 
			
		||||
        "preferences_note": "注意: 設定は、お使いのブラウザの保存領域に保存されます。ブラウザのデータを削除すると初期化されます。",
 | 
			
		||||
        "local_storage": "この操作にはlocalStorageが必要です。Cookieは有効ですか?",
 | 
			
		||||
        "register_no_email_note": "ユーザー名としてのメールアドレスの使用は推奨しません。それでも続けますか?",
 | 
			
		||||
        "next_video_countdown": "{0} 秒後に次の動画",
 | 
			
		||||
        "days": "{amount}日",
 | 
			
		||||
        "weeks": "{amount}週",
 | 
			
		||||
        "hours": "{amount}時間",
 | 
			
		||||
        "months": "{amount}月"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "チャンネル登録: {0}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,14 @@
 | 
			
		|||
        "history": "Amazray",
 | 
			
		||||
        "subscriptions": "Ijerriden",
 | 
			
		||||
        "account": "Amiḍan",
 | 
			
		||||
        "channels": "Ibuda"
 | 
			
		||||
        "channels": "Ibuda",
 | 
			
		||||
        "playlists": "Tabdert n tɣuri",
 | 
			
		||||
        "instance": "Tummant",
 | 
			
		||||
        "player": "Ameɣri",
 | 
			
		||||
        "livestreams": "Azuzer usrid",
 | 
			
		||||
        "bookmarks": "Ticraḍ",
 | 
			
		||||
        "feed": "Amultaɣ",
 | 
			
		||||
        "trending": "Tiddin"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "dark": "Ubrik",
 | 
			
		||||
| 
						 | 
				
			
			@ -19,16 +26,148 @@
 | 
			
		|||
        "delete_playlist_confirm": "Kkes tabdart-a n tɣuri?",
 | 
			
		||||
        "share": "Bḍu",
 | 
			
		||||
        "documentation": "Tasemlit",
 | 
			
		||||
        "status_page": "État"
 | 
			
		||||
        "status_page": "État",
 | 
			
		||||
        "least_recent": "N melmi kna",
 | 
			
		||||
        "channel_name_desc": "Isem n ubadu (A-Z)",
 | 
			
		||||
        "channel_name_asc": "Isem n ubadu (A-z)",
 | 
			
		||||
        "back": "Tuɣalin",
 | 
			
		||||
        "uses_api_from": "Isseqdac API n: ",
 | 
			
		||||
        "enable_sponsorblock": "Rmed amsewḥal n udellel",
 | 
			
		||||
        "skip_sponsors": "Zgel adellel",
 | 
			
		||||
        "add_to_playlist": "Rnu ɣer tebdart n tɣuri",
 | 
			
		||||
        "hide_replies": "Ffer tiririyin",
 | 
			
		||||
        "load_more_replies": "Sali-d ugar n tririyin",
 | 
			
		||||
        "remove_from_playlist": "Kkes seg tebdart n tɣuri",
 | 
			
		||||
        "delete_playlist_video_confirm": "Kkes tavidyut seg tebdart n tɣuri?",
 | 
			
		||||
        "create_playlist": "Rnu tabdart n tɣuri",
 | 
			
		||||
        "subscribe": "Qqen - {count}",
 | 
			
		||||
        "unsubscribe": "Sefsex tuqqna n - {count}",
 | 
			
		||||
        "view_subscriptions": "Wali imuktaɣen",
 | 
			
		||||
        "sort_by": "Semyizwer s:",
 | 
			
		||||
        "most_recent": "Amaynut akk",
 | 
			
		||||
        "theme": "Asentel",
 | 
			
		||||
        "auto": "Awurman",
 | 
			
		||||
        "delete_playlist": "Kkes tabdart n tɣuri",
 | 
			
		||||
        "select_playlist": "Fren tabdart n tɣuri",
 | 
			
		||||
        "please_select_playlist": "Ttxil-k·m fren tabdart n tɣuri",
 | 
			
		||||
        "delete_account": "Kkes amiḍan",
 | 
			
		||||
        "logout": "Ffeɣ seg yibenk-a",
 | 
			
		||||
        "light": "D ameceɛlal",
 | 
			
		||||
        "autoplay_video": "Taɣuri tawurmant n tvidyut",
 | 
			
		||||
        "audio_only": "Ameslaw kan",
 | 
			
		||||
        "default_quality": "Taɣara tuzwirt",
 | 
			
		||||
        "auto_play_next_video": "Taɣuri tawurmant n tvidyut tuḍfirt",
 | 
			
		||||
        "donations": "Tawsa i usnefli",
 | 
			
		||||
        "disable_lbry": "Sens LBRY i usuddem",
 | 
			
		||||
        "enable_lbry_proxy": "Rmed apṛuksi i LBRY",
 | 
			
		||||
        "view_ssl_score": "Sken agmuḍ SSL",
 | 
			
		||||
        "show_recommendations": "Sken iwellihen",
 | 
			
		||||
        "clear_history": "Sfeḍ azray",
 | 
			
		||||
        "copy_link": "Nɣel aseɣwen",
 | 
			
		||||
        "time_code": "Tangalt n wakud (s tsinin)",
 | 
			
		||||
        "show_more": "Sken ugar",
 | 
			
		||||
        "export_to_json": "Sifeḍ ɣer JSON",
 | 
			
		||||
        "minimize_comments": "Semẓi iwenniten",
 | 
			
		||||
        "show_comments": "Sken iwenniten",
 | 
			
		||||
        "minimize_description": "Semẓi aglam",
 | 
			
		||||
        "show_description": "Sken aglam",
 | 
			
		||||
        "minimize_recommendations": "Semẓi iwellihen",
 | 
			
		||||
        "reply_count": "{count} tririyin",
 | 
			
		||||
        "bookmark_playlist": "Tacreḍt",
 | 
			
		||||
        "playlist_bookmarked": "Yettwacreḍ",
 | 
			
		||||
        "instances_list": "Tabdart n tummanin",
 | 
			
		||||
        "country_selection": "Afran n tmurt",
 | 
			
		||||
        "default_homepage": "Asebter agejdan amezwer",
 | 
			
		||||
        "instance_selection": "Afran n tummant",
 | 
			
		||||
        "import_from_json": "Kter seg JSON/CSV",
 | 
			
		||||
        "store_search_history": "Ḥrez azray n unadi",
 | 
			
		||||
        "instance_donations": "Tawsa n tummant",
 | 
			
		||||
        "source_code": "Tangalt taɣbalut",
 | 
			
		||||
        "minimize_description_default": "Semẓi aglam s wudem amezwer",
 | 
			
		||||
        "skip_highlight": "Zgel asebruraq",
 | 
			
		||||
        "minimize_comments_default": "Semẓi iwenniten s wudem amezwer",
 | 
			
		||||
        "store_watch_history": "Ḥrez azray n tmeẓriwt",
 | 
			
		||||
        "loop_this_video": "Err tavidyut-a d taẓayert",
 | 
			
		||||
        "minimize_recommendations_default": "Semẓi iwellihen s wudem amezwer",
 | 
			
		||||
        "no_valid_playlists": "Afaylu ulac deg-s tibdarin n tɣuri timeɣta!",
 | 
			
		||||
        "with_playlist": "Bḍu s tebdart n tɣuri",
 | 
			
		||||
        "skip_preview": "Zgel Taskant/Agzul",
 | 
			
		||||
        "skip_outro": "Zgel ismawen n taggara",
 | 
			
		||||
        "minimize_chapters_default": "Semẓi ixfawen s wudem uzwir",
 | 
			
		||||
        "confirm_reset_preferences": "D tidet tebɣiḍ ad twennzeḍ ismenyifen-ik•im?",
 | 
			
		||||
        "backup_preferences": "Ḥrez ismenyifen",
 | 
			
		||||
        "restore_preferences": "Err-d ismenyifen",
 | 
			
		||||
        "back_to_home": "Uɣal ɣer ugejdan",
 | 
			
		||||
        "follow_link": "Ḍfer aseɣwen",
 | 
			
		||||
        "show_chapters": "Ixfawen",
 | 
			
		||||
        "show_watch_on_youtube": "Sken taqeffalt Wali ɣef YouTube",
 | 
			
		||||
        "reset_preferences": "Wennez ismenyifen",
 | 
			
		||||
        "with_timecode": "Bḍu s tengalt n wakud",
 | 
			
		||||
        "hide_watched": "Ffer tividyutin yemmeẓren deg usuddel",
 | 
			
		||||
        "skip_interaction": "Zgel ismektiyen n umyigew (Multeɣ)",
 | 
			
		||||
        "enabled_codecs": "Isettengal ttwaremden (aṭas)",
 | 
			
		||||
        "buffering_goal": "Iswi n uḥraz akudan (s tsinin)",
 | 
			
		||||
        "skip_non_music": "Zgel aẓawan: tafrant ur nelli d aẓawan",
 | 
			
		||||
        "show_markers": "Sken ticraḍ ɣef umeɣri",
 | 
			
		||||
        "instance_auth_selection": "Tafrant n tummant n usesteb",
 | 
			
		||||
        "invalidate_session": "Ffeɣ seg meṛṛa ibenkan",
 | 
			
		||||
        "different_auth_instance": "Seqdec tummant tayeḍ i usesteb",
 | 
			
		||||
        "download_as_txt": "Sader am.txt",
 | 
			
		||||
        "piped_link": "Aseɣwen n Piped",
 | 
			
		||||
        "clone_playlist": "Tabdart n tɣuri yemtawan"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "version": "Lqem"
 | 
			
		||||
        "version": "Lqem",
 | 
			
		||||
        "has_cdn": "Ɣur-s CDN?",
 | 
			
		||||
        "registered_users": "Iseqdacen yettwajerrden",
 | 
			
		||||
        "up_to_date": "D amaynut?",
 | 
			
		||||
        "ssl_score": "Agmuḍ SSL",
 | 
			
		||||
        "instance_name": "Isem n tummant",
 | 
			
		||||
        "instance_locations": "Idgan n tummant"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Nom d'utilisateur",
 | 
			
		||||
        "password": "Awal n uɛeddi"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Tividyutin"
 | 
			
		||||
        "videos": "Tividyutin",
 | 
			
		||||
        "views": "{views} tmeẓriwin",
 | 
			
		||||
        "watched": "Yemẓer",
 | 
			
		||||
        "shorts": "Tiwezlanin",
 | 
			
		||||
        "live": "{0} srid",
 | 
			
		||||
        "sponsor_segments": "Inegzumen n udellel",
 | 
			
		||||
        "chapters": "Ixfawen",
 | 
			
		||||
        "ratings_disabled": "Iktazalen ttwasensen"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Wali deg {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Isenteḍ-itt {author}",
 | 
			
		||||
        "loading": "Asali n yiwenniten...",
 | 
			
		||||
        "disabled": "Ameskar issens iwenniten.",
 | 
			
		||||
        "user_disabled": "Nsan yiwenniten deg Yiɣewwaren."
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Tebɣiḍ ad d-tiniḍ: {0}?",
 | 
			
		||||
        "music_playlists": "YT Music: Tibdarin n tɣuri",
 | 
			
		||||
        "all": "YouTube: Akk",
 | 
			
		||||
        "videos": "YouTube: Tividyutin",
 | 
			
		||||
        "channels": "YouTube: Ibuda",
 | 
			
		||||
        "music_videos": "YT Music: Tividyutin",
 | 
			
		||||
        "playlists": "YouTube: Tibdarin n tɣuri",
 | 
			
		||||
        "music_songs": "YT Music: Tizlatin",
 | 
			
		||||
        "music_albums": "YT Music: Albumen"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "copied": "Yettwanɣel!",
 | 
			
		||||
        "page_not_found": "Ur yettwaf ara usebter",
 | 
			
		||||
        "cannot_copy": "D awezɣi ad d-yettwanɣel!",
 | 
			
		||||
        "register_no_email_note": "Aseqdec n yimayl am yisem n useqdac, ur yettusireg ara. Kemmel ɣas akken?",
 | 
			
		||||
        "local_storage": "Tigawt-a aḥraz adigan, inagan n tuqqna remden?",
 | 
			
		||||
        "preferences_note": "Tamawt: ismenyifen ttwaskelsen deg uklas adigan n yiminig-ik·im. Tukksa n yisefka yiminig-ik·im ad ten-iwennez."
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Imulteɣ ɣer: {0}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@
 | 
			
		|||
        "show_comments": "댓글 보이기",
 | 
			
		||||
        "unsubscribe": "구독 취소 - {count}",
 | 
			
		||||
        "view_subscriptions": "구독 보기",
 | 
			
		||||
        "least_recent": "가장 오래된",
 | 
			
		||||
        "least_recent": "오래된 순",
 | 
			
		||||
        "theme": "테마",
 | 
			
		||||
        "auto": "자동",
 | 
			
		||||
        "channel_name_desc": "채널 이름 (Z-A)",
 | 
			
		||||
| 
						 | 
				
			
			@ -26,16 +26,16 @@
 | 
			
		|||
        "no": "아니요",
 | 
			
		||||
        "loop_this_video": "이 동영상 반복",
 | 
			
		||||
        "auto_play_next_video": "다음 동영상 자동 재생",
 | 
			
		||||
        "minimize_description": "설명 최소화",
 | 
			
		||||
        "minimize_description": "설명 가리기",
 | 
			
		||||
        "show_recommendations": "추천 동영상 표시",
 | 
			
		||||
        "sort_by": "정렬:",
 | 
			
		||||
        "most_recent": "가장 최신",
 | 
			
		||||
        "most_recent": "최신 순",
 | 
			
		||||
        "channel_name_asc": "채널 이름 (A-Z)",
 | 
			
		||||
        "subscribe": "구독 - {count}",
 | 
			
		||||
        "audio_only": "오디오만",
 | 
			
		||||
        "skip_sponsors": "스폰서 스킵",
 | 
			
		||||
        "dark": "다크",
 | 
			
		||||
        "minimize_description_default": "기본적으로 설명 최소화",
 | 
			
		||||
        "minimize_description_default": "설명 가리기를 기본 설정값으로",
 | 
			
		||||
        "enabled_codecs": "활성화된 코덱 (여러 개)",
 | 
			
		||||
        "instance_selection": "인스턴스 선택",
 | 
			
		||||
        "import_from_json": "JSON/CSV에서 가져오기",
 | 
			
		||||
| 
						 | 
				
			
			@ -50,11 +50,11 @@
 | 
			
		|||
        "show_description": "설명 표시",
 | 
			
		||||
        "disable_lbry": "LBRY 스트리밍 비활성화",
 | 
			
		||||
        "enable_lbry_proxy": "LBRY 프록시 활성화",
 | 
			
		||||
        "minimize_recommendations": "추천 동영상 최소화",
 | 
			
		||||
        "minimize_recommendations": "추천 동영상 가리기",
 | 
			
		||||
        "loading": "로딩...",
 | 
			
		||||
        "view_ssl_score": "SSL 점수 보기",
 | 
			
		||||
        "skip_intro": "중간 휴식/인트로 애니메이션 스킵",
 | 
			
		||||
        "search": "검색",
 | 
			
		||||
        "search": "검색 (Ctrl+K)",
 | 
			
		||||
        "clear_history": "기록 지우기",
 | 
			
		||||
        "skip_highlight": "하이라이트 스킵",
 | 
			
		||||
        "select_playlist": "재생목록 선택",
 | 
			
		||||
| 
						 | 
				
			
			@ -69,8 +69,6 @@
 | 
			
		|||
        "logout": "이 기기에서 로그아웃",
 | 
			
		||||
        "show_chapters": "챕터",
 | 
			
		||||
        "download_as_txt": ".txt로 다운로드",
 | 
			
		||||
        "new_playlist_name": "새 재생목록 이름",
 | 
			
		||||
        "rename_playlist": "재생목록 이름 변경",
 | 
			
		||||
        "share": "공유",
 | 
			
		||||
        "copy_link": "링크 복사",
 | 
			
		||||
        "time_code": "시작 시간 (초)",
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +82,7 @@
 | 
			
		|||
        "restore_preferences": "설정 복원",
 | 
			
		||||
        "backup_preferences": "설정 백업",
 | 
			
		||||
        "back_to_home": "홈으로 가기",
 | 
			
		||||
        "minimize_recommendations_default": "기본적으로 추천 동영상 최소화",
 | 
			
		||||
        "minimize_recommendations_default": "추천 동영상 가리기를 기본 설정값으로",
 | 
			
		||||
        "invalidate_session": "모든 기기에서 로그아웃",
 | 
			
		||||
        "instance_auth_selection": "인증 인스턴스 선택",
 | 
			
		||||
        "different_auth_instance": "인증에 다른 인스턴스 사용",
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +93,17 @@
 | 
			
		|||
        "documentation": "문서",
 | 
			
		||||
        "source_code": "소스 코드",
 | 
			
		||||
        "instance_donations": "인스턴스 기부",
 | 
			
		||||
        "status_page": "상태"
 | 
			
		||||
        "status_page": "상태",
 | 
			
		||||
        "bookmark_playlist": "북마크",
 | 
			
		||||
        "dismiss": "무시",
 | 
			
		||||
        "minimize_comments_default": "댓글 가리기를 기본 설정값으로",
 | 
			
		||||
        "minimize_comments": "댓글 가리기",
 | 
			
		||||
        "show_watch_on_youtube": "‘YouTube에서 보기’ 버튼 표시",
 | 
			
		||||
        "minimize_chapters_default": "챕터 가리기를 기본 설정값으로",
 | 
			
		||||
        "autoplay_next_countdown": "다음 영상 재생까지 대기 시간(초)",
 | 
			
		||||
        "skip_button_only": "스킵 버튼 표시",
 | 
			
		||||
        "skip_automatically": "자동",
 | 
			
		||||
        "show_less": "덜 보기"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "feed": "피드",
 | 
			
		||||
| 
						 | 
				
			
			@ -108,7 +116,10 @@
 | 
			
		|||
        "playlists": "재생목록",
 | 
			
		||||
        "account": "계정",
 | 
			
		||||
        "instance": "인스턴스",
 | 
			
		||||
        "player": "플레이어"
 | 
			
		||||
        "player": "플레이어",
 | 
			
		||||
        "bookmarks": "북마크",
 | 
			
		||||
        "livestreams": "실시간",
 | 
			
		||||
        "channels": "채널"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "에서 보기 {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +151,8 @@
 | 
			
		|||
        "ratings_disabled": "등급 비활성화됨",
 | 
			
		||||
        "live": "{0} 라이브",
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "chapters": "챕터"
 | 
			
		||||
        "chapters": "챕터",
 | 
			
		||||
        "category": "카테고리"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "이것을 찾으셨나요: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,6 @@
 | 
			
		|||
        "reply_count": "{count} atsakymai",
 | 
			
		||||
        "show_chapters": "Skirsniai",
 | 
			
		||||
        "piped_link": "Piped nuoroda",
 | 
			
		||||
        "rename_playlist": "Pervardyti grojaraštį",
 | 
			
		||||
        "follow_link": "Sekti nuorodą",
 | 
			
		||||
        "store_search_history": "Išsaugoti paieškos istoriją",
 | 
			
		||||
        "hide_watched": "Slėpti žiūrėtus vaizdo įrašus sklaidos kanale",
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +87,6 @@
 | 
			
		|||
        "status_page": "Būsena",
 | 
			
		||||
        "copy_link": "Kopijuoti nuorodą",
 | 
			
		||||
        "share": "Dalintis",
 | 
			
		||||
        "new_playlist_name": "Naujas grojaraščio pavadinimas",
 | 
			
		||||
        "minimize_recommendations_default": "Sumažinti rekomendacijas automatiškai",
 | 
			
		||||
        "instance_donations": "Perdavimo šaltinio parama",
 | 
			
		||||
        "instance_auth_selection": "Autentifikavimo perdavimo šaltinio pasirinkimas",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,8 @@
 | 
			
		|||
        "feed": "ഫീഡ്",
 | 
			
		||||
        "login": "പ്രവേശിക്കുക",
 | 
			
		||||
        "subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ",
 | 
			
		||||
        "playlists": "പ്ലേലിസ്റ്റുകൾ"
 | 
			
		||||
        "playlists": "പ്ലേലിസ്റ്റുകൾ",
 | 
			
		||||
        "player": "കളിക്കാരൻ"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "view_subscriptions": "സബ്സ്ക്രിപ്ഷനുകൾ കാണുക",
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +73,8 @@
 | 
			
		|||
        "select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കൂ",
 | 
			
		||||
        "please_select_playlist": "ഒരു പ്ലേലിസ്റ്റ് തിരഞ്ഞെടുക്കുക",
 | 
			
		||||
        "delete_playlist_video_confirm": "ഈ പ്ലേലിസ്റ്റിൽ നിന്ന് ഈ വീഡിയോ ഒഴിവാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?",
 | 
			
		||||
        "delete_playlist_confirm": "ഈ പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടൊ?"
 | 
			
		||||
        "delete_playlist_confirm": "ഈ പ്ലേലിസ്റ്റ് ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടൊ?",
 | 
			
		||||
        "skip_automatically": "സ്വയമേവ"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "കാണുക {0}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,8 +81,6 @@
 | 
			
		|||
        "confirm_reset_preferences": "Tilbakestill alle innstillingene?",
 | 
			
		||||
        "restore_preferences": "Gjenopprett innstillinger",
 | 
			
		||||
        "show_chapters": "Kapitler",
 | 
			
		||||
        "rename_playlist": "Gi spillelisten ny navn",
 | 
			
		||||
        "new_playlist_name": "Nytt spillelistenavn",
 | 
			
		||||
        "share": "Del",
 | 
			
		||||
        "with_timecode": "Del med tidskode",
 | 
			
		||||
        "piped_link": "Piped-lenke",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
{
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "skip_sponsors": "Sponsors Overslaan",
 | 
			
		||||
        "skip_outro": "Eindkaarten/Credits overslaan",
 | 
			
		||||
        "skip_outro": "Eindkaarten/Credits Overslaan",
 | 
			
		||||
        "add_to_playlist": "Aan Afspeellijst Toevoegen",
 | 
			
		||||
        "sort_by": "Sorteer op:",
 | 
			
		||||
        "buffering_goal": "Bufferdoel (in seconden)",
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +10,7 @@
 | 
			
		|||
        "disable_lbry": "LBRY voor Streamen Uitschakelen",
 | 
			
		||||
        "enable_lbry_proxy": "Proxy voor LBRY Inschakelen",
 | 
			
		||||
        "view_ssl_score": "SSL-score Bekijken",
 | 
			
		||||
        "search": "Zoeken",
 | 
			
		||||
        "search": "Zoeken (Ctrl+K)",
 | 
			
		||||
        "filter": "Filter",
 | 
			
		||||
        "skip_filler_tangent": "Opvultangens Overslaan",
 | 
			
		||||
        "theme": "Thema",
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@
 | 
			
		|||
        "skip_self_promo": "Onbetaalde/Zelf-promotie Overslaan",
 | 
			
		||||
        "skip_highlight": "Markering Overslaan",
 | 
			
		||||
        "skip_interaction": "Interactieherinnering Overslaan (Abonneren)",
 | 
			
		||||
        "show_more": "Toon Meer",
 | 
			
		||||
        "show_more": "Toon meer",
 | 
			
		||||
        "unsubscribe": "Afmelden - {count}",
 | 
			
		||||
        "view_subscriptions": "Abonnementen Bekijken",
 | 
			
		||||
        "enable_sponsorblock": "Sponsorblok Inschakelen",
 | 
			
		||||
| 
						 | 
				
			
			@ -58,17 +58,17 @@
 | 
			
		|||
        "remove_from_playlist": "Uit Afspeellijst Verwijderen",
 | 
			
		||||
        "select_playlist": "Selecteer een Afspeellijst",
 | 
			
		||||
        "delete_playlist_confirm": "Deze afspeellijst verwijderen?",
 | 
			
		||||
        "please_select_playlist": "Kies een afspeellijst a.u.b.",
 | 
			
		||||
        "please_select_playlist": "Selecteer een afspeellijst alsjeblief",
 | 
			
		||||
        "instance_selection": "Instantie Selectie",
 | 
			
		||||
        "import_from_json": "Importeren uit JSON/CSV",
 | 
			
		||||
        "clear_history": "Geschiedenis Wissen",
 | 
			
		||||
        "load_more_replies": "Laad meer Antwoorden",
 | 
			
		||||
        "delete_playlist_video_confirm": "Video van playlist verwijderen?",
 | 
			
		||||
        "delete_playlist_video_confirm": "Video uit deze afspeellijst verwijderen?",
 | 
			
		||||
        "create_playlist": "Afspeellijst Maken",
 | 
			
		||||
        "delete_playlist": "Afspeellijst Verwijderen",
 | 
			
		||||
        "show_markers": "Toon markeringen op Speler",
 | 
			
		||||
        "store_search_history": "Zoekgeschiedenis opslaan",
 | 
			
		||||
        "minimize_chapters_default": "Hoofdstukken standaard minimaliseren",
 | 
			
		||||
        "show_markers": "Laat markeringen op speler zien",
 | 
			
		||||
        "store_search_history": "Zoekgeschiedenis Opslaan",
 | 
			
		||||
        "minimize_chapters_default": "Hoofdstukken Standaard Minimaliseren",
 | 
			
		||||
        "show_watch_on_youtube": "Toon Bekijk op YouTube knop",
 | 
			
		||||
        "restore_preferences": "Voorkeuren herstellen",
 | 
			
		||||
        "with_timecode": "Delen met tijdcode",
 | 
			
		||||
| 
						 | 
				
			
			@ -77,11 +77,9 @@
 | 
			
		|||
        "copy_link": "Link kopiëren",
 | 
			
		||||
        "hide_watched": "Verberg bekeken video's in de feed",
 | 
			
		||||
        "minimize_comments": "Opmerkingen minimaliseren",
 | 
			
		||||
        "instance_auth_selection": "Autenticatie Instantie Selectie",
 | 
			
		||||
        "clone_playlist": "Afspeellijst klonen",
 | 
			
		||||
        "instance_auth_selection": "Selectie authenticatie-instantie",
 | 
			
		||||
        "clone_playlist": "Afspeellijst dupliceren",
 | 
			
		||||
        "download_as_txt": "Downloaden als .txt",
 | 
			
		||||
        "rename_playlist": "Afspeellijst hernoemen",
 | 
			
		||||
        "new_playlist_name": "Nieuwe afspeellijstnaam",
 | 
			
		||||
        "share": "Delen",
 | 
			
		||||
        "documentation": "Documentatie",
 | 
			
		||||
        "status_page": "Status",
 | 
			
		||||
| 
						 | 
				
			
			@ -91,18 +89,27 @@
 | 
			
		|||
        "instance_donations": "Instantie donaties",
 | 
			
		||||
        "reply_count": "{count} antwoorden",
 | 
			
		||||
        "no_valid_playlists": "Het bestand bevat geen geldige afspeellijsten!",
 | 
			
		||||
        "clone_playlist_success": "Succesvol gekloond!",
 | 
			
		||||
        "reset_preferences": "Voorkeuren opnieuw instellen",
 | 
			
		||||
        "clone_playlist_success": "Dupliceren gelukt!",
 | 
			
		||||
        "reset_preferences": "Voorkeuren herstellen",
 | 
			
		||||
        "back_to_home": "Terug naar de start",
 | 
			
		||||
        "minimize_comments_default": "Opmerkingen standaard minimaliseren",
 | 
			
		||||
        "minimize_comments_default": "Opmerkingen Standaard Minimaliseren",
 | 
			
		||||
        "delete_account": "Account Verwijderen",
 | 
			
		||||
        "logout": "Uitloggen van dit apparaat",
 | 
			
		||||
        "minimize_recommendations_default": "Aanbevelingen standaard minimaliseren",
 | 
			
		||||
        "logout": "Uitloggen op dit apparaat",
 | 
			
		||||
        "minimize_recommendations_default": "Aanbevelingen Standaard Minimaliseren",
 | 
			
		||||
        "confirm_reset_preferences": "Weet u zeker dat u uw voorkeuren opnieuw wilt instellen?",
 | 
			
		||||
        "backup_preferences": "Back-up voorkeuren",
 | 
			
		||||
        "invalidate_session": "Alle apparaten afmelden",
 | 
			
		||||
        "invalidate_session": "Uitloggen op alle apparaten",
 | 
			
		||||
        "different_auth_instance": "Gebruik een andere instantie voor authenticatie",
 | 
			
		||||
        "with_playlist": "Delen met afspeellijst"
 | 
			
		||||
        "with_playlist": "Delen met afspeellijst",
 | 
			
		||||
        "playlist_bookmarked": "Bladwijzer gemaakt",
 | 
			
		||||
        "bookmark_playlist": "Bladwijzer",
 | 
			
		||||
        "skip_automatically": "Automatisch",
 | 
			
		||||
        "skip_button_only": "toon de overslaan knop",
 | 
			
		||||
        "min_segment_length": "Minimale segmentlengte (in seconden)",
 | 
			
		||||
        "skip_segment": "segment overslaan",
 | 
			
		||||
        "show_less": "Toon minder",
 | 
			
		||||
        "autoplay_next_countdown": "Standaard aftellen tot de volgende video (in seconden)",
 | 
			
		||||
        "dismiss": "Afwijzen"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "register": "Registreren",
 | 
			
		||||
| 
						 | 
				
			
			@ -111,13 +118,14 @@
 | 
			
		|||
        "preferences": "Voorkeuren",
 | 
			
		||||
        "history": "Geschiedenis",
 | 
			
		||||
        "subscriptions": "Abonnementen",
 | 
			
		||||
        "trending": "Trending",
 | 
			
		||||
        "trending": "populair",
 | 
			
		||||
        "playlists": "Afspeellijsten",
 | 
			
		||||
        "account": "Account",
 | 
			
		||||
        "account": "profiel",
 | 
			
		||||
        "instance": "Instantie",
 | 
			
		||||
        "player": "Speler",
 | 
			
		||||
        "livestreams": "Livestreams",
 | 
			
		||||
        "channels": "Kanalen"
 | 
			
		||||
        "channels": "Kanalen",
 | 
			
		||||
        "bookmarks": "Bladwijzers"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Kijk op {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -145,7 +153,9 @@
 | 
			
		|||
        "sponsor_segments": "Sponsorsegmenten",
 | 
			
		||||
        "ratings_disabled": "Beoordelingen Uitgeschakeld",
 | 
			
		||||
        "live": "{0} Live",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "category": "Categorie",
 | 
			
		||||
        "all": "Alle"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "has_cdn": "Heeft CDN?",
 | 
			
		||||
| 
						 | 
				
			
			@ -158,8 +168,8 @@
 | 
			
		|||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Vastgemaakt door {author}",
 | 
			
		||||
        "user_disabled": "Reacties zijn uitgeschakeld in de instellingen.",
 | 
			
		||||
        "loading": "Reacties laden...",
 | 
			
		||||
        "user_disabled": "Opmerkingen zijn uitgeschakeld in de instellingen.",
 | 
			
		||||
        "loading": "Opmerkingen laden...",
 | 
			
		||||
        "disabled": "Reacties zijn uitgeschakeld door de uploader."
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +177,9 @@
 | 
			
		|||
        "copied": "Gekopieerd!",
 | 
			
		||||
        "cannot_copy": "Kan niet kopiëren!",
 | 
			
		||||
        "page_not_found": "Pagina niet gevonden",
 | 
			
		||||
        "local_storage": "Deze actie vereist lokale opslag, zijn cookies ingeschakeld?"
 | 
			
		||||
        "local_storage": "Deze actie vereist lokale opslag, zijn cookies ingeschakeld?",
 | 
			
		||||
        "register_no_email_note": "Een e-mailadres als gebruikersnaam gebruiken wordt afgeraden. Toch doorgaan?",
 | 
			
		||||
        "next_video_countdown": "Volgende video afspelen in {0}s"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Geabonneerd op: {0}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										200
									
								
								src/locales/oc.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/locales/oc.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,200 @@
 | 
			
		|||
{
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "login": "Se connectar",
 | 
			
		||||
        "feed": "Flux",
 | 
			
		||||
        "preferences": "Preferéncias",
 | 
			
		||||
        "history": "Istoric",
 | 
			
		||||
        "account": "Compte",
 | 
			
		||||
        "instance": "Instància",
 | 
			
		||||
        "player": "Lector",
 | 
			
		||||
        "livestreams": "Dirèctes",
 | 
			
		||||
        "channels": "Canals",
 | 
			
		||||
        "trending": "Tendéncias",
 | 
			
		||||
        "register": "S’inscriure",
 | 
			
		||||
        "subscriptions": "Abonaments",
 | 
			
		||||
        "playlists": "Listas de lecturas",
 | 
			
		||||
        "bookmarks": "Marcapaginas",
 | 
			
		||||
        "channel_groups": "Grops de cadenas"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Agachar sus {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "subscribe": "S’abonar - {count}",
 | 
			
		||||
        "unsubscribe": "Se desabonar - {count}",
 | 
			
		||||
        "view_subscriptions": "Veire los abonaments",
 | 
			
		||||
        "sort_by": "Triar per :",
 | 
			
		||||
        "most_recent": "Mai recents",
 | 
			
		||||
        "least_recent": "Mens recents",
 | 
			
		||||
        "channel_name_asc": "Nom del canal (A-Z)",
 | 
			
		||||
        "channel_name_desc": "Nom del canal (Z-A)",
 | 
			
		||||
        "back": "Tornar",
 | 
			
		||||
        "skip_outro": "Passar los crèdits a la fin",
 | 
			
		||||
        "skip_preview": "Passar l’apercebut / resumit",
 | 
			
		||||
        "uses_api_from": "Utiliza l’API de ",
 | 
			
		||||
        "skip_intro": "Passar l’animacion d’entracte / Introduccion",
 | 
			
		||||
        "enable_sponsorblock": "Activar Sponsorblock",
 | 
			
		||||
        "skip_sponsors": "Passar las promocions",
 | 
			
		||||
        "show_comments": "Mostrar los comentaris",
 | 
			
		||||
        "minimize_description": "Minimizar la descripcion",
 | 
			
		||||
        "show_description": "Mostrar la descripcion",
 | 
			
		||||
        "show_recommendations": "Mostrar las recomandacions",
 | 
			
		||||
        "minimize_recommendations": "Minimizar las recomandacions",
 | 
			
		||||
        "enable_lbry_proxy": "Activar lo servidor mandatari per LBRY",
 | 
			
		||||
        "view_ssl_score": "Mostrar l’avaloracion SSL",
 | 
			
		||||
        "search": "Recercar (Ctrl+K)",
 | 
			
		||||
        "filter": "Filtrar",
 | 
			
		||||
        "disable_lbry": "Desactivar LBRY per la difusion en dirècte",
 | 
			
		||||
        "loading": "Cargament…",
 | 
			
		||||
        "clear_history": "Netejar l’istoric",
 | 
			
		||||
        "hide_replies": "Rescondre las responsas",
 | 
			
		||||
        "load_more_replies": "Cargar mai de responsas",
 | 
			
		||||
        "remove_from_playlist": "Levar de la lista de lectura",
 | 
			
		||||
        "add_to_playlist": "Apondre a la listra de lectura",
 | 
			
		||||
        "minimize_comments": "Minimizar los comentaris",
 | 
			
		||||
        "theme": "Tèma",
 | 
			
		||||
        "language_selection": "Seleccion de la lenga",
 | 
			
		||||
        "loop_this_video": "Legir en bocla la vidèo",
 | 
			
		||||
        "reset_preferences": "Restablir las preferéncias",
 | 
			
		||||
        "auto": "Auto",
 | 
			
		||||
        "dark": "Escur",
 | 
			
		||||
        "light": "Clar",
 | 
			
		||||
        "donations": "Don pel desvolopament",
 | 
			
		||||
        "backup_preferences": "Salvagardar las preferéncias",
 | 
			
		||||
        "share": "Partejar",
 | 
			
		||||
        "documentation": "Documentacion",
 | 
			
		||||
        "source_code": "Còdi font",
 | 
			
		||||
        "restore_preferences": "Restablir las preferéncias",
 | 
			
		||||
        "skip_interaction": "Ignorar los rapèls d’interaccion (S’abonar)",
 | 
			
		||||
        "show_markers": "Mostrar los marcadors sul lector",
 | 
			
		||||
        "default_quality": "Qualitat per defaut",
 | 
			
		||||
        "buffering_goal": "Objectiu de mesa en memòria tampon (en segondas)",
 | 
			
		||||
        "minimize_description_default": "Minimizar la descripcion per defaut",
 | 
			
		||||
        "instances_list": "Lista d’instàncias",
 | 
			
		||||
        "enabled_codecs": "Codecs activats (multiples)",
 | 
			
		||||
        "yes": "Òc",
 | 
			
		||||
        "no": "Non",
 | 
			
		||||
        "export_to_json": "Exportar en JSON",
 | 
			
		||||
        "import_from_json": "Importar d’un JSON/CSV",
 | 
			
		||||
        "auto_play_next_video": "Legir la vidèo seguenta automaticament",
 | 
			
		||||
        "create_playlist": "Crear una lista de lectura",
 | 
			
		||||
        "delete_playlist": "Levar de la lista de lectura",
 | 
			
		||||
        "select_playlist": "Seleccionatz una lista de lectura",
 | 
			
		||||
        "delete_playlist_confirm": "Suprimir aquesta lista de lectura ?",
 | 
			
		||||
        "clone_playlist": "Clonar la lista de lectura",
 | 
			
		||||
        "instance_auth_selection": "Seleccion de l’instància d’autentificacion",
 | 
			
		||||
        "clone_playlist_success": "Clonatge capitat !",
 | 
			
		||||
        "follow_link": "Dobrir lo ligam",
 | 
			
		||||
        "skip_self_promo": "Sautar la promocion gratuita / autopromocion",
 | 
			
		||||
        "skip_non_music": "Sautar la musica : seccion non musicala",
 | 
			
		||||
        "skip_highlight": "Ignorar los tempses fòrts",
 | 
			
		||||
        "skip_filler_tangent": "Sautar la tangenta d’emplenament",
 | 
			
		||||
        "autoplay_video": "Legir automaticament la vidèo",
 | 
			
		||||
        "audio_only": "Sonque àudio",
 | 
			
		||||
        "minimize_comments_default": "Minimizar los comentaris per defaut",
 | 
			
		||||
        "instance_selection": "Seleccion d’instàncias",
 | 
			
		||||
        "please_select_playlist": "Seleccionatz una lista de lectura",
 | 
			
		||||
        "show_watch_on_youtube": "Mostrar lo boton « Veire sus YouTube »",
 | 
			
		||||
        "invalidate_session": "Se desconnectar de totes los aparelhs",
 | 
			
		||||
        "different_auth_instance": "Utilizar una instància diferenta per l’autentificacion",
 | 
			
		||||
        "back_to_home": "Tornar a l’acuèlh",
 | 
			
		||||
        "with_timecode": "Partejar amb còdi orari",
 | 
			
		||||
        "piped_link": "Ligam cap a Piped",
 | 
			
		||||
        "show_chapters": "Capítols",
 | 
			
		||||
        "country_selection": "Seleccion del país",
 | 
			
		||||
        "default_homepage": "Pagina d’acuèlh per defaut",
 | 
			
		||||
        "minimize_recommendations_default": "Minimizar las recomandacions per defaut",
 | 
			
		||||
        "store_watch_history": "Servar l’istoric de visualizacion",
 | 
			
		||||
        "show_more": "Ne mostrar mai",
 | 
			
		||||
        "delete_playlist_video_confirm": "Levar aquesta vidèo de la lista de lectura ?",
 | 
			
		||||
        "delete_account": "Suprimir lo compte",
 | 
			
		||||
        "logout": "Se desconnectar d’aqueste aparelh",
 | 
			
		||||
        "minimize_chapters_default": "Minimizar los capítols per defaut",
 | 
			
		||||
        "download_as_txt": "Telecargar coma .txt",
 | 
			
		||||
        "copy_link": "Copiar lo ligam",
 | 
			
		||||
        "time_code": "Moment (en segondas)",
 | 
			
		||||
        "store_search_history": "Gardar l’istoric de recèrca",
 | 
			
		||||
        "confirm_reset_preferences": "Volètz vertadièrament reïnicializar las preferéncias ?",
 | 
			
		||||
        "hide_watched": "Rescondre las vidèos vistas al flux",
 | 
			
		||||
        "reply_count": "{count} responsas",
 | 
			
		||||
        "with_playlist": "Partejar amb una lista de lectura",
 | 
			
		||||
        "playlist_bookmarked": "Marcat",
 | 
			
		||||
        "bookmark_playlist": "Marcapagina",
 | 
			
		||||
        "status_page": "Estat",
 | 
			
		||||
        "no_valid_playlists": "Lo fichièr conten pas cap de lista de lectura valida !",
 | 
			
		||||
        "instance_donations": "Dons d’instància",
 | 
			
		||||
        "skip_button_only": "Afichar lo boton per sautar",
 | 
			
		||||
        "skip_automatically": "Automaticament",
 | 
			
		||||
        "min_segment_length": "Durada minimum de segment (en segondas)",
 | 
			
		||||
        "skip_segment": "Sautar lo segment",
 | 
			
		||||
        "show_less": "Ne mostrar mens",
 | 
			
		||||
        "autoplay_next_countdown": "Descompte per defaut abans de passar a la vidèo seguenta (en segondas)",
 | 
			
		||||
        "dismiss": "Ignorar",
 | 
			
		||||
        "create_group": "Crear un grop",
 | 
			
		||||
        "group_name": "Nom del grop",
 | 
			
		||||
        "cancel": "Anullar",
 | 
			
		||||
        "okay": "D’acòrd",
 | 
			
		||||
        "edit_playlist": "Modificar la lista de lectura",
 | 
			
		||||
        "playlist_name": "Nom de la lista de lectura",
 | 
			
		||||
        "playlist_description": "Descripcion de la lista de lectura",
 | 
			
		||||
        "auto_display_captions": "Afichatge auto dels sostítols",
 | 
			
		||||
        "show_search_suggestions": "Mostrar las suggestions de recèrcas",
 | 
			
		||||
        "chapters_layout_mobile": "Disposicion capítols sus mobil"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_locations": "Localizacion de l’instància",
 | 
			
		||||
        "registered_users": "Utilizaires inscriches",
 | 
			
		||||
        "instance_name": "Nom de l’instància",
 | 
			
		||||
        "has_cdn": "A un CDN ?",
 | 
			
		||||
        "version": "Version",
 | 
			
		||||
        "up_to_date": "Actualizat ?",
 | 
			
		||||
        "ssl_score": "Marca SSL"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Nom d’utilizaire",
 | 
			
		||||
        "password": "Senhal"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "views": "{views} visualizacions",
 | 
			
		||||
        "watched": "Vista",
 | 
			
		||||
        "ratings_disabled": "Avaloracions desactivadas",
 | 
			
		||||
        "sponsor_segments": "Segments de sponsors",
 | 
			
		||||
        "live": "{0} en dirècte",
 | 
			
		||||
        "shorts": "Corts",
 | 
			
		||||
        "chapters": "Capítols",
 | 
			
		||||
        "videos": "Vidèos",
 | 
			
		||||
        "all": "Totas",
 | 
			
		||||
        "category": "Categoria",
 | 
			
		||||
        "chapters_horizontal": "Orizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "Nòta : las preferéncias son gardadas dins l’espaci d’emmagazinatge del navegador. La supression de las donadas del navegador las restablirà.",
 | 
			
		||||
        "copied": "Copiat !",
 | 
			
		||||
        "page_not_found": "Pagina pas trobada",
 | 
			
		||||
        "cannot_copy": "Còpia impossibla !",
 | 
			
		||||
        "local_storage": "Aquesta accion requerís lo localStorage, son activats los cookies ?",
 | 
			
		||||
        "register_no_email_note": "Es pas recomandat d’utilizar una adreça electronica coma nom d’utilizaire. Contunhar çaquelà ?",
 | 
			
		||||
        "next_video_countdown": "La vidèo seguenta començarà d’aquí {0}s"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "disabled": "L’autor a desactivat los comentaris.",
 | 
			
		||||
        "loading": "Cargament dels comentaris…",
 | 
			
		||||
        "user_disabled": "Los comentaris son desactivats als paramètres.",
 | 
			
		||||
        "pinned_by": "Penjat per {author}"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Voliatz dire : {0} ?",
 | 
			
		||||
        "channels": "YouTube : Canals",
 | 
			
		||||
        "videos": "YouTube : Vidèos",
 | 
			
		||||
        "all": "YouTube : Tot",
 | 
			
		||||
        "playlists": "YouTube : Listas de lectura",
 | 
			
		||||
        "music_songs": "YT Music : Cançons",
 | 
			
		||||
        "music_videos": "YT Music : Vidèos",
 | 
			
		||||
        "music_albums": "YT Music : Albums",
 | 
			
		||||
        "music_playlists": "YT Music : Listas de lectura"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Abonat a : {0}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,9 @@
 | 
			
		|||
        "account": "ଆକାଉଣ୍ଟ",
 | 
			
		||||
        "player": "ପ୍ଲେୟାର",
 | 
			
		||||
        "livestreams": "ସିଧାପ୍ରସାରଣ ଗୁଡ଼ିକ",
 | 
			
		||||
        "channels": "ସ୍ରୋତ ଗୁଡ଼ିକ"
 | 
			
		||||
        "channels": "ସ୍ରୋତ ଗୁଡ଼ିକ",
 | 
			
		||||
        "bookmarks": "ବୁକମାର୍କଗୁଡିକ",
 | 
			
		||||
        "channel_groups": "ଚ୍ୟାନେଲ୍ ଗୋଷ୍ଠୀଗୁଡିକ"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "{0} ରେ ଦେଖନ୍ତୁ"
 | 
			
		||||
| 
						 | 
				
			
			@ -32,9 +34,7 @@
 | 
			
		|||
        "minimize_recommendations": "ସୁପାରିଶକୁ କମ୍ କରନ୍ତୁ",
 | 
			
		||||
        "show_recommendations": "ସୁପାରିଶଗୁଡିକ ଦେଖାନ୍ତୁ",
 | 
			
		||||
        "disable_lbry": "ଷ୍ଟ୍ରିମିଂ ପାଇଁ LBRY ଅକ୍ଷମ କରନ୍ତୁ",
 | 
			
		||||
        "search": "ସନ୍ଧାନ କରନ୍ତୁ",
 | 
			
		||||
        "rename_playlist": "ପ୍ଲେ ଲିଷ୍ଟର ନାମ ପରିବର୍ତ୍ତନ କରନ୍ତୁ",
 | 
			
		||||
        "new_playlist_name": "ନୂତନ ପ୍ଲେଲିଷ୍ଟ ନାମ",
 | 
			
		||||
        "search": "ସନ୍ଧାନ କରନ୍ତୁ (Ctrl+K)",
 | 
			
		||||
        "channel_name_asc": "ସ୍ରୋତ ର ନାମ (A-Z)",
 | 
			
		||||
        "least_recent": "ସର୍ବନିମ୍ନ ସାମ୍ପ୍ରତିକ",
 | 
			
		||||
        "channel_name_desc": "ସ୍ରୋତ ର ନାମ (Z-A)",
 | 
			
		||||
| 
						 | 
				
			
			@ -119,7 +119,19 @@
 | 
			
		|||
        "documentation": "ଡକ୍ୟୁମେଣ୍ଟେସନ୍",
 | 
			
		||||
        "instance_donations": "ଇନଷ୍ଟାଣ୍ଟ ଦାନ ଗୁଡ଼ିକ",
 | 
			
		||||
        "minimize_chapters_default": "ଡିଫଲ୍ଟ ଭାବରେ ଅଧ୍ୟାୟଗୁଡ଼ିକୁ କମ୍ କରନ୍ତୁ",
 | 
			
		||||
        "no_valid_playlists": "ଫାଇଲ୍ ଟି ବୈଧ ପ୍ଲେଲିଷ୍ଟ ଧାରଣ କରେ ନାହିଁ!"
 | 
			
		||||
        "no_valid_playlists": "ଫାଇଲ୍ ଟି ବୈଧ ପ୍ଲେଲିଷ୍ଟ ଧାରଣ କରେ ନାହିଁ!",
 | 
			
		||||
        "with_playlist": "ପ୍ଲେଲିଷ୍ଟ ସହିତ ଅଂଶୀଦାର କରନ୍ତୁ",
 | 
			
		||||
        "bookmark_playlist": "ବୁକମାର୍କ",
 | 
			
		||||
        "playlist_bookmarked": "ବୁକମାର୍କ ହୋଇଛି",
 | 
			
		||||
        "min_segment_length": "ସର୍ବନିମ୍ନ ସେଗମେଣ୍ଟ ଲମ୍ବ (ସେକେଣ୍ଡରେ)",
 | 
			
		||||
        "skip_button_only": "ସ୍କିପ୍ ବଟନ୍ ଦେଖାନ୍ତୁ",
 | 
			
		||||
        "skip_automatically": "ସ୍ୱୟଂଚାଳିତ ଭାବରେ",
 | 
			
		||||
        "skip_segment": "ସେଗମେଣ୍ଟକୁ ଏଡ଼ାଇଦିଅ",
 | 
			
		||||
        "show_less": "କମ୍ ଦେଖାନ୍ତୁ",
 | 
			
		||||
        "autoplay_next_countdown": "ପରବର୍ତ୍ତୀ ଭିଡିଓ ପର୍ଯ୍ୟନ୍ତ ଡିଫଲ୍ଟ କାଉଣ୍ଟଡାଉନ୍ (ସେକେଣ୍ଡରେ)",
 | 
			
		||||
        "dismiss": "ବରଖାସ୍ତ",
 | 
			
		||||
        "create_group": "ଗୋଷ୍ଠୀ ସୃଷ୍ଟି କରନ୍ତୁ",
 | 
			
		||||
        "group_name": "ଗୋଷ୍ଠୀ ନାମ"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "loading": "ମନ୍ତବ୍ୟ ଲୋଡ୍ ହେଉଛି ...",
 | 
			
		||||
| 
						 | 
				
			
			@ -135,7 +147,9 @@
 | 
			
		|||
        "videos": "ଭିଡିଓ ଗୁଡିକ",
 | 
			
		||||
        "ratings_disabled": "ମୂଲ୍ୟାୟନ ଅକ୍ଷମ ହୋଇଛି",
 | 
			
		||||
        "chapters": "ଅଧ୍ୟାୟ ଗୁଡ଼ିକ",
 | 
			
		||||
        "live": "{0} ସିଧାପ୍ରସାରଣ"
 | 
			
		||||
        "live": "{0} ସିଧାପ୍ରସାରଣ",
 | 
			
		||||
        "all": "ସମସ୍ତ",
 | 
			
		||||
        "category": "ବର୍ଗ"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "ଆପଣ କହିବାକୁ ଚାହୁଁଛନ୍ତି କି: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +170,9 @@
 | 
			
		|||
        "copied": "କପି ହୋଇଛି!",
 | 
			
		||||
        "cannot_copy": "କପି କରିପାରିବ ନାହିଁ!",
 | 
			
		||||
        "page_not_found": "ପୃଷ୍ଠାଟି ମିଳିଲା ନାହିଁ",
 | 
			
		||||
        "local_storage": "ଏହି କ୍ରିୟା ଲୋକାଲ୍ ଷ୍ଟୋରେଜ୍ ଆବଶ୍ୟକ କରେ, କୁକିଜ୍ ସକ୍ଷମ ଅଛି କି?"
 | 
			
		||||
        "local_storage": "ଏହି କ୍ରିୟା ଲୋକାଲ୍ ଷ୍ଟୋରେଜ୍ ଆବଶ୍ୟକ କରେ, କୁକିଜ୍ ସକ୍ଷମ ଅଛି କି?",
 | 
			
		||||
        "register_no_email_note": "ଉପଯୋଗକର୍ତ୍ତା ନାମ ଭାବରେ ଏକ ଇ-ମେଲ୍ ବ୍ୟବହାର କରିବା ସୁପାରିଶ କରାଯାଏ ନାହିଁ । ଯେକୌଣସି ପ୍ରକାରେ ଅଗ୍ରଗତି କରନ୍ତୁ?",
 | 
			
		||||
        "next_video_countdown": "{0} ସେକେଣ୍ଡ ରେ ପରବର୍ତ୍ତୀ ଭିଡିଓ ଖେଳିବାକୁ ଆରମ୍ଭ ହେବ"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_name": "ଇନଷ୍ଟାନ୍ସ ନାମ",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,10 @@
 | 
			
		|||
        "account": "Konto",
 | 
			
		||||
        "instance": "Instancja",
 | 
			
		||||
        "livestreams": "Na żywo",
 | 
			
		||||
        "channels": "Kanały"
 | 
			
		||||
        "channels": "Kanały",
 | 
			
		||||
        "bookmarks": "Zakładki",
 | 
			
		||||
        "channel_groups": "Grupy kanałów",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Obejrzyj na {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -46,15 +49,15 @@
 | 
			
		|||
        "audio_only": "Tylko audio",
 | 
			
		||||
        "default_quality": "Domyślna jakość",
 | 
			
		||||
        "buffering_goal": "Cel buforowania (w sekundach)",
 | 
			
		||||
        "country_selection": "Wybór kraju",
 | 
			
		||||
        "country_selection": "Kraj",
 | 
			
		||||
        "default_homepage": "Domyślna strona główna",
 | 
			
		||||
        "show_comments": "Pokaż komentarze",
 | 
			
		||||
        "minimize_description_default": "Ukryj opis",
 | 
			
		||||
        "store_watch_history": "Zapamiętaj historię oglądania",
 | 
			
		||||
        "language_selection": "Wybór języka",
 | 
			
		||||
        "language_selection": "Język",
 | 
			
		||||
        "instances_list": "Lista instancji",
 | 
			
		||||
        "enabled_codecs": "Włączone kodeki (lista wielokrotnego wyboru)",
 | 
			
		||||
        "instance_selection": "Wybór instancji",
 | 
			
		||||
        "instance_selection": "Instancja",
 | 
			
		||||
        "show_more": "Pokaż więcej",
 | 
			
		||||
        "yes": "Tak",
 | 
			
		||||
        "no": "Nie",
 | 
			
		||||
| 
						 | 
				
			
			@ -67,10 +70,10 @@
 | 
			
		|||
        "show_description": "Pokaż opis",
 | 
			
		||||
        "minimize_recommendations": "Ukryj proponowane",
 | 
			
		||||
        "show_recommendations": "Pokaż proponowane",
 | 
			
		||||
        "disable_lbry": "Wyłącz LBRY dla streaming-u",
 | 
			
		||||
        "disable_lbry": "Wyłącz LBRY dla przesyłania strumieniowego",
 | 
			
		||||
        "enable_lbry_proxy": "Włącz proxy dla LBRY",
 | 
			
		||||
        "view_ssl_score": "Pokaż ocenę SSL",
 | 
			
		||||
        "search": "Szukaj",
 | 
			
		||||
        "search": "Szukaj (Ctrl+K)",
 | 
			
		||||
        "filter": "Filtruj",
 | 
			
		||||
        "loading": "Ładowanie...",
 | 
			
		||||
        "clear_history": "Wyczyść historię",
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +95,7 @@
 | 
			
		|||
        "documentation": "Dokumentacja",
 | 
			
		||||
        "instance_donations": "Darowizny na rzecz instancji",
 | 
			
		||||
        "back_to_home": "Idź do strony głównej",
 | 
			
		||||
        "instance_auth_selection": "Wybrana instancja autoryzacyjna",
 | 
			
		||||
        "instance_auth_selection": "Instancja uwierzytelniania",
 | 
			
		||||
        "time_code": "Kod czasowy (w sekundach)",
 | 
			
		||||
        "show_markers": "Pokaż segmenty na odtwarzaczu",
 | 
			
		||||
        "store_search_history": "Zapamiętaj historię wyszukiwania",
 | 
			
		||||
| 
						 | 
				
			
			@ -100,7 +103,6 @@
 | 
			
		|||
        "source_code": "Kod źródłowy",
 | 
			
		||||
        "show_chapters": "Rozdziały",
 | 
			
		||||
        "minimize_chapters_default": "Ukryj rozdziały",
 | 
			
		||||
        "rename_playlist": "Zmień nazwę playlisty",
 | 
			
		||||
        "follow_link": "Otwórz link",
 | 
			
		||||
        "minimize_comments_default": "Ukryj sekcję komentarzy",
 | 
			
		||||
        "minimize_comments": "Ukryj komentarze",
 | 
			
		||||
| 
						 | 
				
			
			@ -113,14 +115,35 @@
 | 
			
		|||
        "backup_preferences": "Pobierz kopię zapasową ustawień",
 | 
			
		||||
        "download_as_txt": "Pobierz jako .txt",
 | 
			
		||||
        "reset_preferences": "Zresetuj ustawienia",
 | 
			
		||||
        "new_playlist_name": "Nowa nazwa playlisty",
 | 
			
		||||
        "share": "Udostępnij",
 | 
			
		||||
        "with_timecode": "Udostępnij z kodem czasowym",
 | 
			
		||||
        "piped_link": "Link Piped",
 | 
			
		||||
        "status_page": "Status",
 | 
			
		||||
        "reply_count": "{count} odpowiedzi",
 | 
			
		||||
        "no_valid_playlists": "Ten plik nie zawiera poprawnych playlist!",
 | 
			
		||||
        "with_playlist": "Udostępnij z playlistą"
 | 
			
		||||
        "with_playlist": "Udostępnij z playlistą",
 | 
			
		||||
        "playlist_bookmarked": "Dodano do zakładek",
 | 
			
		||||
        "bookmark_playlist": "Zakładka",
 | 
			
		||||
        "skip_button_only": "Pokaż przycisk pomijania",
 | 
			
		||||
        "skip_automatically": "Automatycznie",
 | 
			
		||||
        "min_segment_length": "Minimalna długość segmentu (w sekundach)",
 | 
			
		||||
        "skip_segment": "Pomiń segment",
 | 
			
		||||
        "show_less": "Pokaż mniej",
 | 
			
		||||
        "autoplay_next_countdown": "Domyślne odliczanie do następnego filmu (w sekundach)",
 | 
			
		||||
        "dismiss": "Odrzuć",
 | 
			
		||||
        "group_name": "Nazwa grupy",
 | 
			
		||||
        "create_group": "Utwórz grupę",
 | 
			
		||||
        "auto_display_captions": "Automatyczne wyświetlanie napisów",
 | 
			
		||||
        "edit_playlist": "Edytuj playlistę",
 | 
			
		||||
        "playlist_name": "Nazwa playlisty",
 | 
			
		||||
        "playlist_description": "Opis playlisty",
 | 
			
		||||
        "okay": "OK",
 | 
			
		||||
        "cancel": "Anuluj",
 | 
			
		||||
        "show_search_suggestions": "Pokaż sugestie wyszukiwania",
 | 
			
		||||
        "chapters_layout_mobile": "Układ rozdziałów na urządzeniach mobilnych",
 | 
			
		||||
        "delete_automatically": "Usuń automatycznie po",
 | 
			
		||||
        "enable_dearrow": "Włącz DeArrow",
 | 
			
		||||
        "generate_qrcode": "Wygeneruj kod QR"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Przypięty przez {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -139,7 +162,9 @@
 | 
			
		|||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Nazwa użytkownika",
 | 
			
		||||
        "password": "Hasło"
 | 
			
		||||
        "password": "Hasło",
 | 
			
		||||
        "password_confirm": "Potwierdź hasło",
 | 
			
		||||
        "passwords_incorrect": "Hasła się nie zgadzają!"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Filmy",
 | 
			
		||||
| 
						 | 
				
			
			@ -148,8 +173,14 @@
 | 
			
		|||
        "sponsor_segments": "Segmenty sponsorowane",
 | 
			
		||||
        "ratings_disabled": "Ocenianie wyłączone",
 | 
			
		||||
        "chapters": "Rozdziały",
 | 
			
		||||
        "live": "{0} Na żywo",
 | 
			
		||||
        "shorts": "Krótkie wideo"
 | 
			
		||||
        "live": "{0} na żywo",
 | 
			
		||||
        "shorts": "Krótkie wideo",
 | 
			
		||||
        "all": "Wszystkie",
 | 
			
		||||
        "category": "Kategoria",
 | 
			
		||||
        "chapters_horizontal": "Poziomy",
 | 
			
		||||
        "chapters_vertical": "Pionowy",
 | 
			
		||||
        "license": "Licencja",
 | 
			
		||||
        "visibility": "Widoczność"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Czy chodziło ci o: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,14 +191,21 @@
 | 
			
		|||
        "music_songs": "YT Music: Utwory",
 | 
			
		||||
        "music_videos": "YT Music: Teledyski",
 | 
			
		||||
        "music_albums": "YT Music: Albumy",
 | 
			
		||||
        "music_playlists": "YT Music: Playlisty"
 | 
			
		||||
        "music_playlists": "YT Music: Playlisty",
 | 
			
		||||
        "music_artists": "YT Music: Wykonawcy"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "cannot_copy": "Nie można skopiować!",
 | 
			
		||||
        "copied": "Skopiowano!",
 | 
			
		||||
        "page_not_found": "Strona nie znaleziona",
 | 
			
		||||
        "preferences_note": "Uwaga: ustawienia są zapisywane w lokalnej pamięci przeglądarki. Usunięcie danych przeglądarki spowoduje ich zresetowanie.",
 | 
			
		||||
        "local_storage": "Ta akcja wymaga dostępu do lokalnej pamięci, czy pliki cookie są włączone?"
 | 
			
		||||
        "local_storage": "Ta akcja wymaga dostępu do lokalnej pamięci, czy pliki cookie są włączone?",
 | 
			
		||||
        "register_no_email_note": "Użycie adresu email jako nazwy użytkownika jest niezalecane. Kontynuować mimo to?",
 | 
			
		||||
        "next_video_countdown": "Odtwarzanie następnego filmu za {0} s",
 | 
			
		||||
        "days": "{amount} dni",
 | 
			
		||||
        "weeks": "{amount} tygodnie",
 | 
			
		||||
        "hours": "{amount} godziny",
 | 
			
		||||
        "months": "{amount} miesiące"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Licznik subskrybcji: {0}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,130 +1,154 @@
 | 
			
		|||
{
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "trending": "Tendências",
 | 
			
		||||
        "preferences": "Configurações",
 | 
			
		||||
        "preferences": "Preferências",
 | 
			
		||||
        "subscriptions": "Subscrições",
 | 
			
		||||
        "login": "Iniciar Sessão",
 | 
			
		||||
        "login": "Iniciar sessão",
 | 
			
		||||
        "register": "Registar",
 | 
			
		||||
        "history": "Histórico",
 | 
			
		||||
        "feed": "Conteúdo",
 | 
			
		||||
        "playlists": "Listas de Reprodução",
 | 
			
		||||
        "playlists": "Listas de reprodução",
 | 
			
		||||
        "account": "Conta",
 | 
			
		||||
        "instance": "Instância",
 | 
			
		||||
        "player": "Reprodutor",
 | 
			
		||||
        "livestreams": "Transmissões ao vivo",
 | 
			
		||||
        "channels": "Canais"
 | 
			
		||||
        "livestreams": "Emissões em direto",
 | 
			
		||||
        "channels": "Canais",
 | 
			
		||||
        "bookmarks": "Marcadores",
 | 
			
		||||
        "channel_groups": "Grupos de canais",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "sort_by": "Ordenar por:",
 | 
			
		||||
        "most_recent": "Mais Recente",
 | 
			
		||||
        "least_recent": "Menos Recente",
 | 
			
		||||
        "channel_name_asc": "Nome do Canal (A-Z)",
 | 
			
		||||
        "back": "Voltar",
 | 
			
		||||
        "most_recent": "Mais recentes",
 | 
			
		||||
        "least_recent": "Mais antigos",
 | 
			
		||||
        "channel_name_asc": "Nome do canal (A-Z)",
 | 
			
		||||
        "back": "Recuar",
 | 
			
		||||
        "uses_api_from": "Utiliza a \"API\" de ",
 | 
			
		||||
        "enable_sponsorblock": "Ativar \"SponsorBlock\"",
 | 
			
		||||
        "skip_intro": "Saltar Intermissão/Animação de Introdução",
 | 
			
		||||
        "skip_outro": "Saltar \"Endcards\"/Créditos",
 | 
			
		||||
        "skip_preview": "Saltar Pré-Visualização/Recapitulação",
 | 
			
		||||
        "skip_intro": "Ignorar intermissão/animação de introdução",
 | 
			
		||||
        "skip_outro": "Ignorar \"Endcards\"/Créditos",
 | 
			
		||||
        "skip_preview": "Ignorar pré-visualização/recapitulando",
 | 
			
		||||
        "auto": "Automático",
 | 
			
		||||
        "dark": "Escuro",
 | 
			
		||||
        "autoplay_video": "Reproduzir Vídeo Automaticamente",
 | 
			
		||||
        "audio_only": "Apenas Áudio",
 | 
			
		||||
        "default_quality": "Qualidade Padrão",
 | 
			
		||||
        "country_selection": "Seleção de País",
 | 
			
		||||
        "default_homepage": "Página Inicial Padrão",
 | 
			
		||||
        "show_comments": "Mostrar Comentários",
 | 
			
		||||
        "minimize_description_default": "Minimizar Descrição por defeito",
 | 
			
		||||
        "store_watch_history": "Guardar Histórico de Visualizações",
 | 
			
		||||
        "instances_list": "Lista de Instâncias",
 | 
			
		||||
        "enabled_codecs": "\"Codecs\" Activados (Vários)",
 | 
			
		||||
        "instance_selection": "Seleção de Instância",
 | 
			
		||||
        "show_more": "Mostrar Mais",
 | 
			
		||||
        "autoplay_video": "Reproduzir vídeos automaticamente",
 | 
			
		||||
        "audio_only": "Apenas áudio",
 | 
			
		||||
        "default_quality": "Qualidade padrão",
 | 
			
		||||
        "country_selection": "País",
 | 
			
		||||
        "default_homepage": "Página inicial padrão",
 | 
			
		||||
        "show_comments": "Mostrar comentários",
 | 
			
		||||
        "minimize_description_default": "Minimizar descrição por omissão",
 | 
			
		||||
        "store_watch_history": "Guardar histórico de visualizações",
 | 
			
		||||
        "instances_list": "Lista de instâncias",
 | 
			
		||||
        "enabled_codecs": "Codificadores ativados (vários)",
 | 
			
		||||
        "instance_selection": "Instância",
 | 
			
		||||
        "show_more": "Mostrar mais",
 | 
			
		||||
        "import_from_json": "Importar de JSON/CSV",
 | 
			
		||||
        "export_to_json": "Exportar para JSON",
 | 
			
		||||
        "loop_this_video": "Repetir este Vídeo",
 | 
			
		||||
        "auto_play_next_video": "Reproduzir Automaticamente o próximo Vídeo",
 | 
			
		||||
        "donations": "Doações de desenvolvimento",
 | 
			
		||||
        "minimize_description": "Minimizar Descrição",
 | 
			
		||||
        "show_description": "Mostrar Descrição",
 | 
			
		||||
        "show_recommendations": "Mostrar Recomendações",
 | 
			
		||||
        "disable_lbry": "Desactivar \"LBRY\" para Transmissão",
 | 
			
		||||
        "enable_lbry_proxy": "Activar \"Proxy\" para \"LBRY\"",
 | 
			
		||||
        "view_ssl_score": "Ver Pontuação \"SSL\"",
 | 
			
		||||
        "search": "Procurar",
 | 
			
		||||
        "loop_this_video": "Repetir este vídeo",
 | 
			
		||||
        "auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
 | 
			
		||||
        "donations": "Doações para o desenvolvimento",
 | 
			
		||||
        "minimize_description": "Minimizar descrição",
 | 
			
		||||
        "show_description": "Mostrar descrição",
 | 
			
		||||
        "show_recommendations": "Mostrar recomendações",
 | 
			
		||||
        "disable_lbry": "Desativar \"LBRY\" para emissões",
 | 
			
		||||
        "enable_lbry_proxy": "Ativar proxy para \"LBRY\"",
 | 
			
		||||
        "view_ssl_score": "Ver avaliação \"SSL\"",
 | 
			
		||||
        "search": "Pesquisa (Ctrl+K)",
 | 
			
		||||
        "filter": "Filtrar",
 | 
			
		||||
        "loading": "A Carregar...",
 | 
			
		||||
        "clear_history": "Limpar Histórico",
 | 
			
		||||
        "loading": "A carregar...",
 | 
			
		||||
        "clear_history": "Limpar histórico",
 | 
			
		||||
        "subscribe": "Subscrever - {count}",
 | 
			
		||||
        "unsubscribe": "Anular subscrição - {count}",
 | 
			
		||||
        "view_subscriptions": "Ver Subscrições",
 | 
			
		||||
        "channel_name_desc": "Nome do Canal (Z-A)",
 | 
			
		||||
        "skip_sponsors": "Saltar Patrocínios",
 | 
			
		||||
        "view_subscriptions": "Ver subscrições",
 | 
			
		||||
        "channel_name_desc": "Nome do canal (Z-A)",
 | 
			
		||||
        "skip_sponsors": "Ignorar patrocínios",
 | 
			
		||||
        "yes": "Sim",
 | 
			
		||||
        "skip_non_music": "Saltar Música: Secção Não-Musical",
 | 
			
		||||
        "skip_non_music": "Ignorar música: secção não musical",
 | 
			
		||||
        "no": "Não",
 | 
			
		||||
        "theme": "Tema",
 | 
			
		||||
        "language_selection": "Seleção de Idioma",
 | 
			
		||||
        "minimize_recommendations": "Minimizar Recomendações",
 | 
			
		||||
        "language_selection": "Idioma",
 | 
			
		||||
        "minimize_recommendations": "Minimizar recomendações",
 | 
			
		||||
        "light": "Claro",
 | 
			
		||||
        "hide_replies": "Ocultar Respostas",
 | 
			
		||||
        "load_more_replies": "Carregar mais Respostas",
 | 
			
		||||
        "skip_highlight": "Saltar Destaque",
 | 
			
		||||
        "skip_interaction": "Saltar Lembrete de Interação (Subscreve)",
 | 
			
		||||
        "skip_self_promo": "Saltar Promoção Não Paga/Auto-Promoção",
 | 
			
		||||
        "buffering_goal": "Objetivo de \"Buffering\" (em segundos)",
 | 
			
		||||
        "skip_filler_tangent": "Saltar Tangente \"Filler\"",
 | 
			
		||||
        "hide_replies": "Ocultar respostas",
 | 
			
		||||
        "load_more_replies": "Carregar mais respostas",
 | 
			
		||||
        "skip_highlight": "Ignorar destaques",
 | 
			
		||||
        "skip_interaction": "Ignorar lembrete de interação (subscrição)",
 | 
			
		||||
        "skip_self_promo": "Ignorar promoção gratuita/auto-promoção",
 | 
			
		||||
        "buffering_goal": "Objetivo de 'buffer' (em segundos)",
 | 
			
		||||
        "skip_filler_tangent": "Ignorar segmentos de preenchimento",
 | 
			
		||||
        "add_to_playlist": "Adicionar à lista de reprodução",
 | 
			
		||||
        "delete_playlist": "Apagar Lista de Reprodução",
 | 
			
		||||
        "select_playlist": "Selecionar uma Lista de Reprodução",
 | 
			
		||||
        "delete_playlist": "Apagar lista de reprodução",
 | 
			
		||||
        "select_playlist": "Selecionar uma lista de reprodução",
 | 
			
		||||
        "delete_playlist_confirm": "Apagar esta lista de reprodução?",
 | 
			
		||||
        "please_select_playlist": "Selecionar uma lista de reprodução se faz favor",
 | 
			
		||||
        "delete_playlist_video_confirm": "Remover o vídeo da lista de reprodução?",
 | 
			
		||||
        "please_select_playlist": "Selecione uma lista de reprodução",
 | 
			
		||||
        "delete_playlist_video_confirm": "Remover vídeo da lista de reprodução?",
 | 
			
		||||
        "remove_from_playlist": "Remover da lista de reprodução",
 | 
			
		||||
        "create_playlist": "Criar Lista de Reprodução",
 | 
			
		||||
        "create_playlist": "Criar lista de reprodução",
 | 
			
		||||
        "clone_playlist_success": "Clonada com sucesso!",
 | 
			
		||||
        "clone_playlist": "Clonar Lista de Reprodução",
 | 
			
		||||
        "show_markers": "Mostrar Marcadores no Leitor",
 | 
			
		||||
        "delete_account": "Apagar Conta",
 | 
			
		||||
        "logout": "Terminar sessão neste aparelho",
 | 
			
		||||
        "minimize_recommendations_default": "Minimizar Recomendações por defeito",
 | 
			
		||||
        "invalidate_session": "Terminar sessão em todos os aparelhos",
 | 
			
		||||
        "clone_playlist": "Clonar lista de reprodução",
 | 
			
		||||
        "show_markers": "Mostrar marcas no reprodutor",
 | 
			
		||||
        "delete_account": "Apagar conta",
 | 
			
		||||
        "logout": "Terminar sessão neste dispositivo",
 | 
			
		||||
        "minimize_recommendations_default": "Minimizar recomendações por omissão",
 | 
			
		||||
        "invalidate_session": "Terminar sessão em todos os dispositivos",
 | 
			
		||||
        "different_auth_instance": "Usar uma instância diferente para autenticação",
 | 
			
		||||
        "instance_auth_selection": "Selecção da Instância para Autenticação",
 | 
			
		||||
        "confirm_reset_preferences": "Tem a certeza que quer redefinir as suas configurações?",
 | 
			
		||||
        "instance_auth_selection": "Instância para autenticação",
 | 
			
		||||
        "confirm_reset_preferences": "Tem a certeza de que deseja repor as preferências?",
 | 
			
		||||
        "download_as_txt": "Descarregar como .txt",
 | 
			
		||||
        "reset_preferences": "Redefinir preferências",
 | 
			
		||||
        "restore_preferences": "Restaurar configurações",
 | 
			
		||||
        "reset_preferences": "Repor preferências",
 | 
			
		||||
        "restore_preferences": "Restaurar preferências",
 | 
			
		||||
        "follow_link": "Seguir ligação",
 | 
			
		||||
        "piped_link": "Ligação do Piped",
 | 
			
		||||
        "backup_preferences": "Exportar configurações",
 | 
			
		||||
        "store_search_history": "Armazenar Histórico de Pesquisa",
 | 
			
		||||
        "hide_watched": "Ocultar vídeos assistidos no feed",
 | 
			
		||||
        "backup_preferences": "Exportar preferências",
 | 
			
		||||
        "store_search_history": "Guardar histórico de pesquisas",
 | 
			
		||||
        "hide_watched": "Ocultar do feed os vídeos visualizados",
 | 
			
		||||
        "documentation": "Documentação",
 | 
			
		||||
        "status_page": "Estado",
 | 
			
		||||
        "source_code": "Código-fonte",
 | 
			
		||||
        "instance_donations": "Doações de instâncias",
 | 
			
		||||
        "minimize_chapters_default": "Minimizar Capítulos por padrão",
 | 
			
		||||
        "show_watch_on_youtube": "Mostrar Botão Assistir no YouTube",
 | 
			
		||||
        "new_playlist_name": "Novo nome da lista de reprodução",
 | 
			
		||||
        "minimize_comments": "Minimizar Comentários",
 | 
			
		||||
        "minimize_chapters_default": "Minimizar capítulos por omissão",
 | 
			
		||||
        "show_watch_on_youtube": "Mostrar botão Ver no YouTube",
 | 
			
		||||
        "minimize_comments": "Minimizar comentários",
 | 
			
		||||
        "back_to_home": "Voltar ao início",
 | 
			
		||||
        "rename_playlist": "Renomear",
 | 
			
		||||
        "copy_link": "Copiar ligação",
 | 
			
		||||
        "time_code": "Código de tempo (em segundos)",
 | 
			
		||||
        "minimize_comments_default": "Minimizar Comentários por defeito",
 | 
			
		||||
        "minimize_comments_default": "Minimizar comentários por omissão",
 | 
			
		||||
        "share": "Partilhar",
 | 
			
		||||
        "with_timecode": "Partilhar com código de tempo",
 | 
			
		||||
        "show_chapters": "Capítulos",
 | 
			
		||||
        "reply_count": "{count} respostas",
 | 
			
		||||
        "no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!"
 | 
			
		||||
        "no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!",
 | 
			
		||||
        "with_playlist": "Partilhar com lista de reprodução",
 | 
			
		||||
        "playlist_bookmarked": "Marcado",
 | 
			
		||||
        "bookmark_playlist": "Marcador",
 | 
			
		||||
        "skip_button_only": "Mostrar botão Ignorar",
 | 
			
		||||
        "skip_automatically": "Automaticamente",
 | 
			
		||||
        "min_segment_length": "Tamanho mínimo do segmento (segundos)",
 | 
			
		||||
        "skip_segment": "Ignorar segmento",
 | 
			
		||||
        "show_less": "Mostrar menos",
 | 
			
		||||
        "dismiss": "Ignorar",
 | 
			
		||||
        "autoplay_next_countdown": "Contagem decrescente até ao próximo vídeo (em segundos)",
 | 
			
		||||
        "create_group": "Criar grupo",
 | 
			
		||||
        "group_name": "Nome do grupo",
 | 
			
		||||
        "auto_display_captions": "Mostrar legendas",
 | 
			
		||||
        "playlist_description": "Descrição da lista de reprodução",
 | 
			
		||||
        "okay": "Ok",
 | 
			
		||||
        "cancel": "Cancelar",
 | 
			
		||||
        "edit_playlist": "Editar lista de reprodução",
 | 
			
		||||
        "playlist_name": "Nome da lista de reprodução",
 | 
			
		||||
        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
			
		||||
        "chapters_layout_mobile": "Aplicações recentemente adicionadas",
 | 
			
		||||
        "enable_dearrow": "Ativar o DeArrow",
 | 
			
		||||
        "delete_automatically": "Eliminar automaticamente após",
 | 
			
		||||
        "generate_qrcode": "Gerar código QR"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_name": "Nome da Instância",
 | 
			
		||||
        "instance_locations": "Localizações da Instância",
 | 
			
		||||
        "ssl_score": "Pontuação \"SSL\"",
 | 
			
		||||
        "has_cdn": "Tem \"CDN\"?",
 | 
			
		||||
        "instance_name": "Nome da instância",
 | 
			
		||||
        "instance_locations": "Localizações da instância",
 | 
			
		||||
        "ssl_score": "Avaliação SSL",
 | 
			
		||||
        "has_cdn": "Tem CDN?",
 | 
			
		||||
        "version": "Versão",
 | 
			
		||||
        "registered_users": "Utilizadores Registados",
 | 
			
		||||
        "registered_users": "Utilizadores registados",
 | 
			
		||||
        "up_to_date": "Atualizada?"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
| 
						 | 
				
			
			@ -135,40 +159,53 @@
 | 
			
		|||
        "videos": "Vídeos",
 | 
			
		||||
        "views": "{views} visualizações",
 | 
			
		||||
        "watched": "Visto",
 | 
			
		||||
        "sponsor_segments": "Segmentos Patrocinados",
 | 
			
		||||
        "ratings_disabled": "Classificações Desactivadas",
 | 
			
		||||
        "sponsor_segments": "Segmentos patrocinados",
 | 
			
		||||
        "ratings_disabled": "Avaliações desativadas",
 | 
			
		||||
        "chapters": "Capítulos",
 | 
			
		||||
        "live": "{0} em Direto",
 | 
			
		||||
        "shorts": "\"Shorts\""
 | 
			
		||||
        "live": "{0} em direto",
 | 
			
		||||
        "shorts": "Curtos",
 | 
			
		||||
        "all": "Todos",
 | 
			
		||||
        "category": "Categoria",
 | 
			
		||||
        "chapters_horizontal": "Horizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical",
 | 
			
		||||
        "license": "Licença",
 | 
			
		||||
        "visibility": "Visibilidade"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Será que querias dizer: {0}?",
 | 
			
		||||
        "did_you_mean": "Será que queria dizer: {0}?",
 | 
			
		||||
        "all": "YouTube: Tudo",
 | 
			
		||||
        "videos": "YouTube: Vídeos",
 | 
			
		||||
        "channels": "YouTube: Canais",
 | 
			
		||||
        "music_songs": "YT Music: Músicas",
 | 
			
		||||
        "music_videos": "YT Music: Vídeos",
 | 
			
		||||
        "music_albums": "YT Music: Álbuns",
 | 
			
		||||
        "music_playlists": "YT Music: Listas de Reprodução",
 | 
			
		||||
        "playlists": "YouTube: Listas de Reprodução"
 | 
			
		||||
        "music_playlists": "YT Music: Listas de reprodução",
 | 
			
		||||
        "playlists": "YouTube: Listas de reprodução",
 | 
			
		||||
        "music_artists": "YT Music: Artistas"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Ver em {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Afixado por {author}",
 | 
			
		||||
        "disabled": "Os comentários estão desactivados pelo dono do canal.",
 | 
			
		||||
        "disabled": "O dono do canal desativou os comentários.",
 | 
			
		||||
        "loading": "A carregar comentários...",
 | 
			
		||||
        "user_disabled": "Os comentários estão desactivados nas definições."
 | 
			
		||||
        "user_disabled": "Os comentários estão desativados nas definições."
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Subscrito a: {0}"
 | 
			
		||||
        "subscribed_channels_count": "Subscreveu: {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "copied": "Copiada!",
 | 
			
		||||
        "cannot_copy": "Não foi possível copiar!",
 | 
			
		||||
        "page_not_found": "Página não encontrada",
 | 
			
		||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
			
		||||
        "preferences_note": "Nota: as configurações são guardadas no armazenamento local to seu navegador. Eliminar os dados de navegação irá redefini-las."
 | 
			
		||||
        "preferences_note": "Nota: as preferências são guardadas no armazenamento local do seu navegador. Se limpar os dados de navegação, também limpa as preferências.",
 | 
			
		||||
        "register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
 | 
			
		||||
        "next_video_countdown": "O próximo vídeo será reproduzido dentro de {0} segundos",
 | 
			
		||||
        "hours": "{quantidade} hora(s)",
 | 
			
		||||
        "days": "{quantidade} dia(s)",
 | 
			
		||||
        "weeks": "{quantidade} semana(s)",
 | 
			
		||||
        "months": "{quantidade} mês(es)"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@
 | 
			
		|||
        "dark": "Escuro",
 | 
			
		||||
        "light": "Claro",
 | 
			
		||||
        "show_comments": "Exibir Comentários",
 | 
			
		||||
        "country_selection": "Seleção de País",
 | 
			
		||||
        "country_selection": "País",
 | 
			
		||||
        "default_homepage": "Página Inicial Padrão",
 | 
			
		||||
        "default_quality": "Qualidade Padrão",
 | 
			
		||||
        "autoplay_video": "Reprodução Automática",
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +34,7 @@
 | 
			
		|||
        "skip_non_music": "Pular Música: Seção não Musical",
 | 
			
		||||
        "skip_filler_tangent": "Pular Enchimento Tangencial",
 | 
			
		||||
        "enabled_codecs": "Codecs Ativados (Múltiplos)",
 | 
			
		||||
        "language_selection": "Seleção de Idioma",
 | 
			
		||||
        "language_selection": "Idioma",
 | 
			
		||||
        "yes": "Sim",
 | 
			
		||||
        "show_more": "Mostrar Mais",
 | 
			
		||||
        "export_to_json": "Exportar para JSON",
 | 
			
		||||
| 
						 | 
				
			
			@ -59,10 +59,10 @@
 | 
			
		|||
        "loop_this_video": "Repetir este Vídeo",
 | 
			
		||||
        "instances_list": "Lista de Instâncias",
 | 
			
		||||
        "clear_history": "Limpar Histórico",
 | 
			
		||||
        "search": "Pesquisar",
 | 
			
		||||
        "search": "Pesquisar (Ctrl+K)",
 | 
			
		||||
        "no": "Não",
 | 
			
		||||
        "show_description": "Exibir Descrição",
 | 
			
		||||
        "instance_selection": "Seleção de Instância",
 | 
			
		||||
        "instance_selection": "Instância",
 | 
			
		||||
        "auto_play_next_video": "Autorreproduzir Próximo Vídeo",
 | 
			
		||||
        "filter": "Filtro",
 | 
			
		||||
        "store_watch_history": "Salvar Histórico de Exibição",
 | 
			
		||||
| 
						 | 
				
			
			@ -81,14 +81,12 @@
 | 
			
		|||
        "status_page": "Estado",
 | 
			
		||||
        "source_code": "Código fonte",
 | 
			
		||||
        "instance_donations": "Doações de instâncias",
 | 
			
		||||
        "instance_auth_selection": "Seleção de Instância de Autenticação",
 | 
			
		||||
        "instance_auth_selection": "Instância de Autenticação",
 | 
			
		||||
        "clone_playlist_success": "Clonada com sucesso!",
 | 
			
		||||
        "download_as_txt": "Baixar como .txt",
 | 
			
		||||
        "restore_preferences": "Restaurar preferências",
 | 
			
		||||
        "back_to_home": "Voltar ao início",
 | 
			
		||||
        "share": "Compartilhar",
 | 
			
		||||
        "rename_playlist": "Renomear playlist",
 | 
			
		||||
        "new_playlist_name": "Novo nome da playlist",
 | 
			
		||||
        "with_timecode": "Compartilhar com código de tempo",
 | 
			
		||||
        "piped_link": "Link do Piped",
 | 
			
		||||
        "follow_link": "Seguir link",
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +100,29 @@
 | 
			
		|||
        "show_watch_on_youtube": "Mostrar Botão Assistir no YouTube",
 | 
			
		||||
        "minimize_chapters_default": "Minimizar Capítulos por padrão",
 | 
			
		||||
        "no_valid_playlists": "O arquivo não contém playlists válidas!",
 | 
			
		||||
        "with_playlist": "Compartilhar com playlist"
 | 
			
		||||
        "with_playlist": "Compartilhar com playlist",
 | 
			
		||||
        "bookmark_playlist": "Favorito",
 | 
			
		||||
        "playlist_bookmarked": "Favoritado",
 | 
			
		||||
        "skip_automatically": "Automaticamente",
 | 
			
		||||
        "skip_segment": "Ignorar Segmento",
 | 
			
		||||
        "min_segment_length": "Comprimento Mínimo do Segmento (em segundos)",
 | 
			
		||||
        "skip_button_only": "Mostrar botão pular",
 | 
			
		||||
        "show_less": "Mostrar menos",
 | 
			
		||||
        "autoplay_next_countdown": "Contagem regressiva padrão até o próximo vídeo (em segundos)",
 | 
			
		||||
        "dismiss": "Liberar",
 | 
			
		||||
        "cancel": "Cancelar",
 | 
			
		||||
        "edit_playlist": "Editar playlist",
 | 
			
		||||
        "playlist_description": "Descrição da playlist",
 | 
			
		||||
        "okay": "Ok",
 | 
			
		||||
        "playlist_name": "Nome da playlist",
 | 
			
		||||
        "auto_display_captions": "Exibição Automática de Legendas",
 | 
			
		||||
        "create_group": "Criar grupo",
 | 
			
		||||
        "group_name": "Nome do grupo",
 | 
			
		||||
        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
			
		||||
        "chapters_layout_mobile": "Layout dos Capítulos no Celular",
 | 
			
		||||
        "delete_automatically": "Deletar automaticamente após",
 | 
			
		||||
        "generate_qrcode": "Gerar código QR",
 | 
			
		||||
        "enable_dearrow": "Ativar DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "history": "Histórico",
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +137,10 @@
 | 
			
		|||
        "player": "Player",
 | 
			
		||||
        "account": "Conta",
 | 
			
		||||
        "channels": "Canais",
 | 
			
		||||
        "livestreams": "Transmissões ao vivo"
 | 
			
		||||
        "livestreams": "Transmissões ao vivo",
 | 
			
		||||
        "bookmarks": "Favoritos",
 | 
			
		||||
        "channel_groups": "Grupos de Canais",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Assistir no {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +172,13 @@
 | 
			
		|||
        "watched": "Assistido",
 | 
			
		||||
        "ratings_disabled": "Avaliações Desativadas",
 | 
			
		||||
        "sponsor_segments": "Segmentos de Patrocinadores",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Todos",
 | 
			
		||||
        "category": "Categoria",
 | 
			
		||||
        "chapters_horizontal": "Horizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical",
 | 
			
		||||
        "license": "Licença",
 | 
			
		||||
        "visibility": "Visibilidade"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Você quis dizer: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,14 +189,21 @@
 | 
			
		|||
        "music_videos": "YT Music: Vídeos",
 | 
			
		||||
        "music_albums": "YT Music: Álbuns",
 | 
			
		||||
        "music_playlists": "YT Music: Playlists",
 | 
			
		||||
        "all": "YouTube: Tudo"
 | 
			
		||||
        "all": "YouTube: Tudo",
 | 
			
		||||
        "music_artists": "YT Music: Artistas"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "copied": "Copiado!",
 | 
			
		||||
        "cannot_copy": "Não foi possível copiar!",
 | 
			
		||||
        "preferences_note": "Nota: as preferências são salvas no armazenamento local do seu navegador. A exclusão dos dados do seu navegador irá redefini-los.",
 | 
			
		||||
        "page_not_found": "página não encontrada",
 | 
			
		||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?"
 | 
			
		||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
			
		||||
        "register_no_email_note": "Usar um e-mail como nome de usuário não é recomendado. Continuar mesmo assim?",
 | 
			
		||||
        "next_video_countdown": "Reproduzindo o próximo vídeo em {0}s",
 | 
			
		||||
        "hours": "{amount} hora(s)",
 | 
			
		||||
        "days": "{amount} dia(s)",
 | 
			
		||||
        "weeks": "{amount} semana(s)",
 | 
			
		||||
        "months": "{amount} mês/meses"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Inscrito em: {0}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,135 +1,158 @@
 | 
			
		|||
{
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "preferences": "Configurações",
 | 
			
		||||
        "preferences": "Preferências",
 | 
			
		||||
        "history": "Histórico",
 | 
			
		||||
        "feed": "Conteúdo",
 | 
			
		||||
        "subscriptions": "Subscrições",
 | 
			
		||||
        "trending": "Tendências",
 | 
			
		||||
        "login": "Iniciar Sessão",
 | 
			
		||||
        "login": "Iniciar sessão",
 | 
			
		||||
        "register": "Registar",
 | 
			
		||||
        "playlists": "Listas de Reprodução",
 | 
			
		||||
        "playlists": "Listas de reprodução",
 | 
			
		||||
        "account": "Conta",
 | 
			
		||||
        "instance": "Instância",
 | 
			
		||||
        "player": "Reprodutor",
 | 
			
		||||
        "livestreams": "Transmissões ao vivo",
 | 
			
		||||
        "channels": "Canais"
 | 
			
		||||
        "livestreams": "Emissões em direto",
 | 
			
		||||
        "channels": "Canais",
 | 
			
		||||
        "bookmarks": "Marcadores",
 | 
			
		||||
        "channel_groups": "Grupos de canais",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "view_subscriptions": "Ver Subscrições",
 | 
			
		||||
        "view_subscriptions": "Ver subscrições",
 | 
			
		||||
        "sort_by": "Ordenar por:",
 | 
			
		||||
        "most_recent": "Mais Recente",
 | 
			
		||||
        "least_recent": "Menos Recente",
 | 
			
		||||
        "channel_name_asc": "Nome do Canal (A-Z)",
 | 
			
		||||
        "channel_name_desc": "Nome do Canal (Z-A)",
 | 
			
		||||
        "most_recent": "Mais recentes",
 | 
			
		||||
        "least_recent": "Mais antigos",
 | 
			
		||||
        "channel_name_asc": "Nome do canal (A-Z)",
 | 
			
		||||
        "channel_name_desc": "Nome do canal (Z-A)",
 | 
			
		||||
        "uses_api_from": "Utiliza a \"API\" de ",
 | 
			
		||||
        "enable_sponsorblock": "Ativar \"SponsorBlock\"",
 | 
			
		||||
        "skip_sponsors": "Saltar Patrocínios",
 | 
			
		||||
        "skip_intro": "Saltar Intermissão/Animação de Introdução",
 | 
			
		||||
        "skip_outro": "Saltar \"Endcards\"/Créditos",
 | 
			
		||||
        "skip_preview": "Saltar Pré-Visualização/Recapitulação",
 | 
			
		||||
        "skip_interaction": "Saltar Lembrete de Interação (Subscreve)",
 | 
			
		||||
        "skip_self_promo": "Saltar Promoção Não Paga/Auto-Promoção",
 | 
			
		||||
        "skip_non_music": "Saltar Música: Secção Não-Musical",
 | 
			
		||||
        "skip_highlight": "Saltar Destaque",
 | 
			
		||||
        "skip_filler_tangent": "Saltar Tangente \"Filler\"",
 | 
			
		||||
        "skip_sponsors": "Ignorar patrocínios",
 | 
			
		||||
        "skip_intro": "Ignorar intermissão/animação de introdução",
 | 
			
		||||
        "skip_outro": "Ignorar \"Endcards\"/Créditos",
 | 
			
		||||
        "skip_preview": "Ignorar pré-visualização/recapitulando",
 | 
			
		||||
        "skip_interaction": "Ignorar lembrete de interação (subscrição)",
 | 
			
		||||
        "skip_self_promo": "Ignorar promoção gratuita/auto-promoção",
 | 
			
		||||
        "skip_non_music": "Ignorar música: secção não musical",
 | 
			
		||||
        "skip_highlight": "Ignorar destaques",
 | 
			
		||||
        "skip_filler_tangent": "Ignorar segmentos de preenchimento",
 | 
			
		||||
        "theme": "Tema",
 | 
			
		||||
        "auto": "Automático",
 | 
			
		||||
        "dark": "Escuro",
 | 
			
		||||
        "light": "Claro",
 | 
			
		||||
        "buffering_goal": "Objetivo de \"Buffering\" (em segundos)",
 | 
			
		||||
        "country_selection": "Seleção de País",
 | 
			
		||||
        "default_homepage": "Página Inicial Padrão",
 | 
			
		||||
        "show_comments": "Mostrar Comentários",
 | 
			
		||||
        "minimize_description_default": "Minimizar Descrição por defeito",
 | 
			
		||||
        "store_watch_history": "Guardar Histórico de Visualizações",
 | 
			
		||||
        "language_selection": "Seleção de Idioma",
 | 
			
		||||
        "enabled_codecs": "\"Codecs\" Activados (Vários)",
 | 
			
		||||
        "instance_selection": "Seleção de Instância",
 | 
			
		||||
        "show_more": "Mostrar Mais",
 | 
			
		||||
        "buffering_goal": "Objetivo de 'buffer' (em segundos)",
 | 
			
		||||
        "country_selection": "País",
 | 
			
		||||
        "default_homepage": "Página inicial padrão",
 | 
			
		||||
        "show_comments": "Mostrar comentários",
 | 
			
		||||
        "minimize_description_default": "Minimizar descrição por omissão",
 | 
			
		||||
        "store_watch_history": "Guardar histórico de visualizações",
 | 
			
		||||
        "language_selection": "Idioma",
 | 
			
		||||
        "enabled_codecs": "Codificadores ativados (vários)",
 | 
			
		||||
        "instance_selection": "Instância",
 | 
			
		||||
        "show_more": "Mostrar mais",
 | 
			
		||||
        "import_from_json": "Importar de JSON/CSV",
 | 
			
		||||
        "loop_this_video": "Repetir este Vídeo",
 | 
			
		||||
        "auto_play_next_video": "Reproduzir Automaticamente o próximo Vídeo",
 | 
			
		||||
        "donations": "Doações de desenvolvimento",
 | 
			
		||||
        "minimize_description": "Minimizar Descrição",
 | 
			
		||||
        "show_description": "Mostrar Descrição",
 | 
			
		||||
        "minimize_recommendations": "Minimizar Recomendações",
 | 
			
		||||
        "show_recommendations": "Mostrar Recomendações",
 | 
			
		||||
        "view_ssl_score": "Ver Pontuação \"SSL\"",
 | 
			
		||||
        "search": "Procurar",
 | 
			
		||||
        "hide_replies": "Ocultar Respostas",
 | 
			
		||||
        "load_more_replies": "Carregar mais Respostas",
 | 
			
		||||
        "loop_this_video": "Repetir este vídeo",
 | 
			
		||||
        "auto_play_next_video": "Reproduzir vídeo seguinte automaticamente",
 | 
			
		||||
        "donations": "Doações para o desenvolvimento",
 | 
			
		||||
        "minimize_description": "Minimizar descrição",
 | 
			
		||||
        "show_description": "Mostrar descrição",
 | 
			
		||||
        "minimize_recommendations": "Minimizar recomendações",
 | 
			
		||||
        "show_recommendations": "Mostrar recomendações",
 | 
			
		||||
        "view_ssl_score": "Ver avaliação \"SSL\"",
 | 
			
		||||
        "search": "Pesquisa (Ctrl+K)",
 | 
			
		||||
        "hide_replies": "Ocultar respostas",
 | 
			
		||||
        "load_more_replies": "Carregar mais respostas",
 | 
			
		||||
        "unsubscribe": "Anular subscrição - {count}",
 | 
			
		||||
        "subscribe": "Subscrever - {count}",
 | 
			
		||||
        "back": "Voltar",
 | 
			
		||||
        "audio_only": "Apenas Áudio",
 | 
			
		||||
        "default_quality": "Qualidade Padrão",
 | 
			
		||||
        "instances_list": "Lista de Instâncias",
 | 
			
		||||
        "back": "Recuar",
 | 
			
		||||
        "audio_only": "Apenas áudio",
 | 
			
		||||
        "default_quality": "Qualidade padrão",
 | 
			
		||||
        "instances_list": "Lista de instâncias",
 | 
			
		||||
        "export_to_json": "Exportar para JSON",
 | 
			
		||||
        "autoplay_video": "Reproduzir Vídeo Automaticamente",
 | 
			
		||||
        "autoplay_video": "Reproduzir vídeos automaticamente",
 | 
			
		||||
        "yes": "Sim",
 | 
			
		||||
        "enable_lbry_proxy": "Activar \"Proxy\" para \"LBRY\"",
 | 
			
		||||
        "enable_lbry_proxy": "Ativar proxy para \"LBRY\"",
 | 
			
		||||
        "no": "Não",
 | 
			
		||||
        "filter": "Filtrar",
 | 
			
		||||
        "clear_history": "Limpar Histórico",
 | 
			
		||||
        "disable_lbry": "Desactivar \"LBRY\" para Transmissão",
 | 
			
		||||
        "loading": "A Carregar...",
 | 
			
		||||
        "please_select_playlist": "Selecionar uma lista de reprodução se faz favor",
 | 
			
		||||
        "select_playlist": "Selecionar uma Lista de Reprodução",
 | 
			
		||||
        "clear_history": "Limpar histórico",
 | 
			
		||||
        "disable_lbry": "Desativar \"LBRY\" para emissões",
 | 
			
		||||
        "loading": "A carregar...",
 | 
			
		||||
        "please_select_playlist": "Selecione uma lista de reprodução",
 | 
			
		||||
        "select_playlist": "Selecionar uma lista de reprodução",
 | 
			
		||||
        "add_to_playlist": "Adicionar à lista de reprodução",
 | 
			
		||||
        "delete_playlist": "Apagar Lista de Reprodução",
 | 
			
		||||
        "delete_playlist": "Apagar lista de reprodução",
 | 
			
		||||
        "download_as_txt": "Descarregar como .txt",
 | 
			
		||||
        "delete_playlist_confirm": "Apagar esta lista de reprodução?",
 | 
			
		||||
        "show_markers": "Mostrar Marcadores no Leitor",
 | 
			
		||||
        "show_markers": "Mostrar marcas no reprodutor",
 | 
			
		||||
        "remove_from_playlist": "Remover da lista de reprodução",
 | 
			
		||||
        "delete_playlist_video_confirm": "Remover o vídeo da lista de reprodução?",
 | 
			
		||||
        "create_playlist": "Criar Lista de Reprodução",
 | 
			
		||||
        "delete_account": "Apagar Conta",
 | 
			
		||||
        "logout": "Terminar sessão neste aparelho",
 | 
			
		||||
        "minimize_recommendations_default": "Minimizar Recomendações por defeito",
 | 
			
		||||
        "delete_playlist_video_confirm": "Remover vídeo da lista de reprodução?",
 | 
			
		||||
        "create_playlist": "Criar lista de reprodução",
 | 
			
		||||
        "delete_account": "Apagar conta",
 | 
			
		||||
        "logout": "Terminar sessão neste dispositivo",
 | 
			
		||||
        "minimize_recommendations_default": "Minimizar recomendações por omissão",
 | 
			
		||||
        "different_auth_instance": "Usar uma instância diferente para autenticação",
 | 
			
		||||
        "instance_auth_selection": "Selecção da Instância para Autenticação",
 | 
			
		||||
        "invalidate_session": "Terminar sessão em todos os aparelhos",
 | 
			
		||||
        "clone_playlist": "Clonar Lista de Reprodução",
 | 
			
		||||
        "instance_auth_selection": "Instância de autenticação",
 | 
			
		||||
        "invalidate_session": "Terminar sessão em todos os dispositivos",
 | 
			
		||||
        "clone_playlist": "Clonar lista de reprodução",
 | 
			
		||||
        "clone_playlist_success": "Clonada com sucesso!",
 | 
			
		||||
        "rename_playlist": "Renomear",
 | 
			
		||||
        "restore_preferences": "Restaurar configurações",
 | 
			
		||||
        "confirm_reset_preferences": "Tem a certeza que quer redefinir as suas configurações?",
 | 
			
		||||
        "new_playlist_name": "Novo nome da lista de reprodução",
 | 
			
		||||
        "restore_preferences": "Restaurar preferências",
 | 
			
		||||
        "confirm_reset_preferences": "Tem a certeza de que deseja repor as preferências?",
 | 
			
		||||
        "share": "Partilhar",
 | 
			
		||||
        "with_timecode": "Partilhar com código de tempo",
 | 
			
		||||
        "piped_link": "Ligação do Piped",
 | 
			
		||||
        "follow_link": "Seguir ligação",
 | 
			
		||||
        "copy_link": "Copiar ligação",
 | 
			
		||||
        "time_code": "Código de tempo (em segundos)",
 | 
			
		||||
        "reset_preferences": "Redefinir preferências",
 | 
			
		||||
        "backup_preferences": "Exportar configurações",
 | 
			
		||||
        "reset_preferences": "Repor preferências",
 | 
			
		||||
        "backup_preferences": "Exportar preferências",
 | 
			
		||||
        "back_to_home": "Voltar ao início",
 | 
			
		||||
        "minimize_comments_default": "Minimizar Comentários por defeito",
 | 
			
		||||
        "store_search_history": "Armazenar Histórico de Pesquisa",
 | 
			
		||||
        "minimize_chapters_default": "Minimizar Capítulos por padrão",
 | 
			
		||||
        "show_watch_on_youtube": "Mostrar Botão Assistir no YouTube",
 | 
			
		||||
        "minimize_comments_default": "Minimizar comentários por omissão",
 | 
			
		||||
        "store_search_history": "Histórico de pesquisa da loja",
 | 
			
		||||
        "minimize_chapters_default": "Minimizar capítulos por omissão",
 | 
			
		||||
        "show_watch_on_youtube": "Mostrar botão Ver no YouTube",
 | 
			
		||||
        "show_chapters": "Capítulos",
 | 
			
		||||
        "hide_watched": "Ocultar vídeos assistidos no feed",
 | 
			
		||||
        "hide_watched": "Ocultar do feed os vídeos visualizados",
 | 
			
		||||
        "documentation": "Documentação",
 | 
			
		||||
        "status_page": "Estado",
 | 
			
		||||
        "minimize_comments": "Minimizar Comentários",
 | 
			
		||||
        "minimize_comments": "Minimizar comentários",
 | 
			
		||||
        "reply_count": "{count} respostas",
 | 
			
		||||
        "source_code": "Código-fonte",
 | 
			
		||||
        "instance_donations": "Doações de instâncias",
 | 
			
		||||
        "no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!"
 | 
			
		||||
        "no_valid_playlists": "O ficheiro não contém listas de reprodução válidas!",
 | 
			
		||||
        "bookmark_playlist": "Marcador",
 | 
			
		||||
        "playlist_bookmarked": "Marcado",
 | 
			
		||||
        "with_playlist": "Partilhar com lista de reprodução",
 | 
			
		||||
        "skip_button_only": "Mostrar botão Ignorar",
 | 
			
		||||
        "skip_automatically": "Automaticamente",
 | 
			
		||||
        "min_segment_length": "Tamanho mínimo do segmento (segundos)",
 | 
			
		||||
        "skip_segment": "Ignorar segmento",
 | 
			
		||||
        "show_less": "Mostrar menos",
 | 
			
		||||
        "autoplay_next_countdown": "Contagem decrescente até ao próximo vídeo (em segundos)",
 | 
			
		||||
        "dismiss": "Ignorar",
 | 
			
		||||
        "create_group": "Criar grupo",
 | 
			
		||||
        "group_name": "Nome do grupo",
 | 
			
		||||
        "auto_display_captions": "Mostrar legendas",
 | 
			
		||||
        "playlist_description": "Descrição da lista de reprodução",
 | 
			
		||||
        "edit_playlist": "Editar lista de reprodução",
 | 
			
		||||
        "playlist_name": "Nome da lista de reprodução",
 | 
			
		||||
        "cancel": "Cancelar",
 | 
			
		||||
        "okay": "Ok",
 | 
			
		||||
        "show_search_suggestions": "Mostrar sugestões de pesquisa",
 | 
			
		||||
        "chapters_layout_mobile": "Aplicações recentemente adicionadas",
 | 
			
		||||
        "enable_dearrow": "Ativar o DeArrow",
 | 
			
		||||
        "delete_automatically": "Eliminar automaticamente após"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Afixado por {author}",
 | 
			
		||||
        "disabled": "Os comentários estão desactivados pelo dono do canal.",
 | 
			
		||||
        "user_disabled": "Os comentários estão desactivados nas definições.",
 | 
			
		||||
        "disabled": "O dono do canal desativou os comentários.",
 | 
			
		||||
        "user_disabled": "Os comentários estão desativados nas definições.",
 | 
			
		||||
        "loading": "A carregar comentários..."
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_name": "Nome da Instância",
 | 
			
		||||
        "instance_locations": "Localizações da Instância",
 | 
			
		||||
        "has_cdn": "Tem \"CDN\"?",
 | 
			
		||||
        "registered_users": "Utilizadores Registados",
 | 
			
		||||
        "ssl_score": "Pontuação \"SSL\"",
 | 
			
		||||
        "instance_name": "Nome da instância",
 | 
			
		||||
        "instance_locations": "Localizações da instância",
 | 
			
		||||
        "has_cdn": "Tem CDN?",
 | 
			
		||||
        "registered_users": "Utilizadores registados",
 | 
			
		||||
        "ssl_score": "Avaliação SSL",
 | 
			
		||||
        "up_to_date": "Atualizada?",
 | 
			
		||||
        "version": "Versão"
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -141,34 +164,47 @@
 | 
			
		|||
        "videos": "Vídeos",
 | 
			
		||||
        "views": "{views} visualizações",
 | 
			
		||||
        "watched": "Visto",
 | 
			
		||||
        "sponsor_segments": "Segmentos Patrocinados",
 | 
			
		||||
        "ratings_disabled": "Classificações Desactivadas",
 | 
			
		||||
        "sponsor_segments": "Segmentos patrocinados",
 | 
			
		||||
        "ratings_disabled": "Avaliações desativadas",
 | 
			
		||||
        "chapters": "Capítulos",
 | 
			
		||||
        "live": "{0} em Direto",
 | 
			
		||||
        "shorts": "\"Shorts\""
 | 
			
		||||
        "live": "{0} em direto",
 | 
			
		||||
        "shorts": "Curtos",
 | 
			
		||||
        "all": "Todos",
 | 
			
		||||
        "category": "Categoria",
 | 
			
		||||
        "chapters_horizontal": "Horizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical",
 | 
			
		||||
        "license": "Licença",
 | 
			
		||||
        "visibility": "Visibilidade"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Será que querias dizer: {0}?",
 | 
			
		||||
        "did_you_mean": "Será que queria dizer: {0}?",
 | 
			
		||||
        "all": "YouTube: Tudo",
 | 
			
		||||
        "videos": "YouTube: Vídeos",
 | 
			
		||||
        "channels": "YouTube: Canais",
 | 
			
		||||
        "playlists": "YouTube: Listas de Reprodução",
 | 
			
		||||
        "playlists": "YouTube: Listas de reprodução",
 | 
			
		||||
        "music_songs": "YT Music: Músicas",
 | 
			
		||||
        "music_videos": "YT Music: Vídeos",
 | 
			
		||||
        "music_albums": "YT Music: Álbuns",
 | 
			
		||||
        "music_playlists": "YT Music: Listas de Reprodução"
 | 
			
		||||
        "music_playlists": "YT Music: Listas de reprodução",
 | 
			
		||||
        "music_artists": "YT Music: Artistas"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Ver em {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Subscrito a: {0}"
 | 
			
		||||
        "subscribed_channels_count": "Subscreveu: {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "Nota: as configurações são guardadas no armazenamento local to seu navegador. Eliminar os dados de navegação irá redefini-las.",
 | 
			
		||||
        "preferences_note": "Nota: as preferências são guardadas no armazenamento local do seu navegador. Se limpar os dados de navegação, também limpa as preferências.",
 | 
			
		||||
        "page_not_found": "Página não encontrada",
 | 
			
		||||
        "copied": "Copiada!",
 | 
			
		||||
        "cannot_copy": "Não foi possível copiar!",
 | 
			
		||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?"
 | 
			
		||||
        "local_storage": "Esta ação requer localStorage, os cookies estão ativados?",
 | 
			
		||||
        "register_no_email_note": "Não recomendamos utilizar um endereço de e-mail como nome de utilizador. Continuar?",
 | 
			
		||||
        "next_video_countdown": "A reproduzir o vídeo seguinte em {0}s",
 | 
			
		||||
        "hours": "{quantidade} hora(s)",
 | 
			
		||||
        "days": "{quantidade} dia(s)",
 | 
			
		||||
        "weeks": "{quantidade} semana(s)",
 | 
			
		||||
        "months": "{quantidade} mês(es)"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,152 +1,182 @@
 | 
			
		|||
{
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "back_to_home": "Înapoi acasă",
 | 
			
		||||
        "store_search_history": "Salveaza Istoric de Cautări",
 | 
			
		||||
        "with_timecode": "Distribuie cu cod de timp",
 | 
			
		||||
        "store_search_history": "Rețineți istoricul de căutări",
 | 
			
		||||
        "with_timecode": "Distribuiți cu timpul de cod",
 | 
			
		||||
        "piped_link": "Link Piped",
 | 
			
		||||
        "time_code": "Cod de timp (secunde)",
 | 
			
		||||
        "show_chapters": "Capitole",
 | 
			
		||||
        "search": "Caută",
 | 
			
		||||
        "logout": "Scoate contul de pe acest dispozitiv",
 | 
			
		||||
        "add_to_playlist": "Adaugă în Playlist",
 | 
			
		||||
        "remove_from_playlist": "Șterge din Playlist",
 | 
			
		||||
        "create_playlist": "Creează Playlist",
 | 
			
		||||
        "delete_playlist": "Șterge Playlist",
 | 
			
		||||
        "delete_playlist_confirm": "Ștergi acest playlist?",
 | 
			
		||||
        "please_select_playlist": "Te rog să alegi un playlist",
 | 
			
		||||
        "minimize_recommendations_default": "Ascunde Recomandări ca default",
 | 
			
		||||
        "subscribe": "Abonează-te - {count}",
 | 
			
		||||
        "least_recent": "Mai puțin recente",
 | 
			
		||||
        "channel_name_asc": "Nume Canal (A-Z)",
 | 
			
		||||
        "channel_name_desc": "Nume Canal (Z-A)",
 | 
			
		||||
        "search": "Căutare (Ctrl+K)",
 | 
			
		||||
        "logout": "Deconectați-vă de pe acest dispozitiv",
 | 
			
		||||
        "add_to_playlist": "Adăugare în playlist",
 | 
			
		||||
        "remove_from_playlist": "Ștergere din playlist",
 | 
			
		||||
        "create_playlist": "Creați playlist",
 | 
			
		||||
        "delete_playlist": "Ștergeți playlist",
 | 
			
		||||
        "delete_playlist_confirm": "Ștergeți acest playlist?",
 | 
			
		||||
        "please_select_playlist": "Vă rugăm să alegeți un playlist",
 | 
			
		||||
        "minimize_recommendations_default": "Minimizați recomandările în mod implicit",
 | 
			
		||||
        "subscribe": "Abonare - {count}",
 | 
			
		||||
        "least_recent": "Cele mai vechi",
 | 
			
		||||
        "channel_name_asc": "Numele canalului (A-Z)",
 | 
			
		||||
        "channel_name_desc": "Nume canalului (Z-A)",
 | 
			
		||||
        "back": "Înapoi",
 | 
			
		||||
        "uses_api_from": "Folosește API de la ",
 | 
			
		||||
        "enable_sponsorblock": "Activează Sponsorblock",
 | 
			
		||||
        "skip_intro": "Sari animația de Intermisie / Intro",
 | 
			
		||||
        "skip_preview": "Sari Preview / Recapitulare",
 | 
			
		||||
        "skip_self_promo": "Sari Promoția Neplătita / Proprie",
 | 
			
		||||
        "skip_non_music": "Sari Muzica: Secțiunea de Non-Muzică",
 | 
			
		||||
        "skip_highlight": "Sari Highlight",
 | 
			
		||||
        "show_markers": "Arată Marcaje in Player",
 | 
			
		||||
        "uses_api_from": "Se folosește API-ul de la ",
 | 
			
		||||
        "enable_sponsorblock": "Activați Sponsorblock",
 | 
			
		||||
        "skip_intro": "Omitere pauze/animații de intro",
 | 
			
		||||
        "skip_preview": "Omitere previzualizare/recapitulare",
 | 
			
		||||
        "skip_self_promo": "Omitere promoție neplătită/autopromovare",
 | 
			
		||||
        "skip_non_music": "Omitere muzică: Secțiune non-muzicală",
 | 
			
		||||
        "skip_highlight": "Omitere evidențiere",
 | 
			
		||||
        "show_markers": "Se afișează marcatori în player",
 | 
			
		||||
        "dark": "Întunecat",
 | 
			
		||||
        "auto": "Auto",
 | 
			
		||||
        "audio_only": "Doar Audio",
 | 
			
		||||
        "default_quality": "Calitate Default",
 | 
			
		||||
        "country_selection": "Selecție Țară",
 | 
			
		||||
        "default_homepage": "Pagina de Acasă",
 | 
			
		||||
        "minimize_comments_default": "Ascunde Comentariile",
 | 
			
		||||
        "minimize_description_default": "Ascunde Descrierea",
 | 
			
		||||
        "language_selection": "Selecție Limbă",
 | 
			
		||||
        "audio_only": "Doar audio",
 | 
			
		||||
        "default_quality": "Calitate implicită",
 | 
			
		||||
        "country_selection": "Țară",
 | 
			
		||||
        "default_homepage": "Pagina principală implicită",
 | 
			
		||||
        "minimize_comments_default": "Minimizați comentariile în mod implicit",
 | 
			
		||||
        "minimize_description_default": "Minimizați descrierea în mod implicit",
 | 
			
		||||
        "language_selection": "Limbă",
 | 
			
		||||
        "instances_list": "Listă de Instanțe",
 | 
			
		||||
        "enabled_codecs": "Activează Codecuri (Multiple)",
 | 
			
		||||
        "loop_this_video": "Repornește Video-ul",
 | 
			
		||||
        "donations": "Donații",
 | 
			
		||||
        "show_recommendations": "Arată Recomandări",
 | 
			
		||||
        "disable_lbry": "Oprește LBRY pentru Streaming",
 | 
			
		||||
        "enable_lbry_proxy": "Activează Proxy pentru LBRY",
 | 
			
		||||
        "view_ssl_score": "Vezi Scor SSL",
 | 
			
		||||
        "enabled_codecs": "Codecuri activate (multiple)",
 | 
			
		||||
        "loop_this_video": "Repetare video",
 | 
			
		||||
        "donations": "Donații pentru dezvoltare",
 | 
			
		||||
        "show_recommendations": "Afișați recomandările",
 | 
			
		||||
        "disable_lbry": "Dezactivați LBRY pentru streaming",
 | 
			
		||||
        "enable_lbry_proxy": "Activați proxy pentru LBRY",
 | 
			
		||||
        "view_ssl_score": "Vedeți scorul SSL",
 | 
			
		||||
        "filter": "Filtru",
 | 
			
		||||
        "loading": "Se încarcă...",
 | 
			
		||||
        "clear_history": "Șterge Istoric",
 | 
			
		||||
        "hide_replies": "Ascunde răspunsuri",
 | 
			
		||||
        "load_more_replies": "Mai multe Răspunsuri",
 | 
			
		||||
        "delete_playlist_video_confirm": "Ștergi video-ul din playlist?",
 | 
			
		||||
        "select_playlist": "Alege un Playlist",
 | 
			
		||||
        "delete_account": "Șterge Contul",
 | 
			
		||||
        "show_watch_on_youtube": "Arata buton de Vezi pe YouTube",
 | 
			
		||||
        "invalidate_session": "Deconectează-te peste tot",
 | 
			
		||||
        "instance_auth_selection": "Selecție Instanță de Autentificare",
 | 
			
		||||
        "clone_playlist_success": "Clonat cu succes!",
 | 
			
		||||
        "reset_preferences": "Resetează preferințele",
 | 
			
		||||
        "confirm_reset_preferences": "Sigur vrei să îți resetezi preferințele?",
 | 
			
		||||
        "rename_playlist": "Redenumește playlist",
 | 
			
		||||
        "new_playlist_name": "Nume playlist nou",
 | 
			
		||||
        "share": "Distribuie",
 | 
			
		||||
        "follow_link": "Deschide link",
 | 
			
		||||
        "copy_link": "Copiază link",
 | 
			
		||||
        "hide_watched": "Ascunde video-urile vizionate",
 | 
			
		||||
        "clear_history": "Ștergeți istoricul",
 | 
			
		||||
        "hide_replies": "Ascundeți răspunsurile",
 | 
			
		||||
        "load_more_replies": "Mai multe răspunsuri",
 | 
			
		||||
        "delete_playlist_video_confirm": "Ștergeți videoclipul din playlist?",
 | 
			
		||||
        "select_playlist": "Selectați un playlist",
 | 
			
		||||
        "delete_account": "Ștergeți-vă contul",
 | 
			
		||||
        "show_watch_on_youtube": "Afișați butonul „Vizionați pe YouTube”",
 | 
			
		||||
        "invalidate_session": "Deconectați toate dispozitivele",
 | 
			
		||||
        "instance_auth_selection": "Instanța de autentificare",
 | 
			
		||||
        "clone_playlist_success": "Clonată cu succes!",
 | 
			
		||||
        "reset_preferences": "Resetați preferințele",
 | 
			
		||||
        "confirm_reset_preferences": "Sunteți sigur că doriți să vă resetați preferințele?",
 | 
			
		||||
        "share": "Distribuiți",
 | 
			
		||||
        "follow_link": "Urmați link-ul",
 | 
			
		||||
        "copy_link": "Copiați link-ul",
 | 
			
		||||
        "hide_watched": "Ascundeți videoclipurile vizionate din flux",
 | 
			
		||||
        "documentation": "Documentație",
 | 
			
		||||
        "status_page": "Status",
 | 
			
		||||
        "source_code": "Cod sursă",
 | 
			
		||||
        "instance_donations": "Donații instanță",
 | 
			
		||||
        "reply_count": "{count} răspunsuri",
 | 
			
		||||
        "minimize_chapters_default": "Ascunde Capitole ca default",
 | 
			
		||||
        "skip_sponsors": "Sari Sponsori",
 | 
			
		||||
        "different_auth_instance": "Folosește altă instanță pentru autentificare",
 | 
			
		||||
        "clone_playlist": "Clonează Playlist",
 | 
			
		||||
        "backup_preferences": "Backup preferințe",
 | 
			
		||||
        "unsubscribe": "Dezabonează-te - {count}",
 | 
			
		||||
        "view_subscriptions": "Vezi Subscripții",
 | 
			
		||||
        "sort_by": "Sortează după:",
 | 
			
		||||
        "download_as_txt": "Descarcă ca .txt",
 | 
			
		||||
        "most_recent": "Cele mai Recente",
 | 
			
		||||
        "skip_outro": "Sari Endcards / Credite",
 | 
			
		||||
        "skip_interaction": "Sari Reminder de interacțiune (Subscribe)",
 | 
			
		||||
        "minimize_chapters_default": "Minimizați capitolele în mod implicit",
 | 
			
		||||
        "skip_sponsors": "Omitere sponsori",
 | 
			
		||||
        "different_auth_instance": "Folosiți o instanță diferită pentru autentificare",
 | 
			
		||||
        "clone_playlist": "Clonați lista de redare",
 | 
			
		||||
        "backup_preferences": "Faceți backup la preferințe",
 | 
			
		||||
        "unsubscribe": "Dezabonare - {count}",
 | 
			
		||||
        "view_subscriptions": "Vedeți abonamentele",
 | 
			
		||||
        "sort_by": "Sortare după:",
 | 
			
		||||
        "download_as_txt": "Descărcați ca .txt",
 | 
			
		||||
        "most_recent": "Cele mai recente",
 | 
			
		||||
        "skip_outro": "Omitere carduri de sfârșit/mulțumiri",
 | 
			
		||||
        "skip_interaction": "Omitere reamintiri de interacțiune (abonare)",
 | 
			
		||||
        "light": "Luminat",
 | 
			
		||||
        "restore_preferences": "Restore preferințe",
 | 
			
		||||
        "skip_filler_tangent": "Sari Tangenta Filler",
 | 
			
		||||
        "restore_preferences": "Restaurați preferințele",
 | 
			
		||||
        "skip_filler_tangent": "Omitere tangentă de umplere",
 | 
			
		||||
        "theme": "Temă",
 | 
			
		||||
        "autoplay_video": "Autopornește Video",
 | 
			
		||||
        "buffering_goal": "Buffering Goal (secunde)",
 | 
			
		||||
        "instance_selection": "Selecție Instanță",
 | 
			
		||||
        "store_watch_history": "Salvează Istoricul de Vizionare",
 | 
			
		||||
        "minimize_comments": "Ascunde Comentarii",
 | 
			
		||||
        "minimize_description": "Ascunde Descriere",
 | 
			
		||||
        "show_more": "Mai Mult",
 | 
			
		||||
        "autoplay_video": "Redare automată video",
 | 
			
		||||
        "buffering_goal": "Obiectiv de tamponare (în secunde)",
 | 
			
		||||
        "instance_selection": "Instanță",
 | 
			
		||||
        "store_watch_history": "Rețineți istoricul de vizionări",
 | 
			
		||||
        "minimize_comments": "Minimizați comentariile",
 | 
			
		||||
        "minimize_description": "Minimizați descrierea",
 | 
			
		||||
        "show_more": "Mai mult",
 | 
			
		||||
        "no": "Nu",
 | 
			
		||||
        "export_to_json": "Exportă ca JSON",
 | 
			
		||||
        "import_from_json": "Importă din JSON/CSV",
 | 
			
		||||
        "auto_play_next_video": "Autopornește următorul Video",
 | 
			
		||||
        "minimize_recommendations": "Ascunde Recomandări",
 | 
			
		||||
        "export_to_json": "Exportați ca JSON",
 | 
			
		||||
        "import_from_json": "Importați din JSON/CSV",
 | 
			
		||||
        "auto_play_next_video": "Redați automat următorul video",
 | 
			
		||||
        "minimize_recommendations": "Minimizați recomandările",
 | 
			
		||||
        "yes": "Da",
 | 
			
		||||
        "show_comments": "Arată Comentarii",
 | 
			
		||||
        "show_description": "Arată Descriere"
 | 
			
		||||
        "show_comments": "Afișați comentariile",
 | 
			
		||||
        "show_description": "Afișați descrierea",
 | 
			
		||||
        "bookmark_playlist": "Marcați",
 | 
			
		||||
        "no_valid_playlists": "Fișierul nu conține playlist-uri valide!",
 | 
			
		||||
        "skip_automatically": "Automat",
 | 
			
		||||
        "min_segment_length": "Lungimea minimă a segmentului (în secunde)",
 | 
			
		||||
        "skip_segment": "Omitere segment",
 | 
			
		||||
        "skip_button_only": "Afișați butonul de omitere",
 | 
			
		||||
        "with_playlist": "Distribuiți cu playlist",
 | 
			
		||||
        "playlist_bookmarked": "Marcat",
 | 
			
		||||
        "show_less": "Mai puțin",
 | 
			
		||||
        "autoplay_next_countdown": "Numărătoarea inversă implicită până la următorul videoclip (în secunde)",
 | 
			
		||||
        "dismiss": "Concediază",
 | 
			
		||||
        "group_name": "Numele grupului",
 | 
			
		||||
        "create_group": "Creați un grup",
 | 
			
		||||
        "auto_display_captions": "Afișare automată subtitrări",
 | 
			
		||||
        "playlist_name": "Numele playlist-ului",
 | 
			
		||||
        "okay": "OK",
 | 
			
		||||
        "playlist_description": "Descrierea playlist-ului",
 | 
			
		||||
        "edit_playlist": "Editează playlist-ul",
 | 
			
		||||
        "cancel": "Anulare",
 | 
			
		||||
        "chapters_layout_mobile": "Mod afișare capitole pe mobil",
 | 
			
		||||
        "show_search_suggestions": "Afișare sugestii căutare",
 | 
			
		||||
        "enable_dearrow": "Activați DeArrow",
 | 
			
		||||
        "delete_automatically": "Șterge automat după"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "Scor SSL",
 | 
			
		||||
        "version": "Versiune",
 | 
			
		||||
        "up_to_date": "Actualizat?",
 | 
			
		||||
        "instance_name": "Nume Instanță",
 | 
			
		||||
        "instance_locations": "Locațiile Instanței",
 | 
			
		||||
        "instance_name": "Nume instanță",
 | 
			
		||||
        "instance_locations": "Locațiile instanței",
 | 
			
		||||
        "has_cdn": "Are CDN?",
 | 
			
		||||
        "registered_users": "Useri Înregistrați"
 | 
			
		||||
        "registered_users": "Utilizatori înregistrați"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "user_disabled": "Comentariile sunt dezactivate în setări.",
 | 
			
		||||
        "pinned_by": "Pomovat de {author}",
 | 
			
		||||
        "disabled": "Comentariile sunt dezactivate de creator.",
 | 
			
		||||
        "loading": "Se incarcă comentariile..."
 | 
			
		||||
        "pinned_by": "Fixat de {author}",
 | 
			
		||||
        "disabled": "Comentariile sunt dezactivate de către autor.",
 | 
			
		||||
        "loading": "Se încarcă comentariile..."
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "views": "{views} vizionări",
 | 
			
		||||
        "chapters": "Capitole",
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "watched": "Văzut",
 | 
			
		||||
        "sponsor_segments": "Segmente Sponsori",
 | 
			
		||||
        "ratings_disabled": "Like-uri dezactivate",
 | 
			
		||||
        "live": "{0} Live",
 | 
			
		||||
        "videos": "Video-uri"
 | 
			
		||||
        "watched": "Vizionat",
 | 
			
		||||
        "sponsor_segments": "Segmente sponsori",
 | 
			
		||||
        "ratings_disabled": "Evaluări dezactivate",
 | 
			
		||||
        "live": "{0} în direct",
 | 
			
		||||
        "videos": "Videoclipuri",
 | 
			
		||||
        "category": "Categorie",
 | 
			
		||||
        "all": "Tot",
 | 
			
		||||
        "chapters_horizontal": "Orizontal",
 | 
			
		||||
        "chapters_vertical": "Vertical"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Nume User",
 | 
			
		||||
        "username": "Nume de utilizator",
 | 
			
		||||
        "password": "Parolă"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "videos": "YouTube: Video-uri",
 | 
			
		||||
        "music_playlists": "YT Music: Playlisturi",
 | 
			
		||||
        "did_you_mean": "Voiai să scrii: {0}?",
 | 
			
		||||
        "videos": "YouTube: Videoclipuri",
 | 
			
		||||
        "music_playlists": "YT Music: Liste de redare",
 | 
			
		||||
        "did_you_mean": "Vă refereați la: {0}?",
 | 
			
		||||
        "all": "YouTube: Toate",
 | 
			
		||||
        "channels": "YouTube: Canale",
 | 
			
		||||
        "playlists": "YouTube: Playlisturi",
 | 
			
		||||
        "music_songs": "YT Music: Cântece",
 | 
			
		||||
        "music_videos": "YT Music: Video-uri",
 | 
			
		||||
        "music_albums": "YT Music: Albume"
 | 
			
		||||
        "playlists": "YouTube: Liste de redare",
 | 
			
		||||
        "music_songs": "YT Music: Muzică",
 | 
			
		||||
        "music_videos": "YT Music: Videoclipuri",
 | 
			
		||||
        "music_albums": "YT Music: Albume",
 | 
			
		||||
        "music_artists": "YT Music: Artiști"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "cannot_copy": "Nu se poate copia!",
 | 
			
		||||
        "preferences_note": "Sfat: preferințele sunt salvate in memoria locala a browserului tău. Ștergând datele browserului le ștergi si pe ele.",
 | 
			
		||||
        "page_not_found": "Pagină negăsită",
 | 
			
		||||
        "copied": "S-a copiat!"
 | 
			
		||||
        "cannot_copy": "Nu s-a putut copia!",
 | 
			
		||||
        "preferences_note": "Notă: preferințele sunt salvate în memoria locală a browserului dvs. Ștergerea datelor din browserul dvs. le va reseta.",
 | 
			
		||||
        "page_not_found": "Pagina nu a fost găsită",
 | 
			
		||||
        "copied": "Copiat!",
 | 
			
		||||
        "register_no_email_note": "Utilizarea unui e-mail ca nume de utilizator nu este recomandată. Continuați oricum?",
 | 
			
		||||
        "local_storage": "Această acțiune necesită localStorage, sunt activate cookie-urile?",
 | 
			
		||||
        "next_video_countdown": "Redarea următorului videoclip în {0}s",
 | 
			
		||||
        "days": "{amount} zi(le)"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Abonat la: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -154,19 +184,22 @@
 | 
			
		|||
    "titles": {
 | 
			
		||||
        "register": "Înregistrare",
 | 
			
		||||
        "history": "Istoric",
 | 
			
		||||
        "subscriptions": "Abonări",
 | 
			
		||||
        "playlists": "Playlisturi",
 | 
			
		||||
        "subscriptions": "Abonamente",
 | 
			
		||||
        "playlists": "Liste de redare",
 | 
			
		||||
        "account": "Cont",
 | 
			
		||||
        "instance": "Instanță",
 | 
			
		||||
        "login": "Logare",
 | 
			
		||||
        "feed": "Abonări",
 | 
			
		||||
        "trending": "Trending",
 | 
			
		||||
        "livestreams": "Live-uri",
 | 
			
		||||
        "login": "Autentificare",
 | 
			
		||||
        "feed": "Flux",
 | 
			
		||||
        "trending": "Tendințe",
 | 
			
		||||
        "livestreams": "Fluxuri live",
 | 
			
		||||
        "channels": "Canale",
 | 
			
		||||
        "preferences": "Preferințe",
 | 
			
		||||
        "player": "Player"
 | 
			
		||||
        "player": "Player-ul",
 | 
			
		||||
        "bookmarks": "Marcaje",
 | 
			
		||||
        "channel_groups": "Grupuri de canale",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Vezi pe {0}"
 | 
			
		||||
        "watch_on": "Vizionați pe {0}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,14 +5,17 @@
 | 
			
		|||
        "register": "Регистрация",
 | 
			
		||||
        "feed": "Подписки",
 | 
			
		||||
        "preferences": "Настройки",
 | 
			
		||||
        "history": "История просмотров",
 | 
			
		||||
        "subscriptions": "Ваши подписки",
 | 
			
		||||
        "history": "История",
 | 
			
		||||
        "subscriptions": "Подписки",
 | 
			
		||||
        "playlists": "Плейлисты",
 | 
			
		||||
        "account": "Аккаунт",
 | 
			
		||||
        "player": "Плеер",
 | 
			
		||||
        "instance": "Сервер",
 | 
			
		||||
        "livestreams": "Прямые трансляции",
 | 
			
		||||
        "channels": "Каналы"
 | 
			
		||||
        "channels": "Каналы",
 | 
			
		||||
        "bookmarks": "Закладки",
 | 
			
		||||
        "channel_groups": "Группы каналов",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Смотреть на {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +30,7 @@
 | 
			
		|||
        "channel_name_asc": "Имя канала (А-Я)",
 | 
			
		||||
        "channel_name_desc": "Имя канала (Я-А)",
 | 
			
		||||
        "back": "Назад",
 | 
			
		||||
        "uses_api_from": "Использовать API, предоставляемое ",
 | 
			
		||||
        "uses_api_from": "Использовать API ",
 | 
			
		||||
        "enable_sponsorblock": "Включить Sponsorblock",
 | 
			
		||||
        "skip_sponsors": "Пропускать спонсорскую рекламу",
 | 
			
		||||
        "skip_intro": "Пропускать заставку/интро",
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +56,7 @@
 | 
			
		|||
        "instances_list": "Список зеркал сервиса Piped",
 | 
			
		||||
        "enabled_codecs": "Включённые кодеки (Можно выбрать несколько)",
 | 
			
		||||
        "instance_selection": "Выбор зеркала сервиса Piped",
 | 
			
		||||
        "show_more": "Показать больше",
 | 
			
		||||
        "show_more": "Показать еще",
 | 
			
		||||
        "yes": "Да",
 | 
			
		||||
        "no": "Нет",
 | 
			
		||||
        "export_to_json": "Экспорт в JSON",
 | 
			
		||||
| 
						 | 
				
			
			@ -66,9 +69,9 @@
 | 
			
		|||
        "minimize_recommendations": "Свернуть рекомендации",
 | 
			
		||||
        "show_recommendations": "Показать рекомендации",
 | 
			
		||||
        "disable_lbry": "Отключить LBRY для стриминга",
 | 
			
		||||
        "enable_lbry_proxy": "Проксировать видео с LBRY",
 | 
			
		||||
        "enable_lbry_proxy": "Проксировать видео для LBRY",
 | 
			
		||||
        "view_ssl_score": "Посмотреть настройки SSL",
 | 
			
		||||
        "search": "Поиск",
 | 
			
		||||
        "search": "Поиск (Ctrl+K)",
 | 
			
		||||
        "filter": "Фильтр",
 | 
			
		||||
        "loading": "Загрузка...",
 | 
			
		||||
        "clear_history": "Очистить историю",
 | 
			
		||||
| 
						 | 
				
			
			@ -84,52 +87,72 @@
 | 
			
		|||
        "select_playlist": "Выбрать плейлист",
 | 
			
		||||
        "delete_playlist_confirm": "Удалить этот плейлист?",
 | 
			
		||||
        "delete_playlist_video_confirm": "Удалить видео из плейлиста?",
 | 
			
		||||
        "show_markers": "Показать Mаркеры Hа Проигрывателе",
 | 
			
		||||
        "show_markers": "Показать маркеры на проигрывателе",
 | 
			
		||||
        "delete_account": "Удалить аккаунт",
 | 
			
		||||
        "logout": "Выйти из этого устройства",
 | 
			
		||||
        "download_as_txt": "Скачать как .txt",
 | 
			
		||||
        "minimize_recommendations_default": "Скрыть Рекомендации по умолчанию",
 | 
			
		||||
        "invalidate_session": "Выйти из всех устройств",
 | 
			
		||||
        "different_auth_instance": "Использовать другие средства аутентификации",
 | 
			
		||||
        "instance_auth_selection": "Выбор средств аутентификации",
 | 
			
		||||
        "different_auth_instance": "Использовать другое зеркало для аутентификации",
 | 
			
		||||
        "instance_auth_selection": "Выбор зеркала аутентификации",
 | 
			
		||||
        "clone_playlist": "Клонировать плейлист",
 | 
			
		||||
        "clone_playlist_success": "Клонирование прошло успешно!",
 | 
			
		||||
        "show_chapters": "Части",
 | 
			
		||||
        "rename_playlist": "Переименовать плейлист",
 | 
			
		||||
        "new_playlist_name": "Новое название плейлиста",
 | 
			
		||||
        "clone_playlist_success": "Успешно клонировано!",
 | 
			
		||||
        "show_chapters": "Главы",
 | 
			
		||||
        "share": "Поделиться",
 | 
			
		||||
        "with_timecode": "Поделиться с отметкой времени",
 | 
			
		||||
        "with_timecode": "Поделиться с таймкодом",
 | 
			
		||||
        "piped_link": "Ссылка Piped",
 | 
			
		||||
        "follow_link": "Ссылка подписки",
 | 
			
		||||
        "follow_link": "Перейти по ссылке",
 | 
			
		||||
        "copy_link": "Скопировать ссылку",
 | 
			
		||||
        "time_code": "Тайм-код (в секундах)",
 | 
			
		||||
        "time_code": "Таймкод (в секундах)",
 | 
			
		||||
        "reset_preferences": "Сбросить настройки",
 | 
			
		||||
        "confirm_reset_preferences": "Вы уверены, что хотите сбросить настройки?",
 | 
			
		||||
        "backup_preferences": "Бэкап настроек",
 | 
			
		||||
        "restore_preferences": "Восстановить настройки",
 | 
			
		||||
        "back_to_home": "Вернутся на главную",
 | 
			
		||||
        "back_to_home": "Назад на главную",
 | 
			
		||||
        "store_search_history": "Хранить историю поиска",
 | 
			
		||||
        "hide_watched": "Скрыть просмотренные видео в ленте",
 | 
			
		||||
        "status_page": "Статус",
 | 
			
		||||
        "source_code": "Исходный код",
 | 
			
		||||
        "documentation": "Пожертвования сервера",
 | 
			
		||||
        "instance_donations": "Пожертвования сервера",
 | 
			
		||||
        "documentation": "Документация",
 | 
			
		||||
        "instance_donations": "Пожертвования зеркала",
 | 
			
		||||
        "reply_count": "{count} ответов",
 | 
			
		||||
        "minimize_comments_default": "Сворачивать комментарии по умолчанию",
 | 
			
		||||
        "minimize_comments": "Свернуть комментарии",
 | 
			
		||||
        "show_watch_on_youtube": "Показать кнопку Смотреть на YouTube",
 | 
			
		||||
        "minimize_chapters_default": "Скрывать главы по умолчанию",
 | 
			
		||||
        "no_valid_playlists": "Файл не содержит действительных списков воспроизведения!"
 | 
			
		||||
        "no_valid_playlists": "Файл не содержит действующих плейлистов!",
 | 
			
		||||
        "with_playlist": "Поделиться с плейлистом",
 | 
			
		||||
        "bookmark_playlist": "Закладка",
 | 
			
		||||
        "playlist_bookmarked": "В закладках",
 | 
			
		||||
        "skip_automatically": "Автоматически",
 | 
			
		||||
        "min_segment_length": "Минимальная длина сегмента (в секундах)",
 | 
			
		||||
        "skip_button_only": "Показать кнопку \"Пропустить\"",
 | 
			
		||||
        "skip_segment": "Пропустить сегмент",
 | 
			
		||||
        "show_less": "Показать меньше",
 | 
			
		||||
        "autoplay_next_countdown": "Отсчет по умолчанию до следующего видео (в секундах)",
 | 
			
		||||
        "dismiss": "Отклонить",
 | 
			
		||||
        "group_name": "Имя группы",
 | 
			
		||||
        "create_group": "Создать группу",
 | 
			
		||||
        "cancel": "Отмена",
 | 
			
		||||
        "edit_playlist": "Редактировать плейлист",
 | 
			
		||||
        "playlist_description": "Описание плейлиста",
 | 
			
		||||
        "okay": "Хорошо",
 | 
			
		||||
        "auto_display_captions": "Авто-отображение субтитров",
 | 
			
		||||
        "playlist_name": "Название плейлиста",
 | 
			
		||||
        "show_search_suggestions": "Показать поисковые предложения",
 | 
			
		||||
        "chapters_layout_mobile": "Расположение глав в мобильном виде",
 | 
			
		||||
        "delete_automatically": "Автоматическое удаление после",
 | 
			
		||||
        "enable_dearrow": "Включить DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Прикреплено пользователем {author}",
 | 
			
		||||
        "pinned_by": "Закреплено пользователем {author}",
 | 
			
		||||
        "loading": "Загрузка комментариев...",
 | 
			
		||||
        "user_disabled": "Комментарии отключены в настройках.",
 | 
			
		||||
        "disabled": "Коментарии отключены автором."
 | 
			
		||||
        "disabled": "Комментарии отключены автором."
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_name": "Название",
 | 
			
		||||
        "instance_locations": "Местоположение",
 | 
			
		||||
        "instance_name": "Имя зеркала",
 | 
			
		||||
        "instance_locations": "Местоположения зеркала",
 | 
			
		||||
        "has_cdn": "Имеется CDN?",
 | 
			
		||||
        "ssl_score": "Оценка настроек SSL",
 | 
			
		||||
        "registered_users": "Зарегистрировано пользователей",
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +160,7 @@
 | 
			
		|||
        "up_to_date": "Версия актуальна?"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Аккаунт на Piped",
 | 
			
		||||
        "username": "Имя пользователя",
 | 
			
		||||
        "password": "Пароль"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +171,13 @@
 | 
			
		|||
        "ratings_disabled": "Оценки отключены",
 | 
			
		||||
        "live": "{0} В эфире",
 | 
			
		||||
        "chapters": "Содержание",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Все",
 | 
			
		||||
        "category": "Категория",
 | 
			
		||||
        "chapters_horizontal": "Горизонтально",
 | 
			
		||||
        "chapters_vertical": "Вертикально",
 | 
			
		||||
        "visibility": "Видимость",
 | 
			
		||||
        "license": "Лицензия"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Может быть вы имели в виду: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -159,16 +188,23 @@
 | 
			
		|||
        "music_songs": "YT Music: Композиции",
 | 
			
		||||
        "music_videos": "YT Music: Видео",
 | 
			
		||||
        "music_albums": "YT Music: Альбомы",
 | 
			
		||||
        "music_playlists": "YT Music: Плейлисты"
 | 
			
		||||
        "music_playlists": "YT Music: Плейлисты",
 | 
			
		||||
        "music_artists": "YT Music: Исполнители"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Подписан на: {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "preferences_note": "Примечание: настройки сохранены в локальном хранилище браузера. При удалении данных браузера они будут удалены.",
 | 
			
		||||
        "preferences_note": "Примечание: настройки сохранены в локальном хранилище браузера. Удаление данных вашего браузера сбросит их.",
 | 
			
		||||
        "copied": "Скопировано!",
 | 
			
		||||
        "cannot_copy": "Не получилось скопировать!",
 | 
			
		||||
        "cannot_copy": "Не удалось скопировать!",
 | 
			
		||||
        "page_not_found": "Страница не найдена",
 | 
			
		||||
        "local_storage": "Это действие требует локального хранилища (localStorage), разрешены ли файлы cookie?"
 | 
			
		||||
        "local_storage": "Это действие требует разрешения localStorage, включены ли cookie-файлы?",
 | 
			
		||||
        "register_no_email_note": "Использование электронной почты в качестве имени пользователя не рекомендуется. Продолжить?",
 | 
			
		||||
        "next_video_countdown": "Следующие видео через {0} с",
 | 
			
		||||
        "days": "{amount} дней",
 | 
			
		||||
        "hours": "{amount} час(ов)",
 | 
			
		||||
        "weeks": "{amount} недель",
 | 
			
		||||
        "months": "{amount} месяцев"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										207
									
								
								src/locales/si.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								src/locales/si.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,207 @@
 | 
			
		|||
{
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "trending": "නැගී එන",
 | 
			
		||||
        "login": "පිවිසෙන්න",
 | 
			
		||||
        "register": "ලියාපදිංචිය",
 | 
			
		||||
        "preferences": "අභිප්රේත",
 | 
			
		||||
        "history": "ඉතිහාසය",
 | 
			
		||||
        "subscriptions": "දායකත්ව",
 | 
			
		||||
        "account": "ගිණුම",
 | 
			
		||||
        "player": "වාදකය",
 | 
			
		||||
        "livestreams": "සජීව ප්රචාර",
 | 
			
		||||
        "channels": "නාලිකා",
 | 
			
		||||
        "playlists": "වාදන ලැයිස්තු",
 | 
			
		||||
        "instance": "සේවාදායකය",
 | 
			
		||||
        "bookmarks": "පොත්යොමු",
 | 
			
		||||
        "feed": "සංග්රහය",
 | 
			
		||||
        "channel_groups": "නාලිකා සමූහ"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "subscribe": "දායකවන්න - {count}",
 | 
			
		||||
        "unsubscribe": "දායක නොවන්න - {count}",
 | 
			
		||||
        "most_recent": "වඩාත් මෑත",
 | 
			
		||||
        "least_recent": "ආසන්න මෑත",
 | 
			
		||||
        "channel_name_asc": "නාලිකාවේ නම (අ-ෆ)",
 | 
			
		||||
        "channel_name_desc": "නාලිකාවේ නම (ෆ-අ)",
 | 
			
		||||
        "back": "ආපසු",
 | 
			
		||||
        "skip_sponsors": "අනුග්රහකයින් මඟහරින්න",
 | 
			
		||||
        "skip_outro": "අවසන් කාඩ්පත/දායක ලැයිස්තුව මඟ හරින්න",
 | 
			
		||||
        "skip_preview": "පෙරදසුන/සාරාංශය මඟ හරින්න",
 | 
			
		||||
        "skip_self_promo": "නොගෙවූ/ස්වයං ප්රවර්ධන මඟ හරින්න",
 | 
			
		||||
        "skip_filler_tangent": "අදාළ නොවන කොටස් මඟහරින්න",
 | 
			
		||||
        "theme": "තේමාව",
 | 
			
		||||
        "dark": "අඳුරු",
 | 
			
		||||
        "light": "දීප්ත",
 | 
			
		||||
        "autoplay_video": "දෘශ්යක ඉබේ වාදනය",
 | 
			
		||||
        "auto": "ස්වයං",
 | 
			
		||||
        "default_quality": "පෙරනිමි ගුණත්වය",
 | 
			
		||||
        "default_homepage": "පෙරනිමි මුල් පිටුව",
 | 
			
		||||
        "show_markers": "වාදකයේ මාකර් පෙන්වන්න",
 | 
			
		||||
        "buffering_goal": "අන්තරාචය ඉලක්කය (තත්. වලින්)",
 | 
			
		||||
        "enable_sponsorblock": "Sponsorblock සබල කරන්න",
 | 
			
		||||
        "sort_by": "පෙළගසන්න:",
 | 
			
		||||
        "skip_highlight": "ඉස්මතු කිරීම් මඟ හරින්න",
 | 
			
		||||
        "language_selection": "භාෂාව",
 | 
			
		||||
        "show_more": "තව පෙන්වන්න",
 | 
			
		||||
        "yes": "ඔව්",
 | 
			
		||||
        "no": "නැහැ",
 | 
			
		||||
        "export_to_json": "JSON ලෙස නිර්යාත කරන්න",
 | 
			
		||||
        "import_from_json": "JSON/CSV වෙතින් ආයාත කරන්න",
 | 
			
		||||
        "loop_this_video": "මෙම දෘශ්යකය පුඩුලන්න",
 | 
			
		||||
        "auto_play_next_video": "ඊළඟ දෘශ්යකය ඉබේ වාදනය කරන්න",
 | 
			
		||||
        "donations": "සංවර්ධන පරිත්යාග",
 | 
			
		||||
        "minimize_comments": "අදහස් හකුළන්න",
 | 
			
		||||
        "show_comments": "අදහස් පෙන්වන්න",
 | 
			
		||||
        "minimize_description": "විස්තරය හකුළන්න",
 | 
			
		||||
        "show_description": "සවිස්තරය පෙන්වන්න",
 | 
			
		||||
        "minimize_recommendations": "නිර්දේශ හකුළන්න",
 | 
			
		||||
        "show_recommendations": "නිර්දේශ පෙන්වන්න",
 | 
			
		||||
        "store_watch_history": "නැරඹුම් ඉතිහාසය ගබඩා කරන්න",
 | 
			
		||||
        "enabled_codecs": "සබල කර ඇති කෝඩෙක්ස් (බහු)",
 | 
			
		||||
        "minimize_description_default": "පෙරනිමි පරිදි සවිස්තරය සඟවන්න",
 | 
			
		||||
        "instances_list": "සේවාදායක ලැයිස්තුව",
 | 
			
		||||
        "instance_selection": "සේවාදායකය",
 | 
			
		||||
        "view_ssl_score": "SSL ලකුණු බලන්න",
 | 
			
		||||
        "search": "සොයන්න (Ctrl+K)",
 | 
			
		||||
        "loading": "පූරණය වෙමින්...",
 | 
			
		||||
        "hide_replies": "පිළිතුරු සඟවන්න",
 | 
			
		||||
        "load_more_replies": "තවත් පිළිතුරු පෙන්වන්න",
 | 
			
		||||
        "add_to_playlist": "වාදන ලැයිස්තුවට දමන්න",
 | 
			
		||||
        "create_playlist": "වාදන ලැයිස්තුව සාදන්න",
 | 
			
		||||
        "delete_playlist": "වාදන ලැයිස්තුව මකන්න",
 | 
			
		||||
        "select_playlist": "වාදන ලැයිස්තුවක් තෝරන්න",
 | 
			
		||||
        "please_select_playlist": "වාදන ලැයිස්තුවක් තෝරන්න",
 | 
			
		||||
        "delete_account": "ගිණුම මකන්න",
 | 
			
		||||
        "logout": "මෙම උපාංගයෙන් නික්මෙන්න",
 | 
			
		||||
        "minimize_recommendations_default": "පෙරනිමි පරිදි නිර්දේශ හකුළන්න",
 | 
			
		||||
        "invalidate_session": "සියළුම උපාංග නික්මවන්න",
 | 
			
		||||
        "clone_playlist": "වාදන ලැයිස්තුවේ අනුපිටපතක්",
 | 
			
		||||
        "download_as_txt": ".txt ලෙස බාගන්න",
 | 
			
		||||
        "reset_preferences": "අභිප්රේත නැවත සකසන්න",
 | 
			
		||||
        "backup_preferences": "අභිප්රේත උපස්ථ කරන්න",
 | 
			
		||||
        "restore_preferences": "අභිප්රේත ප්රත්යර්පණය කරන්න",
 | 
			
		||||
        "back_to_home": "ආපසු මුල් පිටුවට",
 | 
			
		||||
        "share": "බෙදාගන්න",
 | 
			
		||||
        "with_timecode": "කාල කේතය සමඟ බෙදා ගන්න",
 | 
			
		||||
        "piped_link": "පයිප්ඩ් සබැඳිය",
 | 
			
		||||
        "copy_link": "සබැඳියේ පිටපතක්",
 | 
			
		||||
        "time_code": "කාල කේතය (තත්පර වලින්)",
 | 
			
		||||
        "show_chapters": "පරිච්ඡේද",
 | 
			
		||||
        "status_page": "තත්වය",
 | 
			
		||||
        "source_code": "ප්රභව කේතය",
 | 
			
		||||
        "documentation": "ප්රලේඛනය",
 | 
			
		||||
        "reply_count": "පිළිතුරු {count}",
 | 
			
		||||
        "with_playlist": "වාදන ලැයිස්තුව සමඟ බෙදා ගන්න",
 | 
			
		||||
        "bookmark_playlist": "පොත්යොමුවක්",
 | 
			
		||||
        "show_watch_on_youtube": "යූටියුබ්හි නරඹන්න බොත්තම පෙන්වන්න",
 | 
			
		||||
        "filter": "පෙරහන",
 | 
			
		||||
        "instance_donations": "සේවාදායක පරිත්යාග",
 | 
			
		||||
        "instance_auth_selection": "සත්යතාව තහවුරු කිරීම සඳහා සේවාදායකයක් තේරීම",
 | 
			
		||||
        "view_subscriptions": "දායකත්ව බලන්න",
 | 
			
		||||
        "uses_api_from": "භාවිතා වන යෙ.ක්ර.මු. (API): ",
 | 
			
		||||
        "skip_intro": "විරාම/හඳුන්වාදීමේ සජීවිකරණය මඟ හරින්න",
 | 
			
		||||
        "skip_interaction": "අන්තර් ක්රියා මතක් කිරීම මඟ හරින්න (දායක වන්න)",
 | 
			
		||||
        "skip_non_music": "ගීත: ගීතය නොවන කොටස මඟ හරින්න",
 | 
			
		||||
        "remove_from_playlist": "වාදන ලැයිස්තුවෙන් ඉවතලන්න",
 | 
			
		||||
        "audio_only": "හඬ පමණි",
 | 
			
		||||
        "country_selection": "රට",
 | 
			
		||||
        "minimize_comments_default": "පෙරනිමි පරිදි අදහස් සඟවන්න",
 | 
			
		||||
        "clear_history": "ඉතිහාසය මකන්න",
 | 
			
		||||
        "disable_lbry": "ප්රචාරය සඳහා LBRY අබල කරන්න",
 | 
			
		||||
        "delete_playlist_video_confirm": "වාදන ලැයිස්තුවෙන් දෘශ්යකය ඉවත් කරන්නද?",
 | 
			
		||||
        "delete_playlist_confirm": "මෙම වාදන ලැයිස්තුව මකන්නද?",
 | 
			
		||||
        "minimize_chapters_default": "පෙරනිමි පරිදි පරිච්ඡේද හකුළන්න",
 | 
			
		||||
        "clone_playlist_success": "අනුපිටපතක් සෑදිණි!",
 | 
			
		||||
        "confirm_reset_preferences": "ඔබගේ අභිප්රේත නැවත සැකසීමට වුවමනා ද?",
 | 
			
		||||
        "follow_link": "සබැඳියට යන්න",
 | 
			
		||||
        "store_search_history": "සෙවුම් ඉතිහාසය ගබඩා කරන්න",
 | 
			
		||||
        "no_valid_playlists": "ගොනුවේ වලංගු වාදන ලැයිස්තු අඩංගු නොවේ!",
 | 
			
		||||
        "playlist_bookmarked": "පොත්යොමුවක් යෙදිණි",
 | 
			
		||||
        "enable_lbry_proxy": "LBRY සඳහා ප්රතියුක්තය සබල කරන්න",
 | 
			
		||||
        "different_auth_instance": "සත්යතාව තහවුරු කිරීම සඳහා වෙනත් සේවාදායකයක් භාවිතා කරන්න",
 | 
			
		||||
        "hide_watched": "සංග්රහයෙන් නැරඹූ දෘශ්යක සඟවන්න",
 | 
			
		||||
        "skip_button_only": "මඟහරින බොත්තම පෙන්වන්න",
 | 
			
		||||
        "skip_automatically": "ස්වයංක්රීයව",
 | 
			
		||||
        "skip_segment": "කොටස මඟ හරින්න",
 | 
			
		||||
        "min_segment_length": "අවම කොටස් දිග (තත්පර වලින්)",
 | 
			
		||||
        "show_less": "අඩුවෙන් පෙන්වන්න",
 | 
			
		||||
        "dismiss": "අයින් කරන්න",
 | 
			
		||||
        "autoplay_next_countdown": "ඊළඟ දෘශ්යකය තෙක් ගණන් කිරීම (තත්. වලින්)",
 | 
			
		||||
        "group_name": "සමූහයේ නම",
 | 
			
		||||
        "create_group": "සමූහය සාදන්න",
 | 
			
		||||
        "cancel": "අවලංගු",
 | 
			
		||||
        "okay": "හරි",
 | 
			
		||||
        "edit_playlist": "වාදන ලැයිස්තුව සංස්කරණය",
 | 
			
		||||
        "playlist_name": "වාදන ලැයිස්තුවේ නම",
 | 
			
		||||
        "playlist_description": "වාදන ලැයිස්තුවේ සවිස්තරය",
 | 
			
		||||
        "auto_display_captions": "උපසිරැසි ස්වයංක්රීයව පෙන්වන්න",
 | 
			
		||||
        "show_search_suggestions": "සෙවුම් යෝජනා පෙන්වන්න",
 | 
			
		||||
        "delete_automatically": "මෙයින් පසුව මකන්න",
 | 
			
		||||
        "generate_qrcode": "QR කේතයක් උත්පාදනය"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "{0} හි නරඹන්න"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "{author} විසින් අමුණන ලදී",
 | 
			
		||||
        "loading": "අදහස් පූරණය වෙමින්...",
 | 
			
		||||
        "disabled": "උඩුගත කරන්නා විසින් අදහස් අබල කර ඇත.",
 | 
			
		||||
        "user_disabled": "සැකසුම් හරහා අදහස් අබල කර ඇත."
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "has_cdn": "CDN තිබේද?",
 | 
			
		||||
        "version": "අනුවාදය",
 | 
			
		||||
        "up_to_date": "යාවත්කාලීනද?",
 | 
			
		||||
        "instance_name": "සේවාදායකයේ නම",
 | 
			
		||||
        "registered_users": "ලියාපදිංචි පරිශ්රීලකයින්",
 | 
			
		||||
        "ssl_score": "SSL ලකුණු",
 | 
			
		||||
        "instance_locations": "සේවාදායකයේ ස්ථානය"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "පරිශ්රීලක නාමය",
 | 
			
		||||
        "password": "මුරපදය"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "දෘශ්යක",
 | 
			
		||||
        "views": "බැලීම් {views}",
 | 
			
		||||
        "watched": "නැරඹුවා",
 | 
			
		||||
        "sponsor_segments": "අනුග්රාහක අංශ",
 | 
			
		||||
        "chapters": "පරිච්ඡේද",
 | 
			
		||||
        "shorts": "කෙටි දෘශ්යක",
 | 
			
		||||
        "ratings_disabled": "ශ්රේණිගත කිරීම් අබල කර ඇත",
 | 
			
		||||
        "live": "{0} සජීවී",
 | 
			
		||||
        "all": "සියල්ල",
 | 
			
		||||
        "category": "ප්රවර්ගය",
 | 
			
		||||
        "chapters_vertical": "සිරස්",
 | 
			
		||||
        "license": "බලපත්රය",
 | 
			
		||||
        "chapters_horizontal": "තිරස්"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "ඔබ අදහස් කළේ: {0}?",
 | 
			
		||||
        "videos": "යූටියුබ්: දෘශ්යක",
 | 
			
		||||
        "playlists": "යූටියුබි: වාදන ලැයිස්තු",
 | 
			
		||||
        "music_songs": "යූටියුබි ගීත: ගීත",
 | 
			
		||||
        "music_videos": "යූටියුබි ගීත: දෘශ්යක",
 | 
			
		||||
        "music_albums": "YT Music: ඇල්බම",
 | 
			
		||||
        "music_playlists": "යූටියුබි ගීත: වාදන ලැයිස්තු",
 | 
			
		||||
        "channels": "යූටියුබ්: නාලිකා",
 | 
			
		||||
        "all": "යූටියුබි: සියල්ල",
 | 
			
		||||
        "music_artists": "යූටියුබ් ගීත: කලාකරුවන්"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "page_not_found": "පිටුව හමු නොවිණි",
 | 
			
		||||
        "copied": "පිටපත් විය!",
 | 
			
		||||
        "cannot_copy": "පිටපත් නොවේ!",
 | 
			
		||||
        "local_storage": "මෙම ක්රියාමාර්ගයට ස්ථානීය-ආචයනය වුවමනාය, දත්තකඩ සබල කර තිබේද?",
 | 
			
		||||
        "register_no_email_note": "පරිශ්රීලක නාමය ලෙස වි-තැපෑලක් භාවිතය නිර්දේශ නොකෙරේ. ඉදිරියට යන්නද?",
 | 
			
		||||
        "preferences_note": "සටහන: ඔබගේ අතිරික්සුවේ ස්ථානීය ආචයනයේ අභිප්රේත සුරැකෙයි. අතිරික්සුවේ දත්ත මැකීමෙන් ඒවා අහිමි වනු ඇත.",
 | 
			
		||||
        "next_video_countdown": "ඊළඟ දෘශ්යකය තත්. {0} කින් වාදනය වේ",
 | 
			
		||||
        "days": "දවස් {amount}",
 | 
			
		||||
        "weeks": "සති {amount}",
 | 
			
		||||
        "hours": "පැය {amount}",
 | 
			
		||||
        "months": "මාස {amount}"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "දායක වූයේ: {0}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -72,8 +72,6 @@
 | 
			
		|||
        "view_ssl_score": "Zobraziť SSL skóre",
 | 
			
		||||
        "filter": "Filter",
 | 
			
		||||
        "delete_playlist_video_confirm": "Odstrániť video zo zoznamu?",
 | 
			
		||||
        "rename_playlist": "Premenovať zoznam skladieb",
 | 
			
		||||
        "new_playlist_name": "Nový názov zoznamu skladieb",
 | 
			
		||||
        "share": "Zdieľať",
 | 
			
		||||
        "follow_link": "Nasledujte odkaz",
 | 
			
		||||
        "loading": "Načítavanie...",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,9 @@
 | 
			
		|||
        "ratings_disabled": "Оцене су онемогућене",
 | 
			
		||||
        "chapters": "Поглавља",
 | 
			
		||||
        "live": "{0} Уживо",
 | 
			
		||||
        "shorts": "Кратки видео снимци"
 | 
			
		||||
        "shorts": "Кратки видео снимци",
 | 
			
		||||
        "all": "Све",
 | 
			
		||||
        "category": "Категорија"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "view_ssl_score": "Погледај SSL скор/оцену",
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +25,7 @@
 | 
			
		|||
        "load_more_replies": "Учитај још одговора",
 | 
			
		||||
        "unsubscribe": "Прекини са праћењем - {count}",
 | 
			
		||||
        "auto": "Аутоматски",
 | 
			
		||||
        "search": "Претрага",
 | 
			
		||||
        "search": "Претрага (Ctrl+K)",
 | 
			
		||||
        "skip_non_music": "Прескочи делове где нема музике у музичким видео клиповима",
 | 
			
		||||
        "theme": "Теме",
 | 
			
		||||
        "audio_only": "Само звук",
 | 
			
		||||
| 
						 | 
				
			
			@ -98,21 +100,28 @@
 | 
			
		|||
        "status_page": "Статус",
 | 
			
		||||
        "instance_donations": "Донације инстанци",
 | 
			
		||||
        "show_chapters": "Поглавља",
 | 
			
		||||
        "rename_playlist": "Преименуј плејлисту",
 | 
			
		||||
        "with_timecode": "Подели са временским кодом",
 | 
			
		||||
        "piped_link": "Piped веза",
 | 
			
		||||
        "back_to_home": "Врати се на почетну",
 | 
			
		||||
        "follow_link": "Прати везу",
 | 
			
		||||
        "copy_link": "Копирај везу",
 | 
			
		||||
        "time_code": "Временски код (у секундама)",
 | 
			
		||||
        "new_playlist_name": "Ново име плејлисте",
 | 
			
		||||
        "minimize_comments_default": "Подразумевано умањи коментаре",
 | 
			
		||||
        "minimize_comments": "Умањи коментаре",
 | 
			
		||||
        "reply_count": "{count} одговора",
 | 
			
		||||
        "minimize_chapters_default": "Умањи поглавља подразумевано",
 | 
			
		||||
        "show_watch_on_youtube": "Прикажите \"Гредај на YouTube-у\" дугме",
 | 
			
		||||
        "no_valid_playlists": "Датотека не садржи важеће пописе снимака!",
 | 
			
		||||
        "with_playlist": "Делите са пописом снимака"
 | 
			
		||||
        "with_playlist": "Делите са пописом снимака",
 | 
			
		||||
        "playlist_bookmarked": "Обиљежено",
 | 
			
		||||
        "bookmark_playlist": "Биљежак",
 | 
			
		||||
        "show_less": "Прикажи мање",
 | 
			
		||||
        "skip_button_only": "Прикажи дугме за прескакање",
 | 
			
		||||
        "skip_automatically": "Аутоматски",
 | 
			
		||||
        "min_segment_length": "Најмања дужина сегмента (у секундама)",
 | 
			
		||||
        "skip_segment": "Прескочи сегмент",
 | 
			
		||||
        "dismiss": "Одбаци",
 | 
			
		||||
        "autoplay_next_countdown": "Подразумевано одбројавање до следећег видеа (у секундама)"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_locations": "Локација инстанце",
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +160,8 @@
 | 
			
		|||
        "instance": "Инстанца",
 | 
			
		||||
        "player": "Покретник",
 | 
			
		||||
        "livestreams": "Уживо преноси",
 | 
			
		||||
        "channels": "Канали"
 | 
			
		||||
        "channels": "Канали",
 | 
			
		||||
        "bookmarks": "Биљешци"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Закачено од {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -170,6 +180,8 @@
 | 
			
		|||
        "copied": "Копирано!",
 | 
			
		||||
        "cannot_copy": "Није могуће копирати!",
 | 
			
		||||
        "preferences_note": "Напомена: подешавања се чувају у локалној меморији вашег претраживача. Брисање података прегледача ће их ресетовати.",
 | 
			
		||||
        "local_storage": "Ова радња захтева локално складиште, да ли су колачићи омогућени ?"
 | 
			
		||||
        "local_storage": "Ова радња захтева локално складиште, да ли су колачићи омогућени ?",
 | 
			
		||||
        "register_no_email_note": "Коришћење е-поруке као корисничког имена се не препоручује. Желите ли ипак наставити?",
 | 
			
		||||
        "next_video_countdown": "Репродукује се следећи видео за {0}с"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,12 @@
 | 
			
		|||
        "playlists": "Spellistor",
 | 
			
		||||
        "account": "Konto",
 | 
			
		||||
        "instance": "Instans",
 | 
			
		||||
        "player": "Spelare"
 | 
			
		||||
        "player": "Spelare",
 | 
			
		||||
        "bookmarks": "Bokmärken",
 | 
			
		||||
        "dearrow": "DeArrow",
 | 
			
		||||
        "livestreams": "Livesändningar",
 | 
			
		||||
        "channels": "Kanaler",
 | 
			
		||||
        "channel_groups": "Kanal Grupper"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "subscribe": "Prenumerera - {count}",
 | 
			
		||||
| 
						 | 
				
			
			@ -47,17 +52,17 @@
 | 
			
		|||
        "instances_list": "Lista över instanser",
 | 
			
		||||
        "enabled_codecs": "Aktivera codecs (flera)",
 | 
			
		||||
        "import_from_json": "Importera från JSON/CSV",
 | 
			
		||||
        "donations": "Donationer",
 | 
			
		||||
        "donations": "Donationer till utveckling",
 | 
			
		||||
        "filter": "Filter",
 | 
			
		||||
        "hide_replies": "Dölj svar",
 | 
			
		||||
        "load_more_replies": "Ladda fler svar",
 | 
			
		||||
        "enable_sponsorblock": "Aktivera sponsorblockering",
 | 
			
		||||
        "skip_preview": "Hoppa över förhandsgranskning/sammanfattning",
 | 
			
		||||
        "autoplay_video": "Spela upp video automatiskt",
 | 
			
		||||
        "country_selection": "Val av land",
 | 
			
		||||
        "language_selection": "Val av språk",
 | 
			
		||||
        "country_selection": "Land",
 | 
			
		||||
        "language_selection": "Språk",
 | 
			
		||||
        "skip_non_music": "Hoppa över musik: Icke-musikaliskt avsnitt",
 | 
			
		||||
        "instance_selection": "Val av instans",
 | 
			
		||||
        "instance_selection": "Instans",
 | 
			
		||||
        "show_more": "Visa mer",
 | 
			
		||||
        "yes": "Ja",
 | 
			
		||||
        "no": "Nej",
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +72,7 @@
 | 
			
		|||
        "show_recommendations": "Visa rekommendationer",
 | 
			
		||||
        "disable_lbry": "Inaktivera LBRY för strömning",
 | 
			
		||||
        "enable_lbry_proxy": "Aktivera proxy för LBRY",
 | 
			
		||||
        "search": "Sök",
 | 
			
		||||
        "search": "Sök (Ctrl+K)",
 | 
			
		||||
        "clear_history": "Rensa historik",
 | 
			
		||||
        "skip_filler_tangent": "Hoppa över påfyllningstangent",
 | 
			
		||||
        "skip_highlight": "Hoppa över höjdpunkt",
 | 
			
		||||
| 
						 | 
				
			
			@ -87,12 +92,55 @@
 | 
			
		|||
        "minimize_recommendations_default": "Minimera rekommendationer som standard",
 | 
			
		||||
        "invalidate_session": "Logga ut alla enheter",
 | 
			
		||||
        "different_auth_instance": "Använd en annan instans för autentisering",
 | 
			
		||||
        "instance_auth_selection": "Val av autentiseringsinstans",
 | 
			
		||||
        "instance_auth_selection": "Autentiseringsinstans",
 | 
			
		||||
        "download_as_txt": "Ladda ner som .txt",
 | 
			
		||||
        "reset_preferences": "Återställ inställningar",
 | 
			
		||||
        "confirm_reset_preferences": "Är du säker på att du vill återställa dina inställningar?",
 | 
			
		||||
        "backup_preferences": "Inställningar för säkerhetskopiering",
 | 
			
		||||
        "restore_preferences": "Återställa inställningar"
 | 
			
		||||
        "restore_preferences": "Återställa inställningar",
 | 
			
		||||
        "enable_dearrow": "Aktivera DeArrow",
 | 
			
		||||
        "autoplay_next_countdown": "Antal sekunder tills nästa video startar automatiskt",
 | 
			
		||||
        "minimize_comments_default": "Minimera kommentarer som standard",
 | 
			
		||||
        "show_watch_on_youtube": "Visa knappen \"Titta på YouTube\"",
 | 
			
		||||
        "back_to_home": "Tillbaka till startsidan",
 | 
			
		||||
        "delete_automatically": "Ta bort automatiskt efter",
 | 
			
		||||
        "with_timecode": "Dela med tidsstämpel",
 | 
			
		||||
        "reply_count": "{count} svar",
 | 
			
		||||
        "with_playlist": "Dela med spellista",
 | 
			
		||||
        "dismiss": "Avböj",
 | 
			
		||||
        "min_segment_length": "Minsta segmentlängd (i sekunder)",
 | 
			
		||||
        "skip_segment": "Hoppa över segment",
 | 
			
		||||
        "minimize_comments": "Minimera kommentarer",
 | 
			
		||||
        "show_less": "Visa mindre",
 | 
			
		||||
        "cancel": "Avbryt",
 | 
			
		||||
        "store_search_history": "Spara sökhistorik",
 | 
			
		||||
        "documentation": "Dokumentation",
 | 
			
		||||
        "okay": "Okej",
 | 
			
		||||
        "status_page": "Status",
 | 
			
		||||
        "minimize_chapters_default": "Minimera kapitel som standard",
 | 
			
		||||
        "time_code": "Tidsstämpel (i sekunder)",
 | 
			
		||||
        "hide_watched": "Dölj tittade videor i flödet",
 | 
			
		||||
        "share": "Dela",
 | 
			
		||||
        "show_chapters": "Kapitel",
 | 
			
		||||
        "source_code": "Källkod",
 | 
			
		||||
        "edit_playlist": "Redigera spellista",
 | 
			
		||||
        "playlist_name": "Spellistans namn",
 | 
			
		||||
        "playlist_description": "Beskrivning av spellista",
 | 
			
		||||
        "generate_qrcode": "Generera QR-kod",
 | 
			
		||||
        "chapters_layout_mobile": "Layout för kapitel på mobil",
 | 
			
		||||
        "piped_link": "Piped-länk",
 | 
			
		||||
        "follow_link": "Följ-länk",
 | 
			
		||||
        "copy_link": "Kopiera länk",
 | 
			
		||||
        "group_name": "Gruppnamn",
 | 
			
		||||
        "show_search_suggestions": "Visa sökförslag",
 | 
			
		||||
        "auto_display_captions": "Automatisk visning av textning",
 | 
			
		||||
        "bookmark_playlist": "Bokmärke",
 | 
			
		||||
        "instance_donations": "Instans donationer",
 | 
			
		||||
        "no_valid_playlists": "Filen innehåller inga giltiga spellistor!",
 | 
			
		||||
        "playlist_bookmarked": "Bokmärkt",
 | 
			
		||||
        "create_group": "Skapa grupp",
 | 
			
		||||
        "skip_button_only": "Visa hoppa över-knapp",
 | 
			
		||||
        "skip_automatically": "Automatiskt"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Titta på {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +166,13 @@
 | 
			
		|||
        "ratings_disabled": "Betyg inaktiverade",
 | 
			
		||||
        "chapters": "Kapitel",
 | 
			
		||||
        "live": "{0} Live",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "license": "Licens",
 | 
			
		||||
        "all": "Alla",
 | 
			
		||||
        "category": "Kategori",
 | 
			
		||||
        "chapters_horizontal": "Horisontell",
 | 
			
		||||
        "visibility": "Synlighet",
 | 
			
		||||
        "chapters_vertical": "Vertikal"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Fäst av {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -135,12 +189,26 @@
 | 
			
		|||
        "all": "YouTube: Alla",
 | 
			
		||||
        "videos": "YouTube: Videor",
 | 
			
		||||
        "playlists": "YouTube: Spellistor",
 | 
			
		||||
        "music_songs": "YT Music: Låtar"
 | 
			
		||||
        "music_songs": "YT Music: Låtar",
 | 
			
		||||
        "music_artists": "YT Music: Artister"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Prenumererar på: {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "information": {
 | 
			
		||||
        "preferences_note": "Observera: inställningar sparas i webbläsarens lokala lagring. Om du raderar dina webbläsardata återställs de."
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "register_no_email_note": "Det rekommenderas inte att använda e-post som användarnamn. Fortsätt ändå?",
 | 
			
		||||
        "hours": "{amount} timma(r)",
 | 
			
		||||
        "preferences_note": "Obs: Inställningarna sparas i det lokala lagringsutrymmet i din webbläsare. Om du raderar dina webbläsardata återställs de.",
 | 
			
		||||
        "days": "{amount} dag(ar)",
 | 
			
		||||
        "weeks": "{amount} vecka/veckor",
 | 
			
		||||
        "months": "{amount} månad(er)",
 | 
			
		||||
        "next_video_countdown": "Spelar nästa video om {0}s",
 | 
			
		||||
        "cannot_copy": "Kan inte kopiera!",
 | 
			
		||||
        "page_not_found": "Sida hittas ej",
 | 
			
		||||
        "copied": "Kopierad!",
 | 
			
		||||
        "local_storage": "Det här kräver localStorage, är cookies aktiverat?"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
{
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "instances_list": "Örnek Listesi",
 | 
			
		||||
        "language_selection": "Dil Seçimi",
 | 
			
		||||
        "instances_list": "Sunucu Listesi",
 | 
			
		||||
        "language_selection": "Dil",
 | 
			
		||||
        "store_watch_history": "İzleme Geçmişini Sakla",
 | 
			
		||||
        "minimize_description_default": "Açıklamayı Öntanımlı Olarak Küçült",
 | 
			
		||||
        "show_comments": "Yorumları Göster",
 | 
			
		||||
        "default_homepage": "Öntanımlı Ana Sayfa",
 | 
			
		||||
        "country_selection": "Ülke Seçimi",
 | 
			
		||||
        "country_selection": "Ülke",
 | 
			
		||||
        "buffering_goal": "Arabelleğe Alma Hedefi (Saniye Cinsinden)",
 | 
			
		||||
        "default_quality": "Öntanımlı Kalite",
 | 
			
		||||
        "audio_only": "Yalnızca Ses",
 | 
			
		||||
| 
						 | 
				
			
			@ -46,10 +46,10 @@
 | 
			
		|||
        "no": "Hayır",
 | 
			
		||||
        "yes": "Evet",
 | 
			
		||||
        "show_more": "Daha Fazla Göster",
 | 
			
		||||
        "instance_selection": "Örnek Seçimi",
 | 
			
		||||
        "instance_selection": "Sunucu",
 | 
			
		||||
        "loading": "Yükleniyor...",
 | 
			
		||||
        "filter": "Filtrele",
 | 
			
		||||
        "search": "Ara",
 | 
			
		||||
        "search": "Ara (Ctrl+K)",
 | 
			
		||||
        "view_ssl_score": "SSL Puanını Görüntüle",
 | 
			
		||||
        "minimize_recommendations": "Önerileri Küçült",
 | 
			
		||||
        "show_recommendations": "Önerileri Göster",
 | 
			
		||||
| 
						 | 
				
			
			@ -70,9 +70,9 @@
 | 
			
		|||
        "delete_account": "Hesabı Sil",
 | 
			
		||||
        "logout": "Bu Aygıttan Oturumu Kapat",
 | 
			
		||||
        "minimize_recommendations_default": "Önerileri Öntanımlı Olarak Küçült",
 | 
			
		||||
        "different_auth_instance": "Kimlik Doğrulama İçin Farklı Bir Örnek Kullan",
 | 
			
		||||
        "different_auth_instance": "Kimlik Doğrulama İçin Farklı Bir Sunucu Kullan",
 | 
			
		||||
        "invalidate_session": "Tüm Aygıtlardan Oturumu Kapat",
 | 
			
		||||
        "instance_auth_selection": "Kimlik Doğrulama Örneği Seçimi",
 | 
			
		||||
        "instance_auth_selection": "Kimlik Doğrulama Sunucusu",
 | 
			
		||||
        "clone_playlist": "Oynatma Listesini Kopyala",
 | 
			
		||||
        "clone_playlist_success": "Başarıyla kopyalandı!",
 | 
			
		||||
        "download_as_txt": ".txt Olarak İndir",
 | 
			
		||||
| 
						 | 
				
			
			@ -87,14 +87,12 @@
 | 
			
		|||
        "with_timecode": "Zaman Koduyla Paylaş",
 | 
			
		||||
        "piped_link": "Piped Bağlantısı",
 | 
			
		||||
        "share": "Paylaş",
 | 
			
		||||
        "rename_playlist": "Oynatma Listesini Yeniden Adlandır",
 | 
			
		||||
        "new_playlist_name": "Yeni Oynatma Listesi Adı",
 | 
			
		||||
        "show_chapters": "Bölümler",
 | 
			
		||||
        "store_search_history": "Arama Geçmişini Sakla",
 | 
			
		||||
        "hide_watched": "Akışta İzlenen Videoları Gizle",
 | 
			
		||||
        "source_code": "Kaynak Kodu",
 | 
			
		||||
        "documentation": "Belgelendirme",
 | 
			
		||||
        "instance_donations": "Örnek Bağışları",
 | 
			
		||||
        "instance_donations": "Sunucu Bağışları",
 | 
			
		||||
        "status_page": "Durum",
 | 
			
		||||
        "reply_count": "{count} Yanıt",
 | 
			
		||||
        "minimize_comments": "Yorumları Küçült",
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +100,29 @@
 | 
			
		|||
        "show_watch_on_youtube": "YouTube'da İzle Düğmesini Göster",
 | 
			
		||||
        "minimize_chapters_default": "Bölümleri Öntanımlı Olarak Küçült",
 | 
			
		||||
        "no_valid_playlists": "Dosya geçerli oynatma listeleri içermiyor!",
 | 
			
		||||
        "with_playlist": "Oynatma listesiyle paylaş"
 | 
			
		||||
        "with_playlist": "Oynatma listesiyle paylaş",
 | 
			
		||||
        "bookmark_playlist": "Yer imlerine ekle",
 | 
			
		||||
        "playlist_bookmarked": "Yer imlerine eklendi",
 | 
			
		||||
        "min_segment_length": "En Küçük Bölüm Uzunluğu (saniye cinsinden)",
 | 
			
		||||
        "skip_segment": "Bölümü Atla",
 | 
			
		||||
        "skip_button_only": "Atla düğmesini göster",
 | 
			
		||||
        "skip_automatically": "Otomatik olarak",
 | 
			
		||||
        "show_less": "Daha az göster",
 | 
			
		||||
        "dismiss": "Kapat",
 | 
			
		||||
        "autoplay_next_countdown": "Bir sonraki videoya kadar öntanımlı geri sayım (saniye cinsinden)",
 | 
			
		||||
        "create_group": "Grup oluştur",
 | 
			
		||||
        "group_name": "Grup adı",
 | 
			
		||||
        "auto_display_captions": "Alt Yazıları Otomatik Görüntüle",
 | 
			
		||||
        "cancel": "İptal et",
 | 
			
		||||
        "okay": "Tamam",
 | 
			
		||||
        "edit_playlist": "Oynatma listesini düzenle",
 | 
			
		||||
        "playlist_name": "Oynatma listesi adı",
 | 
			
		||||
        "playlist_description": "Oynatma listesi açıklaması",
 | 
			
		||||
        "show_search_suggestions": "Arama önerilerini göster",
 | 
			
		||||
        "chapters_layout_mobile": "Mobilde Bölüm Düzeni",
 | 
			
		||||
        "delete_automatically": "Şundan sonra otomatik olarak sil",
 | 
			
		||||
        "enable_dearrow": "DeArrow'u Etkinleştir",
 | 
			
		||||
        "generate_qrcode": "QR Kodu Oluştur"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "{0} Üzerinde İzle"
 | 
			
		||||
| 
						 | 
				
			
			@ -117,10 +137,13 @@
 | 
			
		|||
        "subscriptions": "Abonelikler",
 | 
			
		||||
        "playlists": "Oynatma Listeleri",
 | 
			
		||||
        "account": "Hesap",
 | 
			
		||||
        "instance": "Örnek",
 | 
			
		||||
        "instance": "Sunucu",
 | 
			
		||||
        "player": "Oynatıcı",
 | 
			
		||||
        "livestreams": "Canlı Yayınlar",
 | 
			
		||||
        "channels": "Kanallar"
 | 
			
		||||
        "channels": "Kanallar",
 | 
			
		||||
        "bookmarks": "Yer İmleri",
 | 
			
		||||
        "channel_groups": "Kanal grupları",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "sponsor_segments": "Sponsorlar Bölümleri",
 | 
			
		||||
| 
						 | 
				
			
			@ -130,13 +153,19 @@
 | 
			
		|||
        "ratings_disabled": "Derecelendirmeler Devre Dışı",
 | 
			
		||||
        "chapters": "Bölümler",
 | 
			
		||||
        "live": "{0} Canlı",
 | 
			
		||||
        "shorts": "Kısa çekimler"
 | 
			
		||||
        "shorts": "Kısa çekimler",
 | 
			
		||||
        "all": "Tümü",
 | 
			
		||||
        "category": "Kategori",
 | 
			
		||||
        "chapters_horizontal": "Yatay",
 | 
			
		||||
        "chapters_vertical": "Dikey",
 | 
			
		||||
        "license": "Lisans",
 | 
			
		||||
        "visibility": "Görünürlük"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "SSL Puanı",
 | 
			
		||||
        "has_cdn": "CDN Var Mı?",
 | 
			
		||||
        "instance_locations": "Örnek Konumları",
 | 
			
		||||
        "instance_name": "Örnek Adı",
 | 
			
		||||
        "instance_locations": "Sunucu Konumları",
 | 
			
		||||
        "instance_name": "Sunucu Adı",
 | 
			
		||||
        "registered_users": "Kayıtlı Kullanıcılar",
 | 
			
		||||
        "version": "Sürüm",
 | 
			
		||||
        "up_to_date": "Güncel Mi?"
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +178,9 @@
 | 
			
		|||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "password": "Parola",
 | 
			
		||||
        "username": "Kullanıcı Adı"
 | 
			
		||||
        "username": "Kullanıcı Adı",
 | 
			
		||||
        "password_confirm": "Parolayı doğrula",
 | 
			
		||||
        "passwords_incorrect": "Parolalar eşleşmiyor!"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Bunu mu demek istediniz: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +191,8 @@
 | 
			
		|||
        "videos": "YouTube: Videolar",
 | 
			
		||||
        "music_songs": "YT Müzik: Şarkılar",
 | 
			
		||||
        "music_videos": "YT Müzik: Videolar",
 | 
			
		||||
        "music_albums": "YT Müzik: Albümler"
 | 
			
		||||
        "music_albums": "YT Müzik: Albümler",
 | 
			
		||||
        "music_artists": "YT Müzik: Sanatçılar"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Abone Olunan: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -173,6 +205,12 @@
 | 
			
		|||
        "page_not_found": "Sayfa Bulunamadı",
 | 
			
		||||
        "copied": "Kopyalandı!",
 | 
			
		||||
        "cannot_copy": "Kopyalanamıyor!",
 | 
			
		||||
        "local_storage": "Bu eylem yerel depolama gerektirir, çerezler etkin mi?"
 | 
			
		||||
        "local_storage": "Bu eylem yerel depolama gerektirir, çerezler etkin mi?",
 | 
			
		||||
        "register_no_email_note": "Kullanıcı adı olarak e-posta kullanılması tavsiye edilmez. Yine de devam edilsin mi?",
 | 
			
		||||
        "next_video_countdown": "Sonraki video {0}s içinde oynatılıyor",
 | 
			
		||||
        "days": "{amount} gün",
 | 
			
		||||
        "months": "{amount} ay",
 | 
			
		||||
        "hours": "{amount} saat",
 | 
			
		||||
        "weeks": "{amount} hafta"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,127 +3,153 @@
 | 
			
		|||
        "watch_on": "Дивитися на {0}"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "Назва аккаунта Piped",
 | 
			
		||||
        "password": "Пароль"
 | 
			
		||||
        "username": "Ім'я користувача",
 | 
			
		||||
        "password": "Пароль",
 | 
			
		||||
        "password_confirm": "Підтвердіть пароль",
 | 
			
		||||
        "passwords_incorrect": "Паролі не збігаються!"
 | 
			
		||||
    },
 | 
			
		||||
    "actions": {
 | 
			
		||||
        "unsubscribe": "Відписатись - {count}",
 | 
			
		||||
        "unsubscribe": "Відписатися - {count}",
 | 
			
		||||
        "back": "Назад",
 | 
			
		||||
        "skip_intro": "Пропускати заставку/інтро",
 | 
			
		||||
        "skip_intro": "Пропускати паузу/заставку",
 | 
			
		||||
        "dark": "Темна",
 | 
			
		||||
        "view_subscriptions": "Продивитися підписки",
 | 
			
		||||
        "channel_name_asc": "Назва каналу (A-Z)",
 | 
			
		||||
        "uses_api_from": "Використовувати API з ",
 | 
			
		||||
        "view_subscriptions": "Переглянути Підписки",
 | 
			
		||||
        "channel_name_asc": "Назвою каналу (А-Я)",
 | 
			
		||||
        "uses_api_from": "Використовує API від ",
 | 
			
		||||
        "enable_sponsorblock": "Увімкнути Sponsorblock",
 | 
			
		||||
        "skip_outro": "Пропускати кінцівку/титри",
 | 
			
		||||
        "skip_preview": "Пропускати короткий вміст поточного епізода або повтор частини минулого",
 | 
			
		||||
        "skip_self_promo": "Пропускати саморекламу",
 | 
			
		||||
        "autoplay_video": "Автоматичний програш відео",
 | 
			
		||||
        "audio_only": "Лише звук",
 | 
			
		||||
        "default_homepage": "За замовчуванням відкривати",
 | 
			
		||||
        "show_comments": "Показувати коментарі",
 | 
			
		||||
        "store_watch_history": "Зберігати історию переглянутих відео",
 | 
			
		||||
        "language_selection": "Вибір мови",
 | 
			
		||||
        "instance_selection": "Вибір копії сервіса Piped",
 | 
			
		||||
        "skip_outro": "Пропускати кінцеву заставку/титри",
 | 
			
		||||
        "skip_preview": "Пропускати попередній перегляд/короткий зміст",
 | 
			
		||||
        "skip_self_promo": "Пропускати саморекламу/рекомендацію",
 | 
			
		||||
        "autoplay_video": "Автоматичне відтворення відео",
 | 
			
		||||
        "audio_only": "Лише аудіо",
 | 
			
		||||
        "default_homepage": "Домашня сторінка за замовчуванням",
 | 
			
		||||
        "show_comments": "Показати коментарі",
 | 
			
		||||
        "store_watch_history": "Зберігати історію перегляду",
 | 
			
		||||
        "language_selection": "Мова",
 | 
			
		||||
        "instance_selection": "Екземпляр",
 | 
			
		||||
        "show_more": "Показати більше",
 | 
			
		||||
        "no": "Ні",
 | 
			
		||||
        "export_to_json": "Експорт в JSON",
 | 
			
		||||
        "export_to_json": "Експортувати в JSON",
 | 
			
		||||
        "minimize_description": "Згорнути опис",
 | 
			
		||||
        "show_recommendations": "Показати рекомендації",
 | 
			
		||||
        "enable_lbry_proxy": "Проксувати відео з LBRY",
 | 
			
		||||
        "search": "Пошук",
 | 
			
		||||
        "enable_lbry_proxy": "Увімкнути проксі для LBRY",
 | 
			
		||||
        "search": "Пошук (Ctrl+K)",
 | 
			
		||||
        "clear_history": "Очистити історію перегляду",
 | 
			
		||||
        "load_more_replies": "Завантажити більше відповідей",
 | 
			
		||||
        "subscribe": "Підписатись - {count}",
 | 
			
		||||
        "sort_by": "Відсортувати по:",
 | 
			
		||||
        "most_recent": "Найновіші",
 | 
			
		||||
        "channel_name_desc": "Назва каналу (Z-A)",
 | 
			
		||||
        "least_recent": "Найстарші",
 | 
			
		||||
        "subscribe": "Підписатися - {count}",
 | 
			
		||||
        "sort_by": "Сортувати за:",
 | 
			
		||||
        "most_recent": "Найновішими",
 | 
			
		||||
        "channel_name_desc": "Назвою каналу (Я-А)",
 | 
			
		||||
        "least_recent": "Найстарішими",
 | 
			
		||||
        "minimize_recommendations": "Згорнути рекомендації",
 | 
			
		||||
        "skip_sponsors": "Пропускати спонсорську рекламу",
 | 
			
		||||
        "skip_interaction": "Пропускати прохання підписатися",
 | 
			
		||||
        "skip_non_music": "Пропускати тишу в музикальних відео",
 | 
			
		||||
        "skip_interaction": "Пропускати нагадування про взаємодію (підписка)",
 | 
			
		||||
        "skip_non_music": "Пропускати сегменти без музики в музикальних відео",
 | 
			
		||||
        "theme": "Тема",
 | 
			
		||||
        "auto": "Авто",
 | 
			
		||||
        "light": "Світла",
 | 
			
		||||
        "buffering_goal": "Розмір буфера відео (в секундах)",
 | 
			
		||||
        "instances_list": "Список копій сервіса Piped",
 | 
			
		||||
        "enabled_codecs": "Увімкнені кодеки (Можно вибрати декілька)",
 | 
			
		||||
        "buffering_goal": "Розмір буфера відео (у секундах)",
 | 
			
		||||
        "instances_list": "Список екземплярів",
 | 
			
		||||
        "enabled_codecs": "Увімкнені кодеки (можна вибрати декілька)",
 | 
			
		||||
        "default_quality": "Якість за замовчуванням",
 | 
			
		||||
        "country_selection": "Вибір країни (для трендів)",
 | 
			
		||||
        "country_selection": "Країна",
 | 
			
		||||
        "minimize_description_default": "Не розгортати опис за замовчуванням",
 | 
			
		||||
        "yes": "Так",
 | 
			
		||||
        "import_from_json": "Імпорт з JSON/CSV",
 | 
			
		||||
        "loop_this_video": "Повтор поточного відео",
 | 
			
		||||
        "auto_play_next_video": "Одразу програвати наступне рекомендоване відео",
 | 
			
		||||
        "import_from_json": "Імпортувати з JSON/CSV",
 | 
			
		||||
        "loop_this_video": "Зациклити це відео",
 | 
			
		||||
        "auto_play_next_video": "Автоматичне відтворення наступного відео",
 | 
			
		||||
        "donations": "Пожертвування на розробку",
 | 
			
		||||
        "show_description": "Показати опис",
 | 
			
		||||
        "disable_lbry": "Вимкнути LBRY для стримінгу",
 | 
			
		||||
        "filter": "Фільтр",
 | 
			
		||||
        "view_ssl_score": "Продивитися оцінку SSL",
 | 
			
		||||
        "view_ssl_score": "Переглянути оцінку SSL",
 | 
			
		||||
        "loading": "Завантаження...",
 | 
			
		||||
        "hide_replies": "Сховати відповіді",
 | 
			
		||||
        "skip_highlight": "Пропустити Хайлайт",
 | 
			
		||||
        "remove_from_playlist": "Видалити з плейлісту",
 | 
			
		||||
        "add_to_playlist": "Додати до плейлісту",
 | 
			
		||||
        "create_playlist": "Створити Плейліст",
 | 
			
		||||
        "delete_playlist_confirm": "Видалити цей плейлист?",
 | 
			
		||||
        "skip_filler_tangent": "Пропускати Нерелевантне",
 | 
			
		||||
        "delete_playlist_video_confirm": "Видалити відео з плейлисту?",
 | 
			
		||||
        "delete_playlist": "Видалити Плейліст",
 | 
			
		||||
        "select_playlist": "Вибрати Плейліст",
 | 
			
		||||
        "please_select_playlist": "Будь ласка виберіть плейліст",
 | 
			
		||||
        "confirm_reset_preferences": "Ви впевнені, що бажаєте скинути налаштування?",
 | 
			
		||||
        "show_markers": "Показувати Маркери на Програвачі",
 | 
			
		||||
        "minimize_recommendations_default": "Ховати Рекомендації за замовчуванням",
 | 
			
		||||
        "skip_highlight": "Пропускати основне",
 | 
			
		||||
        "remove_from_playlist": "Видалити зі списку відтворення",
 | 
			
		||||
        "add_to_playlist": "Додати до списку відтворення",
 | 
			
		||||
        "create_playlist": "Створити список відтворення",
 | 
			
		||||
        "delete_playlist_confirm": "Видалити цей список відтворення?",
 | 
			
		||||
        "skip_filler_tangent": "Пропускати дотичне наповнення/жарти",
 | 
			
		||||
        "delete_playlist_video_confirm": "Видалити відео зі списку відтворення?",
 | 
			
		||||
        "delete_playlist": "Видалити список відтворення",
 | 
			
		||||
        "select_playlist": "Вибрати список відтворення",
 | 
			
		||||
        "please_select_playlist": "Будь ласка, виберіть список відтворення",
 | 
			
		||||
        "confirm_reset_preferences": "Ви впевнені, що бажаєте скинути свої налаштування?",
 | 
			
		||||
        "show_markers": "Показувати маркери на програвачі",
 | 
			
		||||
        "minimize_recommendations_default": "Згортати рекомендації за замовчуванням",
 | 
			
		||||
        "logout": "Вийти з цього пристрою",
 | 
			
		||||
        "backup_preferences": "Налаштування резервних копій",
 | 
			
		||||
        "backup_preferences": "Налаштування резервного копіювання",
 | 
			
		||||
        "download_as_txt": "Завантажити як .txt",
 | 
			
		||||
        "rename_playlist": "Перейменувати плейлист",
 | 
			
		||||
        "show_chapters": "Глави",
 | 
			
		||||
        "invalidate_session": "Вийти зі всіх пристроїв",
 | 
			
		||||
        "clone_playlist": "Клонувати Плейлист",
 | 
			
		||||
        "show_chapters": "Розділи",
 | 
			
		||||
        "invalidate_session": "Вийти з усіх пристроїв",
 | 
			
		||||
        "clone_playlist": "Клонувати список відтворення",
 | 
			
		||||
        "reset_preferences": "Скинути налаштування",
 | 
			
		||||
        "back_to_home": "Повернутися на головну",
 | 
			
		||||
        "share": "Поділитися",
 | 
			
		||||
        "with_timecode": "Поділитися з відміткою часу",
 | 
			
		||||
        "piped_link": "Покликання Piped",
 | 
			
		||||
        "follow_link": "Покликання підписки",
 | 
			
		||||
        "instance_donations": "Пожертвування інстанції",
 | 
			
		||||
        "copy_link": "Копіювати покликання",
 | 
			
		||||
        "store_search_history": "Зберігати історію Пошуку",
 | 
			
		||||
        "piped_link": "Посилання Piped",
 | 
			
		||||
        "follow_link": "Перейти за посиланням",
 | 
			
		||||
        "instance_donations": "Пожертвування екземпляра",
 | 
			
		||||
        "copy_link": "Копіювати посилання",
 | 
			
		||||
        "store_search_history": "Зберігати історію пошуку",
 | 
			
		||||
        "documentation": "Документація",
 | 
			
		||||
        "instance_auth_selection": "Вибір Інстанції для Аутентифікації",
 | 
			
		||||
        "minimize_chapters_default": "Ховати глави за замовчуванням",
 | 
			
		||||
        "instance_auth_selection": "Екземпляр для автентифікації",
 | 
			
		||||
        "minimize_chapters_default": "Згортати розділи за замовчуванням",
 | 
			
		||||
        "show_watch_on_youtube": "Показати кнопку Дивитися на YouTube",
 | 
			
		||||
        "restore_preferences": "Відновити налаштування",
 | 
			
		||||
        "different_auth_instance": "Використовувати іншу інстанцію для аутентифікації",
 | 
			
		||||
        "clone_playlist_success": "Клонування пройшло успішно!",
 | 
			
		||||
        "different_auth_instance": "Використовувати інший екземпляр для автентифікації",
 | 
			
		||||
        "clone_playlist_success": "Успішно клоновано!",
 | 
			
		||||
        "hide_watched": "Сховати переглянуті відео в стрічці",
 | 
			
		||||
        "status_page": "Статус",
 | 
			
		||||
        "source_code": "Вихідний код",
 | 
			
		||||
        "new_playlist_name": "Нова назва плейлиста",
 | 
			
		||||
        "time_code": "Відмітка часу (в секундах)",
 | 
			
		||||
        "time_code": "Відмітка часу (у секундах)",
 | 
			
		||||
        "reply_count": "{count} відповідей",
 | 
			
		||||
        "minimize_comments_default": "Згортати коментарі за замовчуванням",
 | 
			
		||||
        "minimize_comments": "Згорнути коментарі",
 | 
			
		||||
        "delete_account": "Видалити Акаунт",
 | 
			
		||||
        "no_valid_playlists": "Файл не містить дійсних плейлистів!"
 | 
			
		||||
        "delete_account": "Видалити обліковий запис",
 | 
			
		||||
        "no_valid_playlists": "Файл не містить дійсних списків відтворення!",
 | 
			
		||||
        "bookmark_playlist": "Закладка",
 | 
			
		||||
        "playlist_bookmarked": "Додано в закладки",
 | 
			
		||||
        "with_playlist": "Поділитися зі списком відтворення",
 | 
			
		||||
        "skip_button_only": "Показати кнопку пропуску",
 | 
			
		||||
        "skip_segment": "Пропустити сегмент",
 | 
			
		||||
        "skip_automatically": "Автоматично",
 | 
			
		||||
        "min_segment_length": "Мінімальна довжина сегмента (у секундах)",
 | 
			
		||||
        "show_less": "Показати менше",
 | 
			
		||||
        "dismiss": "Відхилити",
 | 
			
		||||
        "autoplay_next_countdown": "Зворотний відлік до наступного відео (у секундах)",
 | 
			
		||||
        "create_group": "Створити групу",
 | 
			
		||||
        "group_name": "Назва групи",
 | 
			
		||||
        "edit_playlist": "Редагувати список відтворення",
 | 
			
		||||
        "playlist_name": "Назва списку відтворення",
 | 
			
		||||
        "playlist_description": "Опис списку відтворення",
 | 
			
		||||
        "auto_display_captions": "Автоматичне відображення субтитрів",
 | 
			
		||||
        "cancel": "Скасувати",
 | 
			
		||||
        "show_search_suggestions": "Показувати пошукові пропозицій",
 | 
			
		||||
        "okay": "Добре",
 | 
			
		||||
        "chapters_layout_mobile": "Макет розділів на телефоні",
 | 
			
		||||
        "enable_dearrow": "Увімкнути DeArrow",
 | 
			
		||||
        "delete_automatically": "Видаляти автоматично після",
 | 
			
		||||
        "generate_qrcode": "Згенерувати QR-код"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "register": "Реєстрація",
 | 
			
		||||
        "feed": "Підписки",
 | 
			
		||||
        "preferences": "Налаштування",
 | 
			
		||||
        "history": "Історія переглядів",
 | 
			
		||||
        "history": "Історія перегляду",
 | 
			
		||||
        "subscriptions": "Канали, на які ви підписані",
 | 
			
		||||
        "trending": "Тренди",
 | 
			
		||||
        "login": "Логін",
 | 
			
		||||
        "playlists": "Плейлісти",
 | 
			
		||||
        "instance": "Інстанція",
 | 
			
		||||
        "playlists": "Списки відтворення",
 | 
			
		||||
        "instance": "Екземпляр",
 | 
			
		||||
        "player": "Програвач",
 | 
			
		||||
        "account": "Акаунт",
 | 
			
		||||
        "account": "Обліковий запис",
 | 
			
		||||
        "livestreams": "Наживо",
 | 
			
		||||
        "channels": "Канали"
 | 
			
		||||
        "channels": "Канали",
 | 
			
		||||
        "bookmarks": "Закладки",
 | 
			
		||||
        "channel_groups": "Групи каналів",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "Прикріплено користувачем {author}",
 | 
			
		||||
| 
						 | 
				
			
			@ -132,13 +158,13 @@
 | 
			
		|||
        "user_disabled": "Коментарі вимкнені в налаштуваннях."
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_locations": "Місцезнаходження копії сервісу",
 | 
			
		||||
        "instance_locations": "Місцезнаходження екземпляру",
 | 
			
		||||
        "ssl_score": "Оцінка SSL",
 | 
			
		||||
        "instance_name": "Назва копії сервісу",
 | 
			
		||||
        "instance_name": "Назва екземпляру",
 | 
			
		||||
        "has_cdn": "Використовує CDN?",
 | 
			
		||||
        "version": "Версія",
 | 
			
		||||
        "up_to_date": "Версія актуальна?",
 | 
			
		||||
        "registered_users": "Зареєстровано Користувачей"
 | 
			
		||||
        "registered_users": "Зареєстровано користувачей"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "videos": "Відео",
 | 
			
		||||
| 
						 | 
				
			
			@ -148,18 +174,25 @@
 | 
			
		|||
        "ratings_disabled": "Оцінки вимкнені",
 | 
			
		||||
        "chapters": "Розділи",
 | 
			
		||||
        "live": "{0} Наживо",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Усі",
 | 
			
		||||
        "category": "Категорія",
 | 
			
		||||
        "chapters_horizontal": "Горизонтальний",
 | 
			
		||||
        "chapters_vertical": "Вертикальний",
 | 
			
		||||
        "visibility": "Видимість",
 | 
			
		||||
        "license": "Ліцензія"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Можливо, ви мали на увазі: {0}?",
 | 
			
		||||
        "music_playlists": "YT Music: Плейлісти",
 | 
			
		||||
        "music_playlists": "YT Music: Списки відтворення",
 | 
			
		||||
        "all": "YouTube: Все",
 | 
			
		||||
        "videos": "YouTube: Відео",
 | 
			
		||||
        "channels": "YouTube: Канали",
 | 
			
		||||
        "music_songs": "YT Music: Пісні",
 | 
			
		||||
        "music_videos": "TY Music: Відео",
 | 
			
		||||
        "playlists": "YouTube: Плейлісти",
 | 
			
		||||
        "music_albums": "YT Music: Альбоми"
 | 
			
		||||
        "playlists": "YouTube: Списки відтворення",
 | 
			
		||||
        "music_albums": "YT Music: Альбоми",
 | 
			
		||||
        "music_artists": "YT Music: Артисти"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Підписано на: {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +201,13 @@
 | 
			
		|||
        "copied": "Скопійовано!",
 | 
			
		||||
        "cannot_copy": "Не вийшло скопіювати!",
 | 
			
		||||
        "page_not_found": "Сторінка не знайдена",
 | 
			
		||||
        "preferences_note": "Зауваження: налаштування зберігаються в локальному сховищі вашого браузера. Видалення даних браузере їх скине.",
 | 
			
		||||
        "local_storage": "Ця дія потребує localStorage, чи ввімкнуті файли cookie?"
 | 
			
		||||
        "preferences_note": "Примітка: налаштування зберігаються в локальній пам'яті вашого браузера. Видалення даних браузера призведе до їх скидання.",
 | 
			
		||||
        "local_storage": "Ця дія потребує localStorage, чи ввімкнуті файли cookie?",
 | 
			
		||||
        "register_no_email_note": "Використання електронної пошти як імені користувача не рекомендується. Все одно продовжити?",
 | 
			
		||||
        "next_video_countdown": "Наступне відео через {0} секунд",
 | 
			
		||||
        "weeks": "{amount} тиждень(-і)",
 | 
			
		||||
        "hours": "{amount} годин(-и)",
 | 
			
		||||
        "months": "{amount} місяць(-і)",
 | 
			
		||||
        "days": "{amount} день(-і)"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
        "subscribe": "Đăng ký - {count}",
 | 
			
		||||
        "autoplay_video": "Video tự động phát",
 | 
			
		||||
        "unsubscribe": "Hủy đăng ký - {count}",
 | 
			
		||||
        "view_subscriptions": "Lượt đăng ký",
 | 
			
		||||
        "view_subscriptions": "Xem những kênh đã đăng ký",
 | 
			
		||||
        "sort_by": "Sắp xếp theo:",
 | 
			
		||||
        "most_recent": "Gần đây nhất",
 | 
			
		||||
        "channel_name_asc": "Tên kênh (A-Z)",
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
        "theme": "Giao diện",
 | 
			
		||||
        "auto": "Tự động",
 | 
			
		||||
        "buffering_goal": "Bộ nhớ đệm (tính bằng giây)",
 | 
			
		||||
        "least_recent": "Ít nhất gần đây",
 | 
			
		||||
        "least_recent": "Cũ nhất",
 | 
			
		||||
        "skip_intro": "Bỏ qua gián đoạn/hoạt hình intro",
 | 
			
		||||
        "skip_outro": "Bỏ qua màn hình kết thúc/danh đề",
 | 
			
		||||
        "skip_interaction": "Bỏ qua lời nhắc tương tác (Đăng ký)",
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@
 | 
			
		|||
        "show_comments": "Hiển thị bình luận",
 | 
			
		||||
        "store_watch_history": "Lịch sử xem trên cửa hàng",
 | 
			
		||||
        "language_selection": "Lựa chọn ngôn ngữ",
 | 
			
		||||
        "instances_list": "Danh sách phiên bản",
 | 
			
		||||
        "instances_list": "Danh sách instance",
 | 
			
		||||
        "show_more": "Hiện thị nhiều hơn",
 | 
			
		||||
        "import_from_json": "Nhập từ JSON/CSV",
 | 
			
		||||
        "loop_this_video": "Lặp lại video này",
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,7 @@
 | 
			
		|||
        "disable_lbry": "Tắt LBRY để phát trực tuyến",
 | 
			
		||||
        "enable_lbry_proxy": "Bật proxy cho LBRY",
 | 
			
		||||
        "view_ssl_score": "Hiện thị điểm số SSL",
 | 
			
		||||
        "search": "Tìm kiếm",
 | 
			
		||||
        "search": "Tìm kiếm (Ctrl+K)",
 | 
			
		||||
        "filter": "Bộ lọc",
 | 
			
		||||
        "loading": "Đang tải...",
 | 
			
		||||
        "clear_history": "Xóa lịch sử",
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +53,7 @@
 | 
			
		|||
        "light": "Sáng",
 | 
			
		||||
        "audio_only": "Chỉ có âm thanh",
 | 
			
		||||
        "minimize_description_default": "Thu nhỏ mô tả theo mặc định",
 | 
			
		||||
        "instance_selection": "Lựa chọn phiên bản",
 | 
			
		||||
        "instance_selection": "Lựa chọn instance",
 | 
			
		||||
        "yes": "Có",
 | 
			
		||||
        "enabled_codecs": "Các codec được bật (Nhiều)",
 | 
			
		||||
        "export_to_json": "Xuất định dạng JSON",
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +78,11 @@
 | 
			
		|||
        "minimize_comments": "Thu nhỏ bình luận",
 | 
			
		||||
        "reply_count": "{count} phản hồi",
 | 
			
		||||
        "status_page": "Trạng thái",
 | 
			
		||||
        "new_playlist_name": "Tên danh sách phát mới"
 | 
			
		||||
        "skip_automatically": "Tự động",
 | 
			
		||||
        "show_chapters": "Chương",
 | 
			
		||||
        "show_less": "Hiển thị ít hơn",
 | 
			
		||||
        "cancel": "Hủy",
 | 
			
		||||
        "okay": "OK"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "register": "Đăng ký",
 | 
			
		||||
| 
						 | 
				
			
			@ -92,7 +96,8 @@
 | 
			
		|||
        "account": "Tài khoản",
 | 
			
		||||
        "channels": "Kênh",
 | 
			
		||||
        "instance": "Instance",
 | 
			
		||||
        "player": "Trình phát video"
 | 
			
		||||
        "player": "Trình phát video",
 | 
			
		||||
        "livestreams": "Phát sóng trực tiếp"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "Xem trên {0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -104,8 +109,8 @@
 | 
			
		|||
        "disabled": "Bình luận đã bị tắt bởi người đăng video."
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "instance_name": "Tên phiên bản",
 | 
			
		||||
        "instance_locations": "Vị trí phiên bản",
 | 
			
		||||
        "instance_name": "Tên instance",
 | 
			
		||||
        "instance_locations": "Vị trí instance",
 | 
			
		||||
        "has_cdn": "Có CDN?",
 | 
			
		||||
        "registered_users": "Người dùng đã đăng ký",
 | 
			
		||||
        "version": "Phiên bản",
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +129,8 @@
 | 
			
		|||
        "live": "{0} Trực tiếp",
 | 
			
		||||
        "chapters": "Chương",
 | 
			
		||||
        "videos": "Video",
 | 
			
		||||
        "shorts": "Shorts"
 | 
			
		||||
        "shorts": "Shorts",
 | 
			
		||||
        "all": "Tất cả"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "Ý của bạn là: {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +146,8 @@
 | 
			
		|||
    "info": {
 | 
			
		||||
        "copied": "Đã sao chép!",
 | 
			
		||||
        "cannot_copy": "Không thể sao chép!",
 | 
			
		||||
        "page_not_found": "Không tìm thấy trang"
 | 
			
		||||
        "page_not_found": "Không tìm thấy trang",
 | 
			
		||||
        "next_video_countdown": "Tự động phát video tiếp theo trong {0} giây"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "Đã đăng ký cho: {0}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,15 +14,15 @@
 | 
			
		|||
        "no": "否",
 | 
			
		||||
        "yes": "是",
 | 
			
		||||
        "show_more": "显示更多",
 | 
			
		||||
        "instance_selection": "实例选择",
 | 
			
		||||
        "instance_selection": "实例",
 | 
			
		||||
        "enabled_codecs": "启用的编解码器 (多个)",
 | 
			
		||||
        "instances_list": "实例列表",
 | 
			
		||||
        "language_selection": "语言选择",
 | 
			
		||||
        "language_selection": "语言",
 | 
			
		||||
        "store_watch_history": "保存观看历史",
 | 
			
		||||
        "minimize_description_default": "默认情况下折叠描述",
 | 
			
		||||
        "show_comments": "显示评论",
 | 
			
		||||
        "default_homepage": "默认主页",
 | 
			
		||||
        "country_selection": "国家/地区选择",
 | 
			
		||||
        "country_selection": "国家/地区",
 | 
			
		||||
        "buffering_goal": "缓冲目标 (以秒为单位)",
 | 
			
		||||
        "default_quality": "默认质量",
 | 
			
		||||
        "audio_only": "仅音频",
 | 
			
		||||
| 
						 | 
				
			
			@ -44,12 +44,12 @@
 | 
			
		|||
        "least_recent": "最早的",
 | 
			
		||||
        "most_recent": "最新的",
 | 
			
		||||
        "sort_by": "排序:",
 | 
			
		||||
        "view_subscriptions": "查看订阅",
 | 
			
		||||
        "view_subscriptions": "查看订阅列表",
 | 
			
		||||
        "unsubscribe": "取消订阅 - {count}",
 | 
			
		||||
        "subscribe": "订购 - {count}",
 | 
			
		||||
        "subscribe": "订阅 - {count}",
 | 
			
		||||
        "loading": "正在加载...",
 | 
			
		||||
        "filter": "筛选",
 | 
			
		||||
        "search": "搜索",
 | 
			
		||||
        "search": "搜索 (Ctrl+K)",
 | 
			
		||||
        "view_ssl_score": "查看 SSL 得分",
 | 
			
		||||
        "minimize_recommendations": "最小化建议",
 | 
			
		||||
        "show_recommendations": "显示推荐",
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +72,7 @@
 | 
			
		|||
        "minimize_recommendations_default": "默认最小化推荐",
 | 
			
		||||
        "invalidate_session": "注销所有设备",
 | 
			
		||||
        "different_auth_instance": "使用不同的实例进行身份验证",
 | 
			
		||||
        "instance_auth_selection": "身份验证实例选择",
 | 
			
		||||
        "instance_auth_selection": "身份验证实例",
 | 
			
		||||
        "clone_playlist": "克隆播放列表",
 | 
			
		||||
        "clone_playlist_success": "克隆成功!",
 | 
			
		||||
        "download_as_txt": "下载为 .txt",
 | 
			
		||||
| 
						 | 
				
			
			@ -87,8 +87,6 @@
 | 
			
		|||
        "share": "分享",
 | 
			
		||||
        "with_timecode": "用时间码分享",
 | 
			
		||||
        "time_code": "时间码(单位:秒)",
 | 
			
		||||
        "rename_playlist": "重命名播放列表",
 | 
			
		||||
        "new_playlist_name": "新播放列表名",
 | 
			
		||||
        "show_chapters": "章节",
 | 
			
		||||
        "store_search_history": "保存搜索历史",
 | 
			
		||||
        "hide_watched": "在源中隐藏看过的视频",
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +99,30 @@
 | 
			
		|||
        "minimize_comments_default": "默认最小化评论",
 | 
			
		||||
        "show_watch_on_youtube": "显示“在 YouTube 上观看”按钮",
 | 
			
		||||
        "minimize_chapters_default": "默认最小化章节",
 | 
			
		||||
        "no_valid_playlists": "此文件不含无效的播放列表!"
 | 
			
		||||
        "no_valid_playlists": "此文件不包含有效的播放列表!",
 | 
			
		||||
        "with_playlist": "分享播放列表",
 | 
			
		||||
        "playlist_bookmarked": "已加入书签",
 | 
			
		||||
        "bookmark_playlist": "书签",
 | 
			
		||||
        "skip_automatically": "自动",
 | 
			
		||||
        "min_segment_length": "最小分段长度(以秒为单位)",
 | 
			
		||||
        "skip_segment": "跳过分段",
 | 
			
		||||
        "skip_button_only": "显示跳过按钮",
 | 
			
		||||
        "show_less": "显示更少",
 | 
			
		||||
        "autoplay_next_countdown": "下一个视频开始前的默认倒计时长(以秒计)",
 | 
			
		||||
        "dismiss": "解除",
 | 
			
		||||
        "group_name": "组名称",
 | 
			
		||||
        "create_group": "创建组",
 | 
			
		||||
        "auto_display_captions": "自动显示字幕",
 | 
			
		||||
        "playlist_name": "播放列表名称",
 | 
			
		||||
        "playlist_description": "播放列表描述",
 | 
			
		||||
        "cancel": "取消",
 | 
			
		||||
        "okay": "好的",
 | 
			
		||||
        "edit_playlist": "编辑播放列表",
 | 
			
		||||
        "show_search_suggestions": "显示搜索建议",
 | 
			
		||||
        "chapters_layout_mobile": "移动设备上的章节布局",
 | 
			
		||||
        "delete_automatically": "多久后自动删除",
 | 
			
		||||
        "enable_dearrow": "启用 DeArrow",
 | 
			
		||||
        "generate_qrcode": "生成二维码"
 | 
			
		||||
    },
 | 
			
		||||
    "video": {
 | 
			
		||||
        "sponsor_segments": "赞助商部分",
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +132,13 @@
 | 
			
		|||
        "live": "{0} 直播",
 | 
			
		||||
        "chapters": "章节",
 | 
			
		||||
        "ratings_disabled": "已禁用评价",
 | 
			
		||||
        "shorts": "短视频"
 | 
			
		||||
        "shorts": "短视频",
 | 
			
		||||
        "all": "全部",
 | 
			
		||||
        "category": "类别",
 | 
			
		||||
        "chapters_horizontal": "水平",
 | 
			
		||||
        "chapters_vertical": "垂直",
 | 
			
		||||
        "license": "许可证",
 | 
			
		||||
        "visibility": "可见性"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "ssl_score": "SSL 分数",
 | 
			
		||||
| 
						 | 
				
			
			@ -132,8 +159,8 @@
 | 
			
		|||
        "watch_on": "在 {0} 观看"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "feed": "RSS 订阅源",
 | 
			
		||||
        "subscriptions": "订阅",
 | 
			
		||||
        "feed": "订阅流",
 | 
			
		||||
        "subscriptions": "订阅列表",
 | 
			
		||||
        "history": "历史",
 | 
			
		||||
        "preferences": "设置",
 | 
			
		||||
        "register": "注册",
 | 
			
		||||
| 
						 | 
				
			
			@ -144,11 +171,16 @@
 | 
			
		|||
        "instance": "实例",
 | 
			
		||||
        "player": "播放器",
 | 
			
		||||
        "livestreams": "直播",
 | 
			
		||||
        "channels": "频道"
 | 
			
		||||
        "channels": "频道",
 | 
			
		||||
        "bookmarks": "书签",
 | 
			
		||||
        "channel_groups": "频道组",
 | 
			
		||||
        "dearrow": "DeArrow"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "password": "密码",
 | 
			
		||||
        "username": "帐号"
 | 
			
		||||
        "username": "帐号",
 | 
			
		||||
        "password_confirm": "确认密码",
 | 
			
		||||
        "passwords_incorrect": "密码不匹配!"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "你是指 {0} 吗?",
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +191,8 @@
 | 
			
		|||
        "music_songs": "YT Music:歌曲",
 | 
			
		||||
        "music_videos": "YT Music:视频",
 | 
			
		||||
        "music_albums": "YT Music:专辑",
 | 
			
		||||
        "music_playlists": "YT Music:播放列表"
 | 
			
		||||
        "music_playlists": "YT Music:播放列表",
 | 
			
		||||
        "music_artists": "YT Music:艺人"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "已订阅:{0}"
 | 
			
		||||
| 
						 | 
				
			
			@ -172,6 +205,12 @@
 | 
			
		|||
        "page_not_found": "未找到页面",
 | 
			
		||||
        "copied": "已复制!",
 | 
			
		||||
        "cannot_copy": "无法复制!",
 | 
			
		||||
        "local_storage": "此操作需要 localStorage,启用 cookie 了吗?"
 | 
			
		||||
        "local_storage": "此操作需要本地存储,是否启用了Cookie?",
 | 
			
		||||
        "register_no_email_note": "不建议使用电子邮件作为用户名。仍要继续吗?",
 | 
			
		||||
        "next_video_countdown": "在{0}秒后播放下一个视频",
 | 
			
		||||
        "days": "{amount} 天",
 | 
			
		||||
        "weeks": "{amount} 周",
 | 
			
		||||
        "months": "{amount} 个月",
 | 
			
		||||
        "hours": "{amount} 小时"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,7 @@
 | 
			
		|||
        "default_homepage": "預設首頁",
 | 
			
		||||
        "store_watch_history": "儲存觀看記錄",
 | 
			
		||||
        "minimize_description": "最小化說明",
 | 
			
		||||
        "search": "搜尋",
 | 
			
		||||
        "search": "搜尋 (Ctrl+K)",
 | 
			
		||||
        "show_recommendations": "顯示推薦",
 | 
			
		||||
        "minimize_recommendations": "最小化推薦",
 | 
			
		||||
        "most_recent": "最新",
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +45,7 @@
 | 
			
		|||
        "import_from_json": "從 JSON/CSV 匯入",
 | 
			
		||||
        "loop_this_video": "循環播放此影片",
 | 
			
		||||
        "auto_play_next_video": "自動播放下一部影片",
 | 
			
		||||
        "donations": "捐款",
 | 
			
		||||
        "donations": "開發捐款",
 | 
			
		||||
        "filter": "篩選",
 | 
			
		||||
        "loading": "載入中……",
 | 
			
		||||
        "clear_history": "清除記錄",
 | 
			
		||||
| 
						 | 
				
			
			@ -57,19 +57,69 @@
 | 
			
		|||
        "delete_playlist_confirm": "要刪除這份播放清單嗎?",
 | 
			
		||||
        "please_select_playlist": "請選擇播放清單",
 | 
			
		||||
        "select_playlist": "選擇播放清單",
 | 
			
		||||
        "add_to_playlist": "加到播放清單",
 | 
			
		||||
        "add_to_playlist": "增至播放清單",
 | 
			
		||||
        "delete_playlist_video_confirm": "要從播放清單中移除影片嗎?",
 | 
			
		||||
        "delete_account": "刪除帳戶",
 | 
			
		||||
        "show_chapters": "章節",
 | 
			
		||||
        "download_as_txt": "以 .txt 下載",
 | 
			
		||||
        "share": "分享",
 | 
			
		||||
        "new_playlist_name": "播放清單的新名稱",
 | 
			
		||||
        "rename_playlist": "重新命名播放清單",
 | 
			
		||||
        "reset_preferences": "重設偏好設定",
 | 
			
		||||
        "confirm_reset_preferences": "確定要重設偏好設定嗎?",
 | 
			
		||||
        "backup_preferences": "備份偏好設定",
 | 
			
		||||
        "restore_preferences": "復原偏好設定",
 | 
			
		||||
        "back_to_home": "回首頁"
 | 
			
		||||
        "back_to_home": "回首頁",
 | 
			
		||||
        "uses_api_from": "使用此API ",
 | 
			
		||||
        "instances_list": "站台列表",
 | 
			
		||||
        "show_markers": "在播放器上顯示標記",
 | 
			
		||||
        "skip_button_only": "顯示跳過按鈕",
 | 
			
		||||
        "skip_filler_tangent": "跳過與影片無關的片段",
 | 
			
		||||
        "autoplay_next_countdown": "預設播放下一段影片前的倒數時間(秒)",
 | 
			
		||||
        "min_segment_length": "最短分段的長度(秒)",
 | 
			
		||||
        "auto_display_captions": "自動顯示字幕",
 | 
			
		||||
        "minimize_comments": "收起留言",
 | 
			
		||||
        "disable_lbry": "不使用 LBRY 作為傳輸媒介",
 | 
			
		||||
        "enable_lbry_proxy": "使用 LBRY 作為代理伺服器",
 | 
			
		||||
        "view_ssl_score": "查看 SSL 分數",
 | 
			
		||||
        "logout": "從這個設置登出",
 | 
			
		||||
        "minimize_comments_default": "預設為收起留言",
 | 
			
		||||
        "instance_selection": "選擇站台",
 | 
			
		||||
        "skip_automatically": "自動",
 | 
			
		||||
        "skip_segment": "跳過分段",
 | 
			
		||||
        "edit_playlist": "編輯播放清單",
 | 
			
		||||
        "playlist_name": "播放清單名稱",
 | 
			
		||||
        "instance_auth_selection": "選擇身分驗證站台",
 | 
			
		||||
        "instance_donations": "站台捐款",
 | 
			
		||||
        "invalidate_session": "從所有裝置登出",
 | 
			
		||||
        "clone_playlist": "複製播放清單",
 | 
			
		||||
        "clone_playlist_success": "複製成功!",
 | 
			
		||||
        "different_auth_instance": "使用其他站台進行身分驗證",
 | 
			
		||||
        "with_timecode": "以時間碼分享",
 | 
			
		||||
        "follow_link": "追隨連結",
 | 
			
		||||
        "store_search_history": "儲存搜尋歷史",
 | 
			
		||||
        "okay": "好的",
 | 
			
		||||
        "no_valid_playlists": "此檔案不包含有效的播放清單!",
 | 
			
		||||
        "piped_link": "Piped 連結",
 | 
			
		||||
        "cancel": "取消",
 | 
			
		||||
        "playlist_description": "播放清單描述",
 | 
			
		||||
        "status_page": "狀態",
 | 
			
		||||
        "source_code": "原始碼",
 | 
			
		||||
        "bookmark_playlist": "書籤",
 | 
			
		||||
        "documentation": "文件",
 | 
			
		||||
        "with_playlist": "以播放清單分享",
 | 
			
		||||
        "playlist_bookmarked": "已存入書籤",
 | 
			
		||||
        "create_group": "建立群組",
 | 
			
		||||
        "group_name": "群組名稱",
 | 
			
		||||
        "show_search_suggestions": "顯示搜尋建議",
 | 
			
		||||
        "copy_link": "複製連結",
 | 
			
		||||
        "time_code": "時間碼(以秒計算)",
 | 
			
		||||
        "minimize_chapters_default": "預設收起章節",
 | 
			
		||||
        "dismiss": "解散",
 | 
			
		||||
        "chapters_layout_mobile": "在手提裝置上的章節佈局",
 | 
			
		||||
        "hide_watched": "在摘要中隱藏看過的影片",
 | 
			
		||||
        "reply_count": "{count} 個回覆",
 | 
			
		||||
        "minimize_recommendations_default": "預設收起推薦影片",
 | 
			
		||||
        "show_watch_on_youtube": "顯示「在Youtube 觀看」按鈕",
 | 
			
		||||
        "show_less": "顯示更少"
 | 
			
		||||
    },
 | 
			
		||||
    "titles": {
 | 
			
		||||
        "history": "歷史記錄",
 | 
			
		||||
| 
						 | 
				
			
			@ -81,12 +131,21 @@
 | 
			
		|||
        "subscriptions": "訂閱內容",
 | 
			
		||||
        "playlists": "播放清單",
 | 
			
		||||
        "account": "帳戶",
 | 
			
		||||
        "player": "播放器"
 | 
			
		||||
        "player": "播放器",
 | 
			
		||||
        "instance": "站台",
 | 
			
		||||
        "bookmarks": "書籤",
 | 
			
		||||
        "livestreams": "直播",
 | 
			
		||||
        "channels": "頻道",
 | 
			
		||||
        "channel_groups": "頻道群組"
 | 
			
		||||
    },
 | 
			
		||||
    "preferences": {
 | 
			
		||||
        "registered_users": "已註冊的使用者",
 | 
			
		||||
        "version": "版本",
 | 
			
		||||
        "has_cdn": "是否有 CDN?"
 | 
			
		||||
        "has_cdn": "是否有 CDN?",
 | 
			
		||||
        "instance_name": "站台名稱",
 | 
			
		||||
        "instance_locations": "站台位置",
 | 
			
		||||
        "up_to_date": "最新?",
 | 
			
		||||
        "ssl_score": "SSL 分數"
 | 
			
		||||
    },
 | 
			
		||||
    "login": {
 | 
			
		||||
        "username": "使用者名",
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +156,14 @@
 | 
			
		|||
        "watched": "有看過",
 | 
			
		||||
        "sponsor_segments": "贊助廣告片段",
 | 
			
		||||
        "ratings_disabled": "評價已停用",
 | 
			
		||||
        "chapters": "章節"
 | 
			
		||||
        "chapters": "章節",
 | 
			
		||||
        "shorts": "短影片",
 | 
			
		||||
        "category": "類別",
 | 
			
		||||
        "chapters_horizontal": "水平",
 | 
			
		||||
        "chapters_vertical": "垂直",
 | 
			
		||||
        "views": "觀看次數:{views}",
 | 
			
		||||
        "live": "{0} 直播",
 | 
			
		||||
        "all": "全部"
 | 
			
		||||
    },
 | 
			
		||||
    "search": {
 | 
			
		||||
        "did_you_mean": "您是否想找 {0}?",
 | 
			
		||||
| 
						 | 
				
			
			@ -108,17 +174,28 @@
 | 
			
		|||
        "playlists": "YouTube:播放清單",
 | 
			
		||||
        "music_songs": "YT Music:歌曲",
 | 
			
		||||
        "music_videos": "YT Music:影片",
 | 
			
		||||
        "music_albums": "YT Music:專輯"
 | 
			
		||||
        "music_albums": "YT Music:專輯",
 | 
			
		||||
        "music_artists": "YT Music: 藝人"
 | 
			
		||||
    },
 | 
			
		||||
    "comment": {
 | 
			
		||||
        "pinned_by": "置頂者: {author}",
 | 
			
		||||
        "disabled": "上傳者停用了留言功能。",
 | 
			
		||||
        "loading": "留言載入中……"
 | 
			
		||||
        "loading": "留言載入中……",
 | 
			
		||||
        "user_disabled": "留言在設置中被關閉。"
 | 
			
		||||
    },
 | 
			
		||||
    "info": {
 | 
			
		||||
        "copied": "已複製!",
 | 
			
		||||
        "cannot_copy": "無法複製!",
 | 
			
		||||
        "page_not_found": "找不到頁面",
 | 
			
		||||
        "preferences_note": "註:偏好設定儲存在本機的瀏覽器的儲存空間內。如果清除瀏覽器的資料,偏好設定就會重設。"
 | 
			
		||||
        "preferences_note": "註:偏好設定儲存在本機的瀏覽器的儲存空間內。如果清除瀏覽器的資料,偏好設定就會重設。",
 | 
			
		||||
        "register_no_email_note": "不建議使用電子郵件地址作為用戶名稱。仍要繼續嗎?",
 | 
			
		||||
        "local_storage": "此動作需要儲存資料在本機,請確認是否啟用了Cookies?",
 | 
			
		||||
        "next_video_countdown": "在{0}秒後播放下一段影片"
 | 
			
		||||
    },
 | 
			
		||||
    "player": {
 | 
			
		||||
        "watch_on": "在 {0} 觀看"
 | 
			
		||||
    },
 | 
			
		||||
    "subscriptions": {
 | 
			
		||||
        "subscribed_channels_count": "己訂閱: {0}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										334
									
								
								src/main.js
									
										
									
									
									
								
							
							
						
						
									
										334
									
								
								src/main.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -20,8 +20,10 @@ import {
 | 
			
		|||
    faBook,
 | 
			
		||||
    faServer,
 | 
			
		||||
    faDonate,
 | 
			
		||||
    faBookmark,
 | 
			
		||||
    faEdit,
 | 
			
		||||
} from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
import { faGithub, faBitcoin, faYoutube } from "@fortawesome/free-brands-svg-icons";
 | 
			
		||||
import { faGithub, faBitcoin, faYoutube, faOdysee } from "@fortawesome/free-brands-svg-icons";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 | 
			
		||||
library.add(
 | 
			
		||||
    faEye,
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +34,7 @@ library.add(
 | 
			
		|||
    faHeart,
 | 
			
		||||
    faHeadphones,
 | 
			
		||||
    faYoutube,
 | 
			
		||||
    faOdysee,
 | 
			
		||||
    faRss,
 | 
			
		||||
    faChevronLeft,
 | 
			
		||||
    faLevelDownAlt,
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +49,8 @@ library.add(
 | 
			
		|||
    faBook,
 | 
			
		||||
    faServer,
 | 
			
		||||
    faDonate,
 | 
			
		||||
    faBookmark,
 | 
			
		||||
    faEdit,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
import router from "@/router/router.js";
 | 
			
		||||
| 
						 | 
				
			
			@ -60,8 +65,6 @@ import "./piped.js";
 | 
			
		|||
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
 | 
			
		||||
import DOMPurify from "dompurify";
 | 
			
		||||
 | 
			
		||||
import TimeAgo from "javascript-time-ago";
 | 
			
		||||
 | 
			
		||||
import en from "javascript-time-ago/locale/en";
 | 
			
		||||
| 
						 | 
				
			
			@ -122,9 +125,6 @@ const mixin = {
 | 
			
		|||
                return response.json();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        purifyHTML(original) {
 | 
			
		||||
            return DOMPurify.sanitize(original);
 | 
			
		||||
        },
 | 
			
		||||
        setPreference(key, value, disableAlert = false) {
 | 
			
		||||
            try {
 | 
			
		||||
                localStorage.setItem(key, value);
 | 
			
		||||
| 
						 | 
				
			
			@ -164,7 +164,17 @@ const mixin = {
 | 
			
		|||
                (value = new URLSearchParams(window.location.search).get(key)) !== null ||
 | 
			
		||||
                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
			
		||||
            ) {
 | 
			
		||||
                return Number(value);
 | 
			
		||||
                const num = Number(value);
 | 
			
		||||
                return isNaN(num) ? defaultVal : num;
 | 
			
		||||
            } else return defaultVal;
 | 
			
		||||
        },
 | 
			
		||||
        getPreferenceJSON(key, defaultVal) {
 | 
			
		||||
            var value;
 | 
			
		||||
            if (
 | 
			
		||||
                (value = new URLSearchParams(window.location.search).get(key)) !== null ||
 | 
			
		||||
                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
			
		||||
            ) {
 | 
			
		||||
                return JSON.parse(value);
 | 
			
		||||
            } else return defaultVal;
 | 
			
		||||
        },
 | 
			
		||||
        apiUrl() {
 | 
			
		||||
| 
						 | 
				
			
			@ -187,21 +197,8 @@ const mixin = {
 | 
			
		|||
        timeAgo(time) {
 | 
			
		||||
            return timeAgo.format(time);
 | 
			
		||||
        },
 | 
			
		||||
        urlify(string) {
 | 
			
		||||
            if (!string) return "";
 | 
			
		||||
            const urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
 | 
			
		||||
            const emailRegex = /([\w-\\.]+@(?:[\w-]+\.)+[\w-]{2,4})/g;
 | 
			
		||||
            return string
 | 
			
		||||
                .replace(urlRegex, url => {
 | 
			
		||||
                    if (url.endsWith("</a>") || url.endsWith("<a")) return url;
 | 
			
		||||
                    return `<a href="${url}" target="_blank">${url}</a>`;
 | 
			
		||||
                })
 | 
			
		||||
                .replace(emailRegex, email => {
 | 
			
		||||
                    return `<a href="mailto:${email}">${email}</a>`;
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        async updateWatched(videos) {
 | 
			
		||||
            if (window.db) {
 | 
			
		||||
            if (window.db && this.getPreferenceBoolean("watchHistory", false)) {
 | 
			
		||||
                var tx = window.db.transaction("watch_history", "readonly");
 | 
			
		||||
                var store = tx.objectStore("watch_history");
 | 
			
		||||
                videos.map(async video => {
 | 
			
		||||
| 
						 | 
				
			
			@ -209,6 +206,7 @@ const mixin = {
 | 
			
		|||
                    request.onsuccess = function (event) {
 | 
			
		||||
                        if (event.target.result) {
 | 
			
		||||
                            video.watched = true;
 | 
			
		||||
                            video.currentTime = event.target.result.currentTime;
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                });
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +227,7 @@ const mixin = {
 | 
			
		|||
        handleLocalSubscriptions(channelId) {
 | 
			
		||||
            var localSubscriptions = this.getLocalSubscriptions() ?? [];
 | 
			
		||||
            if (localSubscriptions.includes(channelId))
 | 
			
		||||
                localSubscriptions.splice(localSubscriptions.indexOf(channelId));
 | 
			
		||||
                localSubscriptions.splice(localSubscriptions.indexOf(channelId), 1);
 | 
			
		||||
            else localSubscriptions.push(channelId);
 | 
			
		||||
            // Sort for better cache hits
 | 
			
		||||
            localSubscriptions.sort();
 | 
			
		||||
| 
						 | 
				
			
			@ -246,8 +244,8 @@ const mixin = {
 | 
			
		|||
            return localSubscriptions.join(",");
 | 
			
		||||
        },
 | 
			
		||||
        /* generate a temporary file and ask the user to download it */
 | 
			
		||||
        download(text, filename, type) {
 | 
			
		||||
            var file = new Blob([text], { type: type });
 | 
			
		||||
        download(text, filename, mimeType) {
 | 
			
		||||
            var file = new Blob([text], { type: mimeType });
 | 
			
		||||
 | 
			
		||||
            const elem = document.createElement("a");
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -256,6 +254,294 @@ const mixin = {
 | 
			
		|||
            elem.click();
 | 
			
		||||
            elem.remove();
 | 
			
		||||
        },
 | 
			
		||||
        getChannelGroupsCursor() {
 | 
			
		||||
            if (!window.db) return;
 | 
			
		||||
            var tx = window.db.transaction("channel_groups", "readonly");
 | 
			
		||||
            var store = tx.objectStore("channel_groups");
 | 
			
		||||
            return store.index("groupName").openCursor();
 | 
			
		||||
        },
 | 
			
		||||
        createOrUpdateChannelGroup(group) {
 | 
			
		||||
            var tx = window.db.transaction("channel_groups", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("channel_groups");
 | 
			
		||||
            store.put({
 | 
			
		||||
                groupName: group.groupName,
 | 
			
		||||
                channels: JSON.stringify(group.channels),
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        deleteChannelGroup(groupName) {
 | 
			
		||||
            var tx = window.db.transaction("channel_groups", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("channel_groups");
 | 
			
		||||
            store.delete(groupName);
 | 
			
		||||
        },
 | 
			
		||||
        async getLocalPlaylist(playlistId) {
 | 
			
		||||
            return await new Promise(resolve => {
 | 
			
		||||
                var tx = window.db.transaction("playlists", "readonly");
 | 
			
		||||
                var store = tx.objectStore("playlists");
 | 
			
		||||
                const req = store.openCursor(playlistId);
 | 
			
		||||
                let playlist = null;
 | 
			
		||||
                req.onsuccess = e => {
 | 
			
		||||
                    playlist = e.target.result.value;
 | 
			
		||||
                    playlist.videos = JSON.parse(playlist.videoIds).length;
 | 
			
		||||
                    resolve(playlist);
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        createOrUpdateLocalPlaylist(playlist) {
 | 
			
		||||
            var tx = window.db.transaction("playlists", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("playlists");
 | 
			
		||||
            store.put(playlist);
 | 
			
		||||
        },
 | 
			
		||||
        // needs to handle both, streamInfo items and streams items
 | 
			
		||||
        createLocalPlaylistVideo(videoId, videoInfo) {
 | 
			
		||||
            if (videoInfo === undefined || videoId === null || videoInfo?.error) return;
 | 
			
		||||
 | 
			
		||||
            var tx = window.db.transaction("playlist_videos", "readwrite");
 | 
			
		||||
            var store = tx.objectStore("playlist_videos");
 | 
			
		||||
            const video = {
 | 
			
		||||
                videoId: videoId,
 | 
			
		||||
                title: videoInfo.title,
 | 
			
		||||
                type: "stream",
 | 
			
		||||
                shortDescription: videoInfo.shortDescription ?? videoInfo.description,
 | 
			
		||||
                url: `/watch?v=${videoId}`,
 | 
			
		||||
                thumbnail: videoInfo.thumbnail ?? videoInfo.thumbnailUrl,
 | 
			
		||||
                uploaderVerified: videoInfo.uploaderVerified,
 | 
			
		||||
                duration: videoInfo.duration,
 | 
			
		||||
                uploaderAvatar: videoInfo.uploaderAvatar,
 | 
			
		||||
                uploaderUrl: videoInfo.uploaderUrl,
 | 
			
		||||
                uploaderName: videoInfo.uploaderName ?? videoInfo.uploader,
 | 
			
		||||
            };
 | 
			
		||||
            store.put(video);
 | 
			
		||||
        },
 | 
			
		||||
        async getLocalPlaylistVideo(videoId) {
 | 
			
		||||
            return await new Promise(resolve => {
 | 
			
		||||
                var tx = window.db.transaction("playlist_videos", "readonly");
 | 
			
		||||
                var store = tx.objectStore("playlist_videos");
 | 
			
		||||
                const req = store.openCursor(videoId);
 | 
			
		||||
                req.onsuccess = e => {
 | 
			
		||||
                    resolve(e.target.result.value);
 | 
			
		||||
                };
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async getPlaylists() {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                if (!window.db) return [];
 | 
			
		||||
                return await new Promise(resolve => {
 | 
			
		||||
                    let playlists = [];
 | 
			
		||||
                    var tx = window.db.transaction("playlists", "readonly");
 | 
			
		||||
                    var store = tx.objectStore("playlists");
 | 
			
		||||
                    const cursorRequest = store.openCursor();
 | 
			
		||||
                    cursorRequest.onsuccess = e => {
 | 
			
		||||
                        const cursor = e.target.result;
 | 
			
		||||
                        if (cursor) {
 | 
			
		||||
                            let playlist = cursor.value;
 | 
			
		||||
                            playlist.videos = JSON.parse(playlist.videoIds).length;
 | 
			
		||||
                            playlists.push(playlist);
 | 
			
		||||
                            cursor.continue();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            resolve(playlists);
 | 
			
		||||
                        }
 | 
			
		||||
                    };
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/user/playlists", null, {
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async getPlaylist(playlistId) {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                const playlist = await this.getLocalPlaylist(playlistId);
 | 
			
		||||
                const videoIds = JSON.parse(playlist.videoIds);
 | 
			
		||||
                const videosFuture = videoIds.map(videoId => this.getLocalPlaylistVideo(videoId));
 | 
			
		||||
                playlist.relatedStreams = await Promise.all(videosFuture);
 | 
			
		||||
                return playlist;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/playlists/" + playlistId);
 | 
			
		||||
        },
 | 
			
		||||
        async createPlaylist(name) {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                const uuid = crypto.randomUUID();
 | 
			
		||||
                const playlistId = `local-${uuid}`;
 | 
			
		||||
                this.createOrUpdateLocalPlaylist({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    // remapping needed for the playlists page
 | 
			
		||||
                    id: playlistId,
 | 
			
		||||
                    name: name,
 | 
			
		||||
                    description: "",
 | 
			
		||||
                    thumbnail: "https://pipedproxy.kavin.rocks/?host=i.ytimg.com",
 | 
			
		||||
                    videoIds: "[]", // empty list
 | 
			
		||||
                });
 | 
			
		||||
                return { playlistId: playlistId };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/user/playlists/create", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    name: name,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async deletePlaylist(playlistId) {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                const playlist = await this.getLocalPlaylist(playlistId);
 | 
			
		||||
                var tx = window.db.transaction("playlists", "readwrite");
 | 
			
		||||
                var store = tx.objectStore("playlists");
 | 
			
		||||
                store.delete(playlistId);
 | 
			
		||||
                // delete videos that don't need to be store anymore
 | 
			
		||||
                const playlists = await this.getPlaylists();
 | 
			
		||||
                const usedVideoIds = playlists
 | 
			
		||||
                    .filter(playlist => playlist.id != playlistId)
 | 
			
		||||
                    .map(playlist => JSON.parse(playlist.videoIds))
 | 
			
		||||
                    .flat();
 | 
			
		||||
                const potentialDeletableVideos = JSON.parse(playlist.videoIds);
 | 
			
		||||
                var videoTx = window.db.transaction("playlist_videos", "readwrite");
 | 
			
		||||
                var videoStore = videoTx.objectStore("playlist_videos");
 | 
			
		||||
                for (let videoId of potentialDeletableVideos) {
 | 
			
		||||
                    if (!usedVideoIds.includes(videoId)) videoStore.delete(videoId);
 | 
			
		||||
                }
 | 
			
		||||
                return { message: "ok" };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/user/playlists/delete", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async renamePlaylist(playlistId, newName) {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                const playlist = await this.getLocalPlaylist(playlistId);
 | 
			
		||||
                playlist.name = newName;
 | 
			
		||||
                this.createOrUpdateLocalPlaylist(playlist);
 | 
			
		||||
                return { message: "ok" };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/user/playlists/rename", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    newName: newName,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async changePlaylistDescription(playlistId, newDescription) {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                const playlist = await this.getLocalPlaylist(playlistId);
 | 
			
		||||
                playlist.description = newDescription;
 | 
			
		||||
                this.createOrUpdateLocalPlaylist(playlist);
 | 
			
		||||
                return { message: "ok" };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/user/playlists/description", null, {
 | 
			
		||||
                method: "PATCH",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    description: newDescription,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async addVideosToPlaylist(playlistId, videoIds, videoInfos) {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                const playlist = await this.getLocalPlaylist(playlistId);
 | 
			
		||||
                const currentVideoIds = JSON.parse(playlist.videoIds);
 | 
			
		||||
                currentVideoIds.push(...videoIds);
 | 
			
		||||
                playlist.videoIds = JSON.stringify(currentVideoIds);
 | 
			
		||||
                let streamInfos =
 | 
			
		||||
                    videoInfos ??
 | 
			
		||||
                    (await Promise.all(videoIds.map(videoId => this.fetchJson(this.apiUrl() + "/streams/" + videoId))));
 | 
			
		||||
                playlist.thumbnail = streamInfos[0].thumbnail || streamInfos[0].thumbnailUrl;
 | 
			
		||||
                this.createOrUpdateLocalPlaylist(playlist);
 | 
			
		||||
                for (let i in videoIds) {
 | 
			
		||||
                    this.createLocalPlaylistVideo(videoIds[i], streamInfos[i]);
 | 
			
		||||
                }
 | 
			
		||||
                return { message: "ok" };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/user/playlists/add", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    videoIds: videoIds,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        async removeVideoFromPlaylist(playlistId, index) {
 | 
			
		||||
            if (!this.authenticated) {
 | 
			
		||||
                const playlist = await this.getLocalPlaylist(playlistId);
 | 
			
		||||
                const videoIds = JSON.parse(playlist.videoIds);
 | 
			
		||||
                videoIds.splice(index, 1);
 | 
			
		||||
                playlist.videoIds = JSON.stringify(videoIds);
 | 
			
		||||
                if (videoIds.length == 0) playlist.thumbnail = "https://pipedproxy.kavin.rocks/?host=i.ytimg.com";
 | 
			
		||||
                this.createOrUpdateLocalPlaylist(playlist);
 | 
			
		||||
                return { message: "ok" };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await this.fetchJson(this.authApiUrl() + "/user/playlists/remove", null, {
 | 
			
		||||
                method: "POST",
 | 
			
		||||
                body: JSON.stringify({
 | 
			
		||||
                    playlistId: playlistId,
 | 
			
		||||
                    index: index,
 | 
			
		||||
                }),
 | 
			
		||||
                headers: {
 | 
			
		||||
                    Authorization: this.getAuthToken(),
 | 
			
		||||
                    "Content-Type": "application/json",
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        getHomePage(_this) {
 | 
			
		||||
            switch (_this.getPreferenceString("homepage", "trending")) {
 | 
			
		||||
                case "trending":
 | 
			
		||||
                    return "/trending";
 | 
			
		||||
                case "feed":
 | 
			
		||||
                    return "/feed";
 | 
			
		||||
                default:
 | 
			
		||||
                    return undefined;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        fetchDeArrowContent(content) {
 | 
			
		||||
            if (!this.getPreferenceBoolean("dearrow", false)) return;
 | 
			
		||||
 | 
			
		||||
            const videoIds = content
 | 
			
		||||
                .filter(item => item.type === "stream")
 | 
			
		||||
                .filter(item => item.dearrow === undefined)
 | 
			
		||||
                .map(item => item.url.substr(-11))
 | 
			
		||||
                .sort();
 | 
			
		||||
 | 
			
		||||
            if (videoIds.length === 0) return;
 | 
			
		||||
 | 
			
		||||
            this.fetchJson(this.apiUrl() + "/dearrow", {
 | 
			
		||||
                videoIds: videoIds.join(","),
 | 
			
		||||
            }).then(json => {
 | 
			
		||||
                Object.keys(json).forEach(videoId => {
 | 
			
		||||
                    const item = content.find(item => item.url.endsWith(videoId));
 | 
			
		||||
                    if (item) item.dearrow = json[videoId];
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        authenticated(_this) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,25 +30,15 @@
 | 
			
		|||
 | 
			
		||||
/*Display: Flex*/ .pp-watch-buttons, .pp-watch-bellow-options .flex.items-center, .pp-channel-page-author, .grid .comment, .pp-chapters {display: flex}
 | 
			
		||||
/*Gap: 15rem*/ .pp-show-playlist, .pp-rec-vids, .pp-mobile-nav, .pp-delete-account .flex, .pp-playlist-add-modal-top, .pp-pref-cards, .pp-watch-bellow-options {gap: var(--efy_gap)}
 | 
			
		||||
/*Color: Text*/ .video-grid div a, .pp-show-recs div a, .video-grid div button:not(.modal button, .btn), .pp-show-recs div button:not(.modal button, .btn), .svg-inline--fa:not(.video-grid svg, .btn svg, button svg) {color: var(--efy_text)}
 | 
			
		||||
/*Color: Text*/ .video-grid div a, .pp-show-recs div a, .video-grid div button:not(.modal button, .btn), .pp-show-recs div button:not(.modal button, .btn), .svg-inline--fa:not(.video-grid svg, [role=button] svg, button svg) {color: var(--efy_text)}
 | 
			
		||||
 | 
			
		||||
/*Border*/ .modal-container, .video-grid>div {border: 1.5px solid var(--efy_bg1);}
 | 
			
		||||
 | 
			
		||||
/*Text-Fill-Color: Text*/  p:not([efy_logo] p), .pp-mobile-nav :is(a, p), .video-grid div a, .pp-show-recs div a, .thumbnail-left, .thumbnail-right, .comment a, .pp-watch-bellow-options a, .pp-logo a, .pp-nav .pp-menu > * {
 | 
			
		||||
/*Text-Fill-Color: Text*/  p:not([efy_logo] p, .efy_sidebar p), .pp-mobile-nav :is(a, p), .video-grid div a, .pp-show-recs div a, .thumbnail-left, .thumbnail-right, .comment a, .pp-watch-bellow-options a, .pp-logo a, .pp-nav .pp-menu > * {
 | 
			
		||||
    -webkit-text-fill-color: var(--efy_text)!important;
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*Text-Fill-Color: Text2*/ .btn, .btn a, a.btn, .modal button {
 | 
			
		||||
    -webkit-text-fill-color: var(--efy_text2);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    background-clip: unset!important;
 | 
			
		||||
    -webkit-background-clip: unset!important;
 | 
			
		||||
    -webkit-text-fill-color: var(--efy_text2)!important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*BG: efy*/ .btn, .pp-chapter-active, .pp-chapters .chapter:hover {background: var(--efy_color); background-clip: padding-box; color: var(--efy_text2)}
 | 
			
		||||
 | 
			
		||||
/*Padding 1*/ .btn {padding: var(--efy_padding); height: auto}
 | 
			
		||||
 | 
			
		||||
/*Avatar*/ .comment-avatar, .pp-import-channel img {width: 40rem; height:  40rem}
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +49,7 @@ tbody:nth-child(odd) {background: var(--efy_bg1)!important; box-shadow: inset 0
 | 
			
		|||
 | 
			
		||||
/*Bellow*/ .pp-watch-bellow-options {margin-top: 15rem}
 | 
			
		||||
.pp-watch-buttons {flex-wrap: wrap; gap: var(--efy_gap0)}
 | 
			
		||||
.pp-watch-buttons .btn {padding: 6rem 15rem; border: 0; color: var(--efy_text2); min-height: 36rem; max-height: 36rem; place-self: center; place-content: center}
 | 
			
		||||
.pp-watch-buttons .btn {padding: 6rem 15rem; border: 0; color: var(--efy_text2); min-height: var(--efy_ratio_width); max-height: var(--efy_ratio_width); place-self: center; place-content: center}
 | 
			
		||||
.pp-bellow-video, .pp-bellow-video .flex {gap:10rem}
 | 
			
		||||
.pp-likes .flex {gap: 0rem}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -95,8 +85,6 @@ tbody:nth-child(odd) {background: var(--efy_bg1)!important; box-shadow: inset 0
 | 
			
		|||
.comment-meta {margin: 0 5rem}
 | 
			
		||||
.comment .comment {margin: var(--efy_gap) 0}
 | 
			
		||||
 | 
			
		||||
/*SVG*/ .svg-inline--fa:not(.video-grid svg, .btn svg, button svg) {margin-right: 5rem}
 | 
			
		||||
 | 
			
		||||
.suggestions-container ul {padding: 0; list-style: none; display: grid; gap: 5rem}
 | 
			
		||||
.suggestions-container li {padding: 3rem 10rem}
 | 
			
		||||
.suggestion-selected {box-shadow: 0 0 0 1.5rem var(--efy_color_border), 0 0 0 1.5rem var(--efy_color_border)}
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +118,10 @@ table {margin-top: 0}
 | 
			
		|||
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div {padding: 15rem; background: var(--efy_bg1); border: var(--efy_border)}
 | 
			
		||||
:is(.video-grid, .pp-show-recs, .pp-show-playlist) div div {padding: 0; border: none; background: transparent}
 | 
			
		||||
 | 
			
		||||
.video-grid > div {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.svg-inline--fa{vertical-align: -2.5rem}
 | 
			
		||||
.children\:px-1>*, .px-1 {padding-left: 4rem; padding-right: 4rem}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -149,8 +141,31 @@ table {margin-top: 0}
 | 
			
		|||
.btn, [role=button] {
 | 
			
		||||
    border: 0;
 | 
			
		||||
}
 | 
			
		||||
.pp-square {
 | 
			
		||||
    width: var(--efy_ratio_width)!important;
 | 
			
		||||
    height: var(--efy_ratio_width);
 | 
			
		||||
}
 | 
			
		||||
.video-tags {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    gap: var(--efy_gap0);
 | 
			
		||||
}
 | 
			
		||||
.video-tags a {
 | 
			
		||||
    background: var(--efy_bg1)!important;
 | 
			
		||||
    border-radius: var(--efy_radius);
 | 
			
		||||
    border: var(--efy_border);
 | 
			
		||||
    background-clip: none!important;
 | 
			
		||||
    -webkit-background-clip: none!important;
 | 
			
		||||
    -webkit-text-fill-color: var(--efy_text)!important;
 | 
			
		||||
    padding: 4rem 10rem;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*Convergence*/
 | 
			
		||||
/*Desktop*/
 | 
			
		||||
.efy_sidebar #custom_sidebar_menu {
 | 
			
		||||
    display: none !important
 | 
			
		||||
}
 | 
			
		||||
@media (max-width: 990px) {
 | 
			
		||||
    .pp-watch-bellow-options {flex-direction: column; margin-top: 15rem}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -163,6 +178,7 @@ table {margin-top: 0}
 | 
			
		|||
    .pp-rec-vids {grid-template-columns: 1fr}
 | 
			
		||||
    .pp-nav .pp-menu {display: none!important}
 | 
			
		||||
    .pp-mobile-btn {display: block}
 | 
			
		||||
    .efy_sidebar #custom_sidebar_menu {display: flex!important}
 | 
			
		||||
}
 | 
			
		||||
@media (min-width: 640px){
 | 
			
		||||
    .sm\:order-last {-webkit-box-ordinal-group: 10000; -webkit-order: 9999; order: 9999}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ const routes = [
 | 
			
		|||
        component: () => import("../components/PlaylistPage.vue"),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: "/:path(v|w|embed|shorts|watch)/:v?",
 | 
			
		||||
        path: "/:path(v|w|embed|live|shorts|watch)/:v?",
 | 
			
		||||
        name: "WatchVideo",
 | 
			
		||||
        component: () => import("../components/WatchVideo.vue"),
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +41,11 @@ const routes = [
 | 
			
		|||
        name: "Channel",
 | 
			
		||||
        component: () => import("../components/ChannelPage.vue"),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: "/@:channelId",
 | 
			
		||||
        name: "Channel handle",
 | 
			
		||||
        component: () => import("../components/ChannelPage.vue"),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: "/login",
 | 
			
		||||
        name: "Login",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -172,7 +172,7 @@
 | 
			
		|||
    { "code": "CH", "name": "Suiza" },
 | 
			
		||||
    { "code": "SR", "name": "Surinam" },
 | 
			
		||||
    { "code": "TH", "name": "Tailandia" },
 | 
			
		||||
    { "code": "TW", "name": "Taiwán" }, 
 | 
			
		||||
    { "code": "TW", "name": "Taiwán" },
 | 
			
		||||
    { "code": "TZ", "name": "Tanzania" },
 | 
			
		||||
    { "code": "TJ", "name": "Tayikistán" },
 | 
			
		||||
    { "code": "TL", "name": "Timor Oriental" },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,201 +4,204 @@ import { Buffer } from "buffer";
 | 
			
		|||
window.Buffer = Buffer;
 | 
			
		||||
import { json2xml } from "xml-js";
 | 
			
		||||
 | 
			
		||||
const DashUtils = {
 | 
			
		||||
    generate_dash_file_from_formats(VideoFormats, VideoLength) {
 | 
			
		||||
        const generatedJSON = this.generate_xmljs_json_from_data(VideoFormats, VideoLength);
 | 
			
		||||
        return json2xml(generatedJSON);
 | 
			
		||||
    },
 | 
			
		||||
    generate_xmljs_json_from_data(VideoFormatArray, VideoLength) {
 | 
			
		||||
        const convertJSON = {
 | 
			
		||||
            declaration: {
 | 
			
		||||
                attributes: {
 | 
			
		||||
                    version: "1.0",
 | 
			
		||||
                    encoding: "utf-8",
 | 
			
		||||
                },
 | 
			
		||||
export function generate_dash_file_from_formats(VideoFormats, VideoLength) {
 | 
			
		||||
    const generatedJSON = generate_xmljs_json_from_data(VideoFormats, VideoLength);
 | 
			
		||||
    return json2xml(generatedJSON);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function generate_xmljs_json_from_data(VideoFormatArray, VideoLength) {
 | 
			
		||||
    const convertJSON = {
 | 
			
		||||
        declaration: {
 | 
			
		||||
            attributes: {
 | 
			
		||||
                version: "1.0",
 | 
			
		||||
                encoding: "utf-8",
 | 
			
		||||
            },
 | 
			
		||||
            elements: [
 | 
			
		||||
                {
 | 
			
		||||
                    type: "element",
 | 
			
		||||
                    name: "MPD",
 | 
			
		||||
                    attributes: {
 | 
			
		||||
                        xmlns: "urn:mpeg:dash:schema:mpd:2011",
 | 
			
		||||
                        profiles: "urn:mpeg:dash:profile:full:2011",
 | 
			
		||||
                        minBufferTime: "PT1.5S",
 | 
			
		||||
                        type: "static",
 | 
			
		||||
                        mediaPresentationDuration: `PT${VideoLength}S`,
 | 
			
		||||
                    },
 | 
			
		||||
                    elements: [
 | 
			
		||||
                        {
 | 
			
		||||
                            type: "element",
 | 
			
		||||
                            name: "Period",
 | 
			
		||||
                            elements: this.generate_adaptation_set(VideoFormatArray),
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
        },
 | 
			
		||||
        elements: [
 | 
			
		||||
            {
 | 
			
		||||
                type: "element",
 | 
			
		||||
                name: "MPD",
 | 
			
		||||
                attributes: {
 | 
			
		||||
                    xmlns: "urn:mpeg:dash:schema:mpd:2011",
 | 
			
		||||
                    profiles: "urn:mpeg:dash:profile:full:2011",
 | 
			
		||||
                    minBufferTime: "PT1.5S",
 | 
			
		||||
                    type: "static",
 | 
			
		||||
                    mediaPresentationDuration: `PT${VideoLength}S`,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
        return convertJSON;
 | 
			
		||||
    },
 | 
			
		||||
    generate_adaptation_set(VideoFormatArray) {
 | 
			
		||||
        const adaptationSets = [];
 | 
			
		||||
                elements: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: "element",
 | 
			
		||||
                        name: "Period",
 | 
			
		||||
                        elements: generate_adaptation_set(VideoFormatArray),
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    };
 | 
			
		||||
    return convertJSON;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        let mimeAudioObjs = [];
 | 
			
		||||
function generate_adaptation_set(VideoFormatArray) {
 | 
			
		||||
    const adaptationSets = [];
 | 
			
		||||
 | 
			
		||||
        VideoFormatArray.forEach(videoFormat => {
 | 
			
		||||
            // the dual formats should not be used
 | 
			
		||||
            if (videoFormat.mimeType.indexOf("video") != -1 && !videoFormat.videoOnly) {
 | 
			
		||||
    let mimeAudioObjs = [];
 | 
			
		||||
 | 
			
		||||
    VideoFormatArray.forEach(videoFormat => {
 | 
			
		||||
        // the dual formats should not be used
 | 
			
		||||
        if (
 | 
			
		||||
            (videoFormat.mimeType.includes("video") && !videoFormat.videoOnly) ||
 | 
			
		||||
            videoFormat.mimeType.includes("application")
 | 
			
		||||
        ) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const audioTrackId = videoFormat.audioTrackId;
 | 
			
		||||
        const mimeType = videoFormat.mimeType;
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < mimeAudioObjs.length; i++) {
 | 
			
		||||
            const mimeAudioObj = mimeAudioObjs[i];
 | 
			
		||||
 | 
			
		||||
            if (mimeAudioObj.audioTrackId == audioTrackId && mimeAudioObj.mimeType == mimeType) {
 | 
			
		||||
                mimeAudioObj.videoFormats.push(videoFormat);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            const audioTrackId = videoFormat.audioTrackId;
 | 
			
		||||
            const mimeType = videoFormat.mimeType;
 | 
			
		||||
 | 
			
		||||
            for (let i = 0; i < mimeAudioObjs.length; i++) {
 | 
			
		||||
                const mimeAudioObj = mimeAudioObjs[i];
 | 
			
		||||
 | 
			
		||||
                if (mimeAudioObj.audioTrackId == audioTrackId && mimeAudioObj.mimeType == mimeType) {
 | 
			
		||||
                    mimeAudioObj.videoFormats.push(videoFormat);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            mimeAudioObjs.push({
 | 
			
		||||
                audioTrackId,
 | 
			
		||||
                mimeType,
 | 
			
		||||
                videoFormats: [videoFormat],
 | 
			
		||||
            });
 | 
			
		||||
        mimeAudioObjs.push({
 | 
			
		||||
            audioTrackId,
 | 
			
		||||
            mimeType,
 | 
			
		||||
            videoFormats: [videoFormat],
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
        mimeAudioObjs.forEach(mimeAudioObj => {
 | 
			
		||||
            const adapSet = {
 | 
			
		||||
                type: "element",
 | 
			
		||||
                name: "AdaptationSet",
 | 
			
		||||
                attributes: {
 | 
			
		||||
                    id: mimeAudioObj.audioTrackId,
 | 
			
		||||
                    lang: mimeAudioObj.audioTrackId?.substr(0, 2),
 | 
			
		||||
                    mimeType: mimeAudioObj.mimeType,
 | 
			
		||||
                    startWithSAP: "1",
 | 
			
		||||
                    subsegmentAlignment: "true",
 | 
			
		||||
                },
 | 
			
		||||
                elements: [],
 | 
			
		||||
            };
 | 
			
		||||
    mimeAudioObjs.forEach(mimeAudioObj => {
 | 
			
		||||
        const adapSet = {
 | 
			
		||||
            type: "element",
 | 
			
		||||
            name: "AdaptationSet",
 | 
			
		||||
            attributes: {
 | 
			
		||||
                id: mimeAudioObj.audioTrackId,
 | 
			
		||||
                lang: mimeAudioObj.audioTrackId?.substr(0, 2),
 | 
			
		||||
                mimeType: mimeAudioObj.mimeType,
 | 
			
		||||
                startWithSAP: "1",
 | 
			
		||||
                subsegmentAlignment: "true",
 | 
			
		||||
            },
 | 
			
		||||
            elements: [],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
            let isVideoFormat = false;
 | 
			
		||||
        let isVideoFormat = false;
 | 
			
		||||
 | 
			
		||||
            if (mimeAudioObj.mimeType.includes("video")) {
 | 
			
		||||
                isVideoFormat = true;
 | 
			
		||||
            }
 | 
			
		||||
        if (mimeAudioObj.mimeType.includes("video")) {
 | 
			
		||||
            isVideoFormat = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isVideoFormat) {
 | 
			
		||||
            adapSet.attributes.scanType = "progressive";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (var i = 0; i < mimeAudioObj.videoFormats.length; i++) {
 | 
			
		||||
            const videoFormat = mimeAudioObj.videoFormats[i];
 | 
			
		||||
            if (isVideoFormat) {
 | 
			
		||||
                adapSet.attributes.scanType = "progressive";
 | 
			
		||||
                adapSet.elements.push(generate_representation_video(videoFormat));
 | 
			
		||||
            } else {
 | 
			
		||||
                adapSet.elements.push(generate_representation_audio(videoFormat));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < mimeAudioObj.videoFormats.length; i++) {
 | 
			
		||||
                const videoFormat = mimeAudioObj.videoFormats[i];
 | 
			
		||||
                if (isVideoFormat) {
 | 
			
		||||
                    adapSet.elements.push(this.generate_representation_video(videoFormat));
 | 
			
		||||
                } else {
 | 
			
		||||
                    adapSet.elements.push(this.generate_representation_audio(videoFormat));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        adaptationSets.push(adapSet);
 | 
			
		||||
    });
 | 
			
		||||
    return adaptationSets;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
            adaptationSets.push(adapSet);
 | 
			
		||||
        });
 | 
			
		||||
        return adaptationSets;
 | 
			
		||||
    },
 | 
			
		||||
    generate_representation_audio(Format) {
 | 
			
		||||
        const representation = {
 | 
			
		||||
            type: "element",
 | 
			
		||||
            name: "Representation",
 | 
			
		||||
            attributes: {
 | 
			
		||||
                id: Format.itag,
 | 
			
		||||
                codecs: Format.codec,
 | 
			
		||||
                bandwidth: Format.bitrate,
 | 
			
		||||
function generate_representation_audio(Format) {
 | 
			
		||||
    const representation = {
 | 
			
		||||
        type: "element",
 | 
			
		||||
        name: "Representation",
 | 
			
		||||
        attributes: {
 | 
			
		||||
            id: Format.itag,
 | 
			
		||||
            codecs: Format.codec,
 | 
			
		||||
            bandwidth: Format.bitrate,
 | 
			
		||||
        },
 | 
			
		||||
        elements: [
 | 
			
		||||
            {
 | 
			
		||||
                type: "element",
 | 
			
		||||
                name: "AudioChannelConfiguration",
 | 
			
		||||
                attributes: {
 | 
			
		||||
                    schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
 | 
			
		||||
                    value: "2",
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            elements: [
 | 
			
		||||
                {
 | 
			
		||||
                    type: "element",
 | 
			
		||||
                    name: "AudioChannelConfiguration",
 | 
			
		||||
                    attributes: {
 | 
			
		||||
                        schemeIdUri: "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
 | 
			
		||||
                        value: "2",
 | 
			
		||||
            {
 | 
			
		||||
                type: "element",
 | 
			
		||||
                name: "BaseURL",
 | 
			
		||||
                elements: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: "text",
 | 
			
		||||
                        text: Format.url,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    type: "element",
 | 
			
		||||
                    name: "BaseURL",
 | 
			
		||||
                    elements: [
 | 
			
		||||
                        {
 | 
			
		||||
                            type: "text",
 | 
			
		||||
                            text: Format.url,
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    type: "element",
 | 
			
		||||
                    name: "SegmentBase",
 | 
			
		||||
                    attributes: {
 | 
			
		||||
                        indexRange: `${Format.indexStart}-${Format.indexEnd}`,
 | 
			
		||||
                    },
 | 
			
		||||
                    elements: [
 | 
			
		||||
                        {
 | 
			
		||||
                            type: "element",
 | 
			
		||||
                            name: "Initialization",
 | 
			
		||||
                            attributes: {
 | 
			
		||||
                                range: `${Format.initStart}-${Format.initEnd}`,
 | 
			
		||||
                            },
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
        return representation;
 | 
			
		||||
    },
 | 
			
		||||
    generate_representation_video(Format) {
 | 
			
		||||
        const representation = {
 | 
			
		||||
            type: "element",
 | 
			
		||||
            name: "Representation",
 | 
			
		||||
            attributes: {
 | 
			
		||||
                id: Format.itag,
 | 
			
		||||
                codecs: Format.codec,
 | 
			
		||||
                bandwidth: Format.bitrate,
 | 
			
		||||
                width: Format.width,
 | 
			
		||||
                height: Format.height,
 | 
			
		||||
                maxPlayoutRate: "1",
 | 
			
		||||
                frameRate: Format.fps,
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
            elements: [
 | 
			
		||||
                {
 | 
			
		||||
                    type: "element",
 | 
			
		||||
                    name: "BaseURL",
 | 
			
		||||
                    elements: [
 | 
			
		||||
                        {
 | 
			
		||||
                            type: "text",
 | 
			
		||||
                            text: Format.url,
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
            {
 | 
			
		||||
                type: "element",
 | 
			
		||||
                name: "SegmentBase",
 | 
			
		||||
                attributes: {
 | 
			
		||||
                    indexRange: `${Format.indexStart}-${Format.indexEnd}`,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    type: "element",
 | 
			
		||||
                    name: "SegmentBase",
 | 
			
		||||
                    attributes: {
 | 
			
		||||
                        indexRange: `${Format.indexStart}-${Format.indexEnd}`,
 | 
			
		||||
                elements: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: "element",
 | 
			
		||||
                        name: "Initialization",
 | 
			
		||||
                        attributes: {
 | 
			
		||||
                            range: `${Format.initStart}-${Format.initEnd}`,
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                    elements: [
 | 
			
		||||
                        {
 | 
			
		||||
                            type: "element",
 | 
			
		||||
                            name: "Initialization",
 | 
			
		||||
                            attributes: {
 | 
			
		||||
                                range: `${Format.initStart}-${Format.initEnd}`,
 | 
			
		||||
                            },
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
        return representation;
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    };
 | 
			
		||||
    return representation;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default DashUtils;
 | 
			
		||||
function generate_representation_video(Format) {
 | 
			
		||||
    const representation = {
 | 
			
		||||
        type: "element",
 | 
			
		||||
        name: "Representation",
 | 
			
		||||
        attributes: {
 | 
			
		||||
            id: Format.itag,
 | 
			
		||||
            codecs: Format.codec,
 | 
			
		||||
            bandwidth: Format.bitrate,
 | 
			
		||||
            width: Format.width,
 | 
			
		||||
            height: Format.height,
 | 
			
		||||
            maxPlayoutRate: "1",
 | 
			
		||||
            frameRate: Format.fps,
 | 
			
		||||
        },
 | 
			
		||||
        elements: [
 | 
			
		||||
            {
 | 
			
		||||
                type: "element",
 | 
			
		||||
                name: "BaseURL",
 | 
			
		||||
                elements: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: "text",
 | 
			
		||||
                        text: Format.url,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                type: "element",
 | 
			
		||||
                name: "SegmentBase",
 | 
			
		||||
                attributes: {
 | 
			
		||||
                    indexRange: `${Format.indexStart}-${Format.indexEnd}`,
 | 
			
		||||
                },
 | 
			
		||||
                elements: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: "element",
 | 
			
		||||
                        name: "Initialization",
 | 
			
		||||
                        attributes: {
 | 
			
		||||
                            range: `${Format.initStart}-${Format.initEnd}`,
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    };
 | 
			
		||||
    return representation;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								src/utils/HtmlUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/utils/HtmlUtils.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
import DOMPurify from "dompurify";
 | 
			
		||||
 | 
			
		||||
export const purifyHTML = html => {
 | 
			
		||||
    return DOMPurify.sanitize(html);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
import linkifyHtml from "linkify-html";
 | 
			
		||||
 | 
			
		||||
export const rewriteDescription = text => {
 | 
			
		||||
    return linkifyHtml(text)
 | 
			
		||||
        .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtube\.com(\/[/a-zA-Z0-9_?=&-]*)/gm, "$1")
 | 
			
		||||
        .replaceAll(/(?:http(?:s)?:\/\/)?(?:www\.)?youtu\.be\/(?:watch\?v=)?([/a-zA-Z0-9_?=&-]*)/gm, "/watch?v=$1")
 | 
			
		||||
        .replaceAll("\n", "<br>");
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										29
									
								
								src/utils/Misc.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/utils/Misc.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
export const isEmail = input => {
 | 
			
		||||
    // Taken from https://emailregex.com
 | 
			
		||||
    const result = input.match(
 | 
			
		||||
        //eslint-disable-next-line
 | 
			
		||||
        /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
 | 
			
		||||
    );
 | 
			
		||||
    return result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const parseTimeParam = time => {
 | 
			
		||||
    let start = 0;
 | 
			
		||||
    if (/^[\d]*$/g.test(time)) {
 | 
			
		||||
        start = time;
 | 
			
		||||
    } else {
 | 
			
		||||
        const hours = /([\d]*)h/gi.exec(time)?.[1];
 | 
			
		||||
        const minutes = /([\d]*)m/gi.exec(time)?.[1];
 | 
			
		||||
        const seconds = /([\d]*)s/gi.exec(time)?.[1];
 | 
			
		||||
        if (hours) {
 | 
			
		||||
            start += parseInt(hours) * 60 * 60;
 | 
			
		||||
        }
 | 
			
		||||
        if (minutes) {
 | 
			
		||||
            start += parseInt(minutes) * 60;
 | 
			
		||||
        }
 | 
			
		||||
        if (seconds) {
 | 
			
		||||
            start += parseInt(seconds);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return start;
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue