Merge branch 'Master' into 'efy', Update efy submodule

This commit is contained in:
dragos-efy 2023-09-06 00:13:33 +03:00
commit fbe23772cb
111 changed files with 11620 additions and 6205 deletions

View file

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

View file

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

View file

@ -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;
});
},

View file

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

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

View file

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

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

View file

@ -6,7 +6,10 @@
import { defineAsyncComponent } from "vue";
const props = defineProps({
item: Object,
item: {
type: Object,
required: true,
},
});
const VideoItem = defineAsyncComponent(() => import("./VideoItem.vue"));

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
},
},
};

View file

@ -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 ? '&#9989;' : '&#10060;'}`" />
<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
View 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>

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

View file

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

View file

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

View file

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

View file

@ -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"]);

View file

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

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

@ -0,0 +1 @@
{}

View file

@ -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} يوم (أيام)"
}
}

View file

@ -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
View 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": "Използването на имейл като потребителско име не се препоръчва. Продължете все пак?"
}
}

View file

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

View file

@ -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?"
}
}

View file

@ -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ů"
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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} लाइव"

View file

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

View file

@ -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
View 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} վրկ-ում"
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}?",

View file

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

View file

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

View file

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

View file

@ -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
View 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": "Sinscriure",
"subscriptions": "Abonaments",
"playlists": "Listas de lecturas",
"bookmarks": "Marcapaginas",
"channel_groups": "Grops de cadenas"
},
"player": {
"watch_on": "Agachar sus {0}"
},
"actions": {
"subscribe": "Sabonar - {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 lapercebut / resumit",
"uses_api_from": "Utiliza lAPI de ",
"skip_intro": "Passar lanimacion dentracte / 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 lavaloracion SSL",
"search": "Recercar (Ctrl+K)",
"filter": "Filtrar",
"disable_lbry": "Desactivar LBRY per la difusion en dirècte",
"loading": "Cargament…",
"clear_history": "Netejar listoric",
"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 dinteraccion (Sabonar)",
"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 dinstàncias",
"enabled_codecs": "Codecs activats (multiples)",
"yes": "Òc",
"no": "Non",
"export_to_json": "Exportar en JSON",
"import_from_json": "Importar dun 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 linstància dautentificacion",
"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 demplenament",
"autoplay_video": "Legir automaticament la vidèo",
"audio_only": "Sonque àudio",
"minimize_comments_default": "Minimizar los comentaris per defaut",
"instance_selection": "Seleccion dinstà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 lautentificacion",
"back_to_home": "Tornar a lacuè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 dacuèlh per defaut",
"minimize_recommendations_default": "Minimizar las recomandacions per defaut",
"store_watch_history": "Servar listoric 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 daqueste 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 listoric 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 dinstà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": "Dacò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 linstància",
"registered_users": "Utilizaires inscriches",
"instance_name": "Nom de linstància",
"has_cdn": "A un CDN ?",
"version": "Version",
"up_to_date": "Actualizat ?",
"ssl_score": "Marca SSL"
},
"login": {
"username": "Nom dutilizaire",
"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 lespaci demmagazinatge 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 dutilizar una adreça electronica coma nom dutilizaire. Contunhar çaquelà ?",
"next_video_countdown": "La vidèo seguenta començarà daquí {0}s"
},
"comment": {
"disabled": "Lautor 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}"
}
}

View file

@ -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": "ଇନଷ୍ଟାନ୍ସ ନାମ",

View file

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

View file

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

View file

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

View file

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

View file

@ -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": "Ștergi 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}"
}
}

View file

@ -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
View 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}"
}
}

View file

@ -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...",

View file

@ -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}с"
}
}

View file

@ -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?"
}
}

View file

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

View file

@ -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} день(-і)"
}
}

View file

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

View file

@ -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} 小时"
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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" },

View file

@ -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
View 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
View 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;
};