Compare commits

...

42 Commits

Author SHA1 Message Date
Ajay Ramachandran
bddbedbdd0 Increase version number 2020-12-29 22:54:25 -05:00
Ajay Ramachandran
1d83a4616d Merge pull request #603 from ajayyy/youcap
YouCap
2020-12-29 22:53:54 -05:00
Ajay Ramachandran
06f09f5fd9 Merge pull request #549 from opl-/feat/preview-bar-cleanup
Clean up Preview Bar
2020-12-29 22:51:50 -05:00
Ajay Ramachandran
e95029c229 Add notice about YouCap 2020-12-29 22:51:00 -05:00
Ajay Ramachandran
6d1c51e7ec New Crowdin updates (#579) 2020-12-29 22:14:09 -05:00
Ajay Ramachandran
83a9526e52 Prevent youtube page from overriding 2020-12-29 22:10:16 -05:00
opl-
0f68c503b3 Fix YouTube overriding player preview tooltip styles 2020-12-26 18:35:25 +01:00
Ajay Ramachandran
3828c00d27 Merge pull request #601 from ajayyy/translations
Remove some unused strings
2020-12-24 23:29:47 -05:00
Ajay Ramachandran
0baf01c1af Remove some unused strings 2020-12-24 23:27:25 -05:00
Ajay Ramachandran
edbbb9022b Don't filter segments 2020-12-24 12:43:21 -05:00
Ajay Ramachandran
73b81424b1 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into feat/preview-bar-cleanup
# Conflicts:
#	src/content.ts
#	src/js-components/previewBar.ts
2020-12-24 12:41:40 -05:00
Ajay Ramachandran
bd12ccb6f4 Merge pull request #589 from ajayyy/fix-mobile
Fix mobile support
2020-12-24 12:32:34 -05:00
Ajay Ramachandran
d81229a157 Merge pull request #599 from FoseFx/fosefx-no-this-alias
Replace self-equals-this code with arrow functions
2020-12-24 12:32:16 -05:00
Max Baumann
0e32042634 refactor(lint): replace self-equals-this code with arrow functions 2020-12-24 11:59:24 +01:00
Ajay Ramachandran
54c36e65ef Make popup work better on mobile 2020-12-22 01:20:22 -05:00
Ajay Ramachandran
979e7e7629 Fix mobile support 2020-12-22 01:10:57 -05:00
Ajay Ramachandran
cba0fc0a87 Merge pull request #580 from ajayyy/category-list
Lock category order and show all categories + more
2020-12-20 18:51:57 -05:00
Ajay Ramachandran
24df146c53 Merge pull request #586 from MRuy/master
Ensure correct start- endtime order
2020-12-20 18:51:44 -05:00
Nanobyte
bc10690304 Ensure correct start- endtime order
#585
2020-12-20 22:48:38 +01:00
Ajay Ramachandran
8dd9a79aa5 Merge branch 'master' into category-list 2020-12-20 14:39:03 -05:00
Ajay Ramachandran
1bab5063aa Merge pull request #582 from FoseFx/fosefx-ts-msg
Types for messages
2020-12-19 14:50:53 -05:00
Max Baumann
1d7dab6ea0 Merge master 2020-12-18 18:35:26 +01:00
Ajay Ramachandran
c30038fd26 Merge pull request #578 from FoseFx/fosefx-eslint-fix
Fix eslint warnings
2020-12-17 13:20:48 -05:00
Ajay Ramachandran
81926367b1 Revert "Don't include non signed Firefox extension in release assets"
This reverts commit 9bd5c971c6.
2020-12-16 13:41:23 -05:00
Ajay Ramachandran
5ac5b557b0 Fix manual skip going into directly adjacent segment
Resolves #531
2020-12-16 01:19:54 -05:00
Ajay Ramachandran
e5ec478e6a Update dependencies 2020-12-16 00:53:15 -05:00
Ajay Ramachandran
90d53a34b5 Move unlisted check request to background script
Resolves #566
2020-12-15 21:34:29 -05:00
Ajay Ramachandran
fbe64c115b Added cancel button when choosing a disable category 2020-12-15 16:32:38 -05:00
Ajay Ramachandran
9bd5c971c6 Don't include non signed Firefox extension in release assets 2020-12-15 16:22:30 -05:00
Ajay Ramachandran
41e5cb25aa Fix category list alert not being localized 2020-12-15 16:18:28 -05:00
Ajay Ramachandran
fd9116c81c Lock category order and show all categories 2020-12-15 16:13:56 -05:00
Max Baumann
2afe510912 refactor(types): add PageElements type 2020-12-15 20:10:19 +01:00
Max Baumann
c0d910decd refactor: more types and dead code removal 2020-12-15 19:54:33 +01:00
Max Baumann
cd4f5fc667 refactor(types): add strong types to messages 2020-12-15 19:37:48 +01:00
Max Baumann
09c527417d refactor: type 'config' Proxy 2020-12-15 18:29:47 +01:00
Max Baumann
094ef84f15 refactor(types): add StorageChangesObject type 2020-12-15 14:24:29 +01:00
Max Baumann
dc36e8097d refactor: remove unused function 2020-12-15 13:34:02 +01:00
Max Baumann
b7ea5689c7 refactor(types): add VideoInfo interface 2020-12-15 13:33:38 +01:00
Max Baumann
7756a89960 refactor: remove more unused variables 2020-12-15 12:58:44 +01:00
Max Baumann
5d0559aebd refactor: remove dead code, add more types 2020-12-14 23:37:35 +01:00
Ajay Ramachandran
120642667d Rename timestamp to segment 2020-12-14 17:24:35 -05:00
opl-
7078e1f033 Clean up Preview Bar
Fixes:
- Segments hidden by longer segments
- Duration with skips not accounting for segment overlaps
- Duration with skips not accounting for user's skip choices
- Segment category text in preview tooltip overlaps the seek bar
- Segment category text in preview tooltip breaks for timestamps over one hour
- `previewBar.ts` lacks function argument and return types
- Tooltip label not cleaned up on remove
- General code style issues
2020-12-14 19:58:00 +01:00
60 changed files with 2595 additions and 1254 deletions

View File

@@ -20,10 +20,8 @@ module.exports = {
plugins: ["react", "@typescript-eslint"],
rules: {
// TODO: Remove warn rules when not needed anymore
"@typescript-eslint/no-this-alias": "warn",
"no-self-assign": "warn",
"@typescript-eslint/no-empty-interface": "warn",
"@typescript-eslint/ban-types": "warn",
"no-self-assign": "off",
"@typescript-eslint/no-empty-interface": "off",
},
settings: {
react: {

View File

@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "SponsorBlock",
"version": "2.0.10.1",
"version": "2.0.11",
"default_locale": "en",
"description": "__MSG_Description__",
"content_scripts": [{

1195
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@
"ts-jest": "^26.2.0",
"ts-loader": "^6.2.1",
"typescript": "~3.7.3",
"web-ext": "^5.0.0",
"web-ext": "^5.4.1",
"webpack": "~4.41.2",
"webpack-cli": "~3.3.10",
"webpack-merge": "~4.2.2"

View File

@@ -34,12 +34,6 @@
"Loading": {
"message": "Зареждане..."
},
"Mins": {
"message": "Минути"
},
"Secs": {
"message": "Секунди"
},
"Hide": {
"message": "Никога не показвай"
},
@@ -88,9 +82,6 @@
"submitCheck": {
"message": "Сигурни ли сте, че искате да подадете това?"
},
"here": {
"message": "тук"
},
"discordAdvert": {
"message": "Елате в официалния Discord сървър за да давате предложения!"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Carregant..."
},
"Mins": {
"message": "Minuts"
},
"Secs": {
"message": "Segons"
},
"Hide": {
"message": "No mostris mai"
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Načítání..."
},
"Mins": {
"message": "Minuty"
},
"Secs": {
"message": "Sekundy"
},
"Hide": {
"message": "Nikdy nezobrazovat"
},
@@ -121,6 +115,12 @@
"submitCheck": {
"message": "Opravdu to chcete odeslat?"
},
"whitelistChannel": {
"message": "Přidat kanál do výjimek"
},
"removeFromWhitelist": {
"message": "Odebrat kanál z výjimek"
},
"voteOnTime": {
"message": "Hlasovat pro segment"
},
@@ -133,9 +133,6 @@
"viewLeaderboard": {
"message": "Žebříček"
},
"here": {
"message": "zde"
},
"recordTimesDescription": {
"message": "Odeslat"
},
@@ -575,6 +572,10 @@
"chooseACategory": {
"message": "Vyberte kategorii"
},
"enableThisCategoryFirst": {
"message": "Pro odeslání segmentů v kategorii \"{0}\" to nejprve musíte povolit v nastavení. Nyní budete přesměrováni do nastavení.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Musíte vybrat kategorii pro všechny odeslané segmenty!"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Indlæser..."
},
"Mins": {
"message": "Minutter"
},
"Secs": {
"message": "Sekunder"
},
"Hide": {
"message": "Vis aldrig"
},

View File

@@ -4,7 +4,7 @@
"description": "Name of the extension."
},
"Description": {
"message": "Überspringe gesponserte Videosegmente, Betteln um Abonnenten und mehr in YouTube Videos. Melde gesponserte Videosegmente in Videos, die du guckst, um anderen Zeit zu sparen.",
"message": "Überspringe Sponsoren, betteln um Abonnenten und mehr in YouTube Videos. Melde Sponsoren in Videos, die du guckst, um anderen Zeit zu sparen.",
"description": "Description of the extension."
},
"400": {
@@ -40,12 +40,6 @@
"Loading": {
"message": "Laden..."
},
"Mins": {
"message": "Minuten"
},
"Secs": {
"message": "Sekunden"
},
"Hide": {
"message": "Verstecken"
},
@@ -86,7 +80,7 @@
"message": "Segment endet jetzt"
},
"noVideoID": {
"message": "Kein YouTube-Video gefunden.\nWenn dies falsch ist, aktualisieren Sie den Tab."
"message": "Kein YouTube-Video gefunden.\nWenn dies falsch ist, aktualisiere den Tab."
},
"success": {
"message": "Geschafft!"
@@ -121,11 +115,33 @@
"submitCheck": {
"message": "Bist du sicher, dass du dies übermitteln willst?"
},
"whitelistChannel": {
"message": "Kanal zur Whitelist hinzufügen"
},
"removeFromWhitelist": {
"message": "Kanal von der Whitelist entfernen"
},
"voteOnTime": {
"message": "Über ein Segment abstimmen"
},
"here": {
"message": "hier"
"Submissions": {
"message": "Beiträge"
},
"savedPeopleFrom": {
"message": "Andere Nutzer bewahrt vor: "
},
"viewLeaderboard": {
"message": "Rangliste"
},
"recordTimesDescription": {
"message": "Senden"
},
"submissionEditHint": {
"message": "Die Bearbeitung von Beiträgen wird angezeigt, nachdem du auf Senden klickst",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "Tipp: Du kannst Tastenkombinationen für das Senden in den Optionen festlegen"
},
"clearTimesButton": {
"message": "Zeiten löschen"
@@ -136,6 +152,9 @@
"publicStats": {
"message": "So wirst du in der öffentlichen Rangliste angezeigt. Siehe"
},
"Username": {
"message": "Benutzername"
},
"setUsername": {
"message": "Alias festlegen"
},
@@ -231,6 +250,12 @@
"0": {
"message": "Zeitüberschreibung. Überprüfe deine Internetverbindung. Bist du mit dem Internet verbunden, ist der Server wahrscheinlich offline."
},
"disableSkipping": {
"message": "Überspringen ist aktiviert"
},
"enableSkipping": {
"message": "Überspringen ist deaktiviert"
},
"yourWork": {
"message": "Deine Statistik",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -268,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "Diese Zeit wird in Klammern neben der kompletten Videodauer im YouTube-Videoplayer angezeigt. Dies betrifft auch Segmente, die als \"In Suchleiste anzeigen\" markiert sind."
},
"youHaveSkipped": {
"message": "Du übersprangst "
},
"youHaveSaved": {
"message": " und erspartest dir damit "
},
@@ -283,6 +311,12 @@
"hoursLower": {
"message": "Stunden"
},
"youHaveSavedTime": {
"message": "Du erspartest anderen"
},
"youHaveSavedTimeEnd": {
"message": " ihres Lebens"
},
"statusReminder": {
"message": "Du kannst den Serverstatus auf https://status.sponsor.ajay.app überprüfen."
},
@@ -314,10 +348,10 @@
"message": "Invidious-Kompatibilität"
},
"supportInvidiousDescription": {
"message": "Invidious (https://invidio.us) ist ein Drittanbieter-YouTube-Client. Um SponsorBlock mit Invidious verwenden zu können, musst du die zusätzlichen Berechtigungen akzeptieren. Dies funktioniert NICHT in Chrome's Inkognitomodus oder anderen Chromium-Varianten."
"message": "Invidious (invidio.us) ist ein Drittanbieter-YouTube-Client. Um Support zu aktivieren, müssen Sie die zusätzlichen Berechtigungen akzeptieren. Dies funktioniert NICHT im Incongnito-modus auf Chrome und anderen Chromium-Varianten."
},
"optionsInfo": {
"message": "Zu überspringende Videosegmente auswählen, automatisches Überspringen, Knöpfe ein- & ausblenden und noch viel mehr."
"message": "Zu überspringende Kategorien auswählen, automatisches Überspringen, Tasten ein- & ausblenden und noch viel mehr."
},
"addInvidiousInstance": {
"message": "Invidious-Instanzen hinzufügen"
@@ -524,10 +558,10 @@
"message": "Betatest-Server aktivieren"
},
"whatEnableTestingServer": {
"message": "Deine Einreichungen und Bewertungen/Meldungen werden NICHT an den Hauptserver übertragen. Benutze diese Option also nur für Tests."
"message": "Deine Beiträge und Bewertungen/Meldungen werden NICHT an den Hauptserver übertragen. Benutze diese Option also nur für Tests."
},
"testingServerWarning": {
"message": "Alle Einreichungen und Bewertungen/Meldungen werden NICHT an den Hauptserver übertragen. Deaktiviere die Betatest-Server Option um Einreichungen an den Hauptserver zu senden."
"message": "Alle Beiträge und Bewertungen/Meldungen werden NICHT an den Hauptserver übertragen. Deaktiviere die Betatest-Server Option um Einreichungen an den Hauptserver zu senden."
},
"bracketNow": {
"message": "(jetzt)"
@@ -538,6 +572,10 @@
"chooseACategory": {
"message": "Wähle eine Kategorie"
},
"enableThisCategoryFirst": {
"message": "Um Segmente aus der Kategorie \"{0}\" zu senden, musst du diese in den Optionen aktivieren. Du wirst jetzt zu den Optionen weitergeleitet.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Du musst eine Kategorie für jedes zu übermittelnde Segment auswählen!"
},
@@ -598,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "Zeigt eine Benachrichtigung an, wenn du ein Video mit nicht übertragenden Segmenten verlässt."
},
"help": {
"message": "Hilfe"
}
}

View File

@@ -24,12 +24,6 @@
"Loading": {
"message": "Φόρτωση..."
},
"Mins": {
"message": "Λεπτά"
},
"Secs": {
"message": "Δευτερόλεπτα"
},
"Hide": {
"message": "Να μην εμφανίζεται ποτέ"
},
@@ -51,9 +45,6 @@
"clearTimes": {
"message": "Καθαρισμός τμημάτων"
},
"here": {
"message": "εδώ"
},
"Options": {
"message": "Επιλογές"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Loading..."
},
"Mins": {
"message": "Minutes"
},
"Secs": {
"message": "Seconds"
},
"Hide": {
"message": "Never Show"
},
@@ -139,9 +133,6 @@
"viewLeaderboard": {
"message": "Leaderboard"
},
"here": {
"message": "here"
},
"recordTimesDescription": {
"message": "Submit"
},
@@ -581,6 +572,10 @@
"chooseACategory": {
"message": "Choose a Category"
},
"enableThisCategoryFirst": {
"message": "To submit segments with the category of \"{0}\", you must enable it in the options. You will be redirected to the options now.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "You must select a category for all segments you are submitting!"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Cargando..."
},
"Mins": {
"message": "Minutos"
},
"Secs": {
"message": "Segundos"
},
"Hide": {
"message": "Nunca Mostrar"
},
@@ -128,14 +122,11 @@
"message": "Solicitudes"
},
"savedPeopleFrom": {
"message": "Has salvado a gente de "
"message": " salvado personas de "
},
"viewLeaderboard": {
"message": "Tablas de clasificación"
},
"here": {
"message": "aquí"
},
"recordTimesDescription": {
"message": "Enviar"
},
@@ -156,7 +147,7 @@
"message": "Esto se utiliza en la página de estadísticas públicas para mostrar cuánto has contribuido. Véala"
},
"Username": {
"message": "Nombre de usario"
"message": "Usuario"
},
"setUsername": {
"message": "Escoger Nombre De Usuario"
@@ -297,7 +288,7 @@
"message": "Esta duración aparece entre paréntesis junto al tiempo actual del video, debajo de la barra de navegación. Esta muestra la duración total del vídeo menos cualquier segmento eliminado. Esto incluye los segmentos marcados como solo \"Mostrar en la barra de navegación\"."
},
"youHaveSkipped": {
"message": "Lo has saltado "
"message": "Los has saltado "
},
"youHaveSaved": {
"message": "Te has ahorrado "

View File

@@ -1 +1,613 @@
{}
{
"fullName": {
"message": "SponsorBlock YouTube'ile - jäta sponsorid vahele",
"description": "Name of the extension."
},
"Description": {
"message": "Jäta sponsorid, tellimise palumine ja muud YouTube'i videote tüütused vahele. Teata vaadatavate videote sponsoritest, et säästa teiste aega.",
"description": "Description of the extension."
},
"400": {
"message": "Server ütles, et see taotlus oli sobimatu"
},
"429": {
"message": "Sa oled sellele ühele videole liiga palju sponsoriaegu saatnud, kas oled kindel, et neid on tõesti nii palju?"
},
"409": {
"message": "See on juba varasemalt saadetud"
},
"channelWhitelisted": {
"message": "Kanal lubamisnimekirjas!"
},
"Segment": {
"message": "segmendi"
},
"Segments": {
"message": "segmenti"
},
"upvoteButtonInfo": {
"message": "Anna segmendile poolthääl"
},
"reportButtonTitle": {
"message": "Teavita"
},
"reportButtonInfo": {
"message": "Teavita sellest segmendist kui sobimatust."
},
"Dismiss": {
"message": "Sulge"
},
"Loading": {
"message": "Laadimine..."
},
"Hide": {
"message": "Ära näita kunagi"
},
"hitGoBack": {
"message": "Vajuta \"tühista vahelejätmine\", et jõuda sinna, kust tulid."
},
"unskip": {
"message": "Tühista vahelejätmine"
},
"reskip": {
"message": "Jäta uuesti vahele"
},
"paused": {
"message": "Pausil"
},
"manualPaused": {
"message": "Taimer peatatud"
},
"confirmMSG": {
"message": "Üksikute väärtuste muutmiseks või kustutamiseks vajuta infonuppu või ava laienduse hüpik, vajutades selle ikoonile üleval paremal nurgas."
},
"clearThis": {
"message": "Kas soovid tõesti selle tühjendada?\n\n"
},
"Unknown": {
"message": "Sinu sponsoriaegade saatmisel esines tõrge, palun proovi hiljem uuesti."
},
"sponsorFound": {
"message": "Sellel videol on andmebaasis segmente!"
},
"sponsor404": {
"message": "Segmente ei leitud"
},
"sponsorStart": {
"message": "Segment algab nüüd"
},
"sponsorEnd": {
"message": "Segment lõpeb nüüd"
},
"noVideoID": {
"message": "YouTube'i videot ei leitud.\nKui see ei vasta tõele, laadi kaart uuesti."
},
"success": {
"message": "Õnnestus!"
},
"voted": {
"message": "Hääletatud!"
},
"serverDown": {
"message": "Tundub, et server on maas. Anna sellest koheselt arendajale teada."
},
"connectionError": {
"message": "Ühendusega esines tõrge. Veakood: "
},
"wantToSubmit": {
"message": "Kas soovid segmendid saata video IDle"
},
"leftTimes": {
"message": "Sul tunduvad olevat mõned segmendid esitamata. Nende esitamiseks mine sellele lehele tagasi (need ei ole kustutatud)."
},
"clearTimes": {
"message": "Tühjenda segmendid"
},
"openPopup": {
"message": "Ava SponsorBlocki hüpik"
},
"closePopup": {
"message": "Sulge hüpik"
},
"SubmitTimes": {
"message": "Esita segmendid"
},
"submitCheck": {
"message": "Kas soovid kindlasti selle saata?"
},
"whitelistChannel": {
"message": "Lisa kanal lubamisnimekirja"
},
"removeFromWhitelist": {
"message": "Eemalda kanal lubamisnimekirjast"
},
"voteOnTime": {
"message": "Hääleta segmendi sobivust"
},
"Submissions": {
"message": "Saadetud segmendid"
},
"savedPeopleFrom": {
"message": "Oled inimeste aega säästnud "
},
"viewLeaderboard": {
"message": "Edetabel"
},
"recordTimesDescription": {
"message": "Saada"
},
"submissionEditHint": {
"message": "Segmendi töötlemine avaneb pärast saatmist",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "Vihje: sa võid saatmiseks määrata valikutes klaviatuuriotseteid"
},
"clearTimesButton": {
"message": "Tühjenda ajad"
},
"submitTimesButton": {
"message": "Saada ajad"
},
"publicStats": {
"message": "Seda kasutatakse avaliku statistika lehel, et näidata välja, kui palju oled panustanud. Vaata seda"
},
"Username": {
"message": "Kasutajanimi"
},
"setUsername": {
"message": "Määra kasutajanimi"
},
"discordAdvert": {
"message": "Tule liitu ametliku Discordi serveriga, et anda soovitusi ja tagasisidet!"
},
"hideThis": {
"message": "Peida see"
},
"Options": {
"message": "Valikud"
},
"showButtons": {
"message": "Kuva YouTube'i mängijal nupud"
},
"hideButtons": {
"message": "Peida YouTube'i mängijal nupud"
},
"hideButtonsDescription": {
"message": "See peidab nupud, mis kuvatakse YouTube'i mängijal vahelejätmiste segmentide saatmiseks."
},
"showInfoButton": {
"message": "Kuva YouTube'i mängijal infonupp"
},
"hideInfoButton": {
"message": "Peida YouTube'i mängijal infonupp"
},
"whatInfoButton": {
"message": "See on see nupp, mis avab YouTube'i lehel hüpiku."
},
"hideDeleteButton": {
"message": "Peida YouTube'i mängijal kustutusnupp"
},
"showDeleteButton": {
"message": "Kuva YouTube'i mängijal kustutusnupp"
},
"whatDeleteButton": {
"message": "YouTube'i mängija nupp, mis tühjendab kõik sinu praeguse video saatmata segmendid."
},
"enableViewTracking": {
"message": "Luba vahelejätmiste arvu jälgimine"
},
"whatViewTracking": {
"message": "See funktsioon jälgib, milliseid segmente sa oled vahele jätnud, et kasutajad teaksid, kui palju nende sisestus on teisi aidanud ning et koos poolthäältega veenduda rämpsu mittesattumises andmebaasi. Laiendus saadab iga segmendi vahelejätmisel serverisse sõnumi. Loodetavasti enamus inimesi ei muuda seda seadistust, et vaatamisarvud oleksid täpsed. :)"
},
"enableQueryByHashPrefix": {
"message": "Küsi räsi eesliite järgi"
},
"whatQueryByHashPrefix": {
"message": "VideoID järgi segmentide küsimise asemel saadetakse videoID räsi 4 esimest märki ning server saadab tagasi kõigi sarnaste räsidega videote andmed."
},
"enableRefetchWhenNotFound": {
"message": "Too segmendid uutel videotel uuesti"
},
"whatRefetchWhenNotFound": {
"message": "Kui video on uus ning segmente ei leitud, proovitakse vaatamise ajal iga paari minuti tagant uuesti segmente tuua."
},
"showNotice": {
"message": "Kuva märkus uuesti"
},
"longDescription": {
"message": "SponsorBlock lubab sul vahele jätta sponsorid, vaheajad, kanali tellimise meeldetuletused ja muud YouTube'i videote tüütud kohad. SponsorBlock on rahva ühistööna toimiv brauserilaiendus, mis lubab igaühel saata sponsoreeritud segmendi algus- ja lõpuaegu ning teiste video segmentide aegu. Kui üks inimene saadab sponsoreeritud segmendi, jätavad teised laienduse kasutajad kohe selle vahele. Laiendus võimaldab ka muusikavideotel mitte-muusika jaotised vahele jätta.",
"description": "Full description of the extension on the store pages."
},
"website": {
"message": "Veebileht",
"description": "Used on Firefox Store Page"
},
"sourceCode": {
"message": "Lähtekood",
"description": "Used on Firefox Store Page"
},
"noticeUpdate": {
"message": "Teavitus on uuendatud!",
"description": "The first line of the message displayed after the notice was upgraded."
},
"noticeUpdate2": {
"message": "Kui sulle see ikka ei meeldi, vajuta \"ära näita kunagi\" nuppu.",
"description": "The second line of the message displayed after the notice was upgraded."
},
"setStartSponsorShortcut": {
"message": "Seadista segmendi alustamise klahv"
},
"setSubmitKeybind": {
"message": "Seadista segmendi saatmise klahv"
},
"keybindDescription": {
"message": "Vali klahv, seda vajutades"
},
"keybindDescriptionComplete": {
"message": "Otsetee on seatud: "
},
"disableSkipping": {
"message": "Vahelejätmine on lubatud"
},
"enableSkipping": {
"message": "Vahelejätmine on keelatud"
},
"yourWork": {
"message": "Sinu töö",
"description": "Used to describe the section that will show you the statistics from your submissions."
},
"502": {
"message": "Server paistab olevat ülekoormatud. Proovi uuesti mõne sekundi pärast."
},
"errorCode": {
"message": "Veakood: "
},
"skip": {
"message": "Jäta vahele"
},
"skip_category": {
"message": "Jätad {0} vahele?"
},
"skipped": {
"message": "Vahelejäetud"
},
"disableAutoSkip": {
"message": "Keela automaatne vahelejätmine"
},
"enableAutoSkip": {
"message": "Luba automaatne vahelejätmine"
},
"audioNotification": {
"message": "Heliteavitus vahelejätmisel"
},
"showTimeWithSkips": {
"message": "Kuva vahelejäetud segmentidega aeg"
},
"youHaveSkipped": {
"message": "Oled vahele jätnud "
},
"youHaveSaved": {
"message": "Oled enda aega säästnud "
},
"minLower": {
"message": "minut"
},
"minsLower": {
"message": "minutit"
},
"hourLower": {
"message": "tund"
},
"hoursLower": {
"message": "tundi"
},
"youHaveSavedTime": {
"message": "Oled inimestel säästnud"
},
"youHaveSavedTimeEnd": {
"message": " nende ajast"
},
"statusReminder": {
"message": "Serveri oleku saamiseks vaata status.sponsor.ajay.app"
},
"changeUserID": {
"message": "Impordi/ekspordi oma UserID"
},
"whatChangeUserID": {
"message": "Seda tuleks privaatsena hoida. See on nagu parool ning seda ei tohiks kellegagi jagada. Kui kellelgi see on, saavad nad sinuna esineda."
},
"setUserID": {
"message": "Seadista UserID"
},
"userIDChangeWarning": {
"message": "Hoiatus: UserID muutmine on püsiv. Kas soovid kindlasti seda teha? Igaks juhuks soovitame eelmise UserID varundada."
},
"createdBy": {
"message": "Autor"
},
"autoSkip": {
"message": "Autom. vahelejätmine"
},
"showSkipNotice": {
"message": "Kuva segmendi vahelejätmisel teatis"
},
"keybindCurrentlySet": {
"message": ". Hetkel on selleks määratud:"
},
"supportInvidious": {
"message": "Invidiouse tugi"
},
"supportInvidiousDescription": {
"message": "Invidious (invidio.us) on kolmanda osapoole YouTube'i klient. Selle toe lubamiseks pead nõustuma lisalubadega. See EI tööta inkognito-režiimis nii Chromes kui ka teistes Chromiumi brauserites."
},
"optionsInfo": {
"message": "Luba Invidiouse tugi, keela automaatne vahelejätmine, peida nupud ja muud valikud."
},
"addInvidiousInstance": {
"message": "Lisa Invidiouse eksemplar"
},
"addInvidiousInstanceDescription": {
"message": "Lisa kohandatud Invidiouse eksemplar. See tuleb vormistada AINULT domeeniga. Näide:\ninvidious.ajay.app"
},
"add": {
"message": "Lisa"
},
"addInvidiousInstanceError": {
"message": "See on sobimatu domeen. Siia tuleks kirjutada AINULT domeeniosa, nt invidious.ajay.app"
},
"resetInvidiousInstance": {
"message": "Lähtesta Invidiouse eksemplaride nimekiri"
},
"resetInvidiousInstanceAlert": {
"message": "Lähtestad Invidiouse eksemplaride nimekirja"
},
"currentInstances": {
"message": "Praegused eksemplarid:"
},
"minDuration": {
"message": "Minimaalne kestus (sekundit):"
},
"minDurationDescription": {
"message": "Segmendid, mis on lühemad kui määratud väärtus ei jäeta vahele või ei kuvata mängijal."
},
"showUploadButton": {
"message": "Kuva üleslaadimisnupp"
},
"whatUploadButton": {
"message": "See nupp kuvatakse YouTube'i mängijal, kui oled ajatempli ära valinud ning saatmiseks valmis."
},
"customServerAddress": {
"message": "SponsorBlocki serveri aadress"
},
"save": {
"message": "Salvesta"
},
"reset": {
"message": "Lähtesta"
},
"customAddressError": {
"message": "Aadress ei ole õiges vormingus. Veendu, et sul on alguses http:// või https:// ning lõpus ei ole kaldkriipsu."
},
"areYouSureReset": {
"message": "Kas soovid kindlasti selle lähtestada?"
},
"confirmPrivacy": {
"message": "See video on registrivälisena tuvastatud. Klõpsa \"tühista\", kui ei soovi otsida vahelejätmise segmente."
},
"unlistedCheck": {
"message": "Ignoreeri registriväliseid/privaatsed videoid"
},
"whatUnlistedCheck": {
"message": "See valik aeglustab veidi SponsorBlocki. Vahelejätmise segmentide hankimiseks saadetakse video ID serverisse. Kui oled mures registriväliste videote IDde internetti saatmise üle, luba see valik."
},
"mobileUpdateInfo": {
"message": "m.youtube.com on nüüd toetatud"
},
"exportOptions": {
"message": "Impordi/ekspordi kõik valikud"
},
"whatExportOptions": {
"message": "See on sinu kogu seadistus JSON-formaadis. Selle hulgas on ka UserID, seega jaga seda targalt."
},
"setOptions": {
"message": "Määra valikud"
},
"incorrectlyFormattedOptions": {
"message": "See JSON ei ole korralikult vormistatud. Sinu valikuid ei muudetud."
},
"confirmNoticeTitle": {
"message": "Saada segment"
},
"submit": {
"message": "Saada"
},
"cancel": {
"message": "Katkesta"
},
"delete": {
"message": "Kustuta"
},
"preview": {
"message": "Eelvaade"
},
"inspect": {
"message": "Inspekteeri"
},
"edit": {
"message": "Muuda"
},
"copyDebugInformation": {
"message": "Kopeeri silumisteave lõikelauale"
},
"copyDebugInformationFailed": {
"message": "Lõikelauale kirjutamine ebaõnnestus"
},
"theKey": {
"message": "Klahv"
},
"keyAlreadyUsed": {
"message": "on juba teisele tegevusele määratud. Palun vali teine klahv."
},
"to": {
"message": "kuni",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"category_sponsor": {
"message": "Sponsor"
},
"category_sponsor_description": {
"message": "Tasulised promod, tasulised viited ja otsesed reklaamid. Pole mõeldud enesepromo või tasuta petitsioonide/autorite/veebilehtede/toodete mainimiste puhul."
},
"category_intro": {
"message": "Vaheaeg/sissejuhatav animatsioon"
},
"category_intro_description": {
"message": "Tegeliku sisuta intervall. Võib olla paus, seisev pilt, korduv animatsioon. Seda ei peaks kasutama üleminekutel, milles on teabega sisu."
},
"category_intro_short": {
"message": "Vaheaeg"
},
"category_outro": {
"message": "Lõpukaardid/-tiitrid"
},
"category_outro_description": {
"message": "Tiitrid või YouTube'i lõpukaardid. Pole mõeldud informatsiooniga järelduste jaoks."
},
"category_interaction": {
"message": "Tegutsemise meeldetuletus (kanali tellimine)"
},
"category_interaction_description": {
"message": "Lühike sisukeskne meeldetuletus anda videole meeldib, tellida kanalit või jälgida autorit. Kui see on pikk või millegi kindlaga seotud, peaks see olema enesepromo all."
},
"category_interaction_short": {
"message": "Tegutsemise meeldetuletus"
},
"category_selfpromo": {
"message": "Tasumata/enesepromo"
},
"category_selfpromo_description": {
"message": "Sarnaneb \"sponsorile\", ent on mõeldud tasumata või enesepromo jaoks. Selle alla kuuluvad jaotised oma müüdava kauba, annetuste ja koostööpartnerite kohta."
},
"category_music_offtopic": {
"message": "Muusika: mitte-muusika jaotis"
},
"category_music_offtopic_description": {
"message": "Ainult muusikavideotes kasutamiseks. Sisaldab muusikavideote sissejuhatusi ja väljajuhatusi."
},
"category_music_offtopic_short": {
"message": "Mitte-muusika"
},
"category_livestream_messages": {
"message": "Otseülekanne: annetuste ja sõnumite lugemine"
},
"category_livestream_messages_short": {
"message": "Sõnumite lugemine"
},
"disable": {
"message": "Keela"
},
"manualSkip": {
"message": "Käsitsi vahelejätmine"
},
"showOverlay": {
"message": "Kuva mängija ajaribal"
},
"colorFormatIncorrect": {
"message": "Sinu värv on sobimatult vormistatud. See peaks olema 3- või 6-numbriline 16-kümmendsüsteemis kood, arvu ees trellid."
},
"previewColor": {
"message": "Värvi eelvaade",
"description": "Referring to submissions that have not been sent to the server yet."
},
"seekBarColor": {
"message": "Ajariba värv"
},
"category": {
"message": "Kategooria"
},
"skipOption": {
"message": "Vahelejätmise valik",
"description": "Used on the options page to describe the ways to skip the segment (auto skip, manual, etc.)"
},
"enableTestingServer": {
"message": "Luba beetatestimise server"
},
"whatEnableTestingServer": {
"message": "Sinu saadetud segmendid ja hääled EI LÄHE põhiserveri alla. Kasuta seda vaid katsetamiseks."
},
"bracketNow": {
"message": "(nüüd)"
},
"moreCategories": {
"message": "Rohkem kategooriaid"
},
"chooseACategory": {
"message": "Vali kategooria"
},
"enableThisCategoryFirst": {
"message": "Kategooriaga \"{0}\" segmentide saatmiseks pead selle enne valikutes lubama. Sind suunatakse nüüd valikutesse.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Sa pead enne saatmist igale segmendile kategooria valima!"
},
"bracketEnd": {
"message": "(lõpp)"
},
"hiddenDueToDownvote": {
"message": "peidetud: vastuhääl"
},
"hiddenDueToDuration": {
"message": "peidetud: liiga lühike"
},
"channelDataNotFound": {
"message": "Kanali ID pole veel laaditud."
},
"adblockerIssue": {
"message": "Tundub, et miski segab SponsorBlocki video andmete hankimise võimalust. See on ilmselt sinu reklaamiblokeerija. Palun vaata https://github.com/ajayyy/SponsorBlock/wiki/Fix-Ad-Blocker-Blocking-SponsorBlock's-Requests"
},
"itCouldBeAdblockerIssue": {
"message": "Kui see jätkub, võib see olla põhjustatud sinu reklaamiblokeerijast. Palun vaata https://github.com/ajayyy/SponsorBlock/wiki/Fix-Ad-Blocker-Blocking-SponsorBlock's-Requests"
},
"forceChannelCheck": {
"message": "Sunnitud kanalikontroll enne vahelejätmist"
},
"forceChannelCheckPopup": {
"message": "Kaalu valiku \"Sunnitud kanalikontroll enne vahelejätmist\" lubamist"
},
"downvoteDescription": {
"message": "Sobimatu/vale ajastus"
},
"incorrectCategory": {
"message": "Vale kategooria"
},
"nonMusicCategoryOnMusic": {
"message": "See video on muusikana kategoriseeritud. Kas oled kindel, et sellel on sponsor? Kui see on tegelikult \"mitte-muusika segment\", ava laienduse valikud ning luba see kategooria. Seejärel saad selle segmendi saata \"mitte-muusika\" kategoorias. Segaduse korral palun loe üle juhised."
},
"multipleSegments": {
"message": "Mitu segmenti"
},
"guidelines": {
"message": "Juhised"
},
"readTheGuidelines": {
"message": "Loe juhiseid!!",
"description": "Show the first time they submit or if they are \"high risk\""
},
"categoryUpdate1": {
"message": "Kategooriad on siin!"
},
"categoryUpdate2": {
"message": "Ava valikud, et jätta vahele vaheaegu, müüdavat kaupa jms."
},
"unsubmittedWarning": {
"message": "Saatmata segmentide teatis"
},
"unsubmittedWarningDescription": {
"message": "Saada teavitus, kui lahkud videost segmentidega, mis ei ole üleslaaditud"
},
"help": {
"message": "Abi"
}
}

View File

@@ -30,12 +30,6 @@
"Loading": {
"message": "درحال بارگذاری..."
},
"Mins": {
"message": "دقیقه"
},
"Secs": {
"message": "ثانیه"
},
"Hide": {
"message": "هرگز نمایش نده"
},
@@ -102,9 +96,6 @@
"voteOnTime": {
"message": "رأی دهی به یک بخش"
},
"here": {
"message": "اینجا"
},
"clearTimesButton": {
"message": "حذف دفعات"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Ladataan..."
},
"Mins": {
"message": "Minuuttia"
},
"Secs": {
"message": "Sekuntia"
},
"Hide": {
"message": "Älä näytä koskaan"
},
@@ -121,9 +115,6 @@
"voteOnTime": {
"message": "Äänestä Segmenttiä"
},
"here": {
"message": "tässä"
},
"clearTimesButton": {
"message": "Tyhjennä ajat"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Chargement..."
},
"Mins": {
"message": "Minutes"
},
"Secs": {
"message": "Secondes"
},
"Hide": {
"message": "Ne plus montrer"
},
@@ -80,7 +74,7 @@
"message": "Aucun segment trouvé"
},
"sponsorStart": {
"message": "Début du segement"
"message": "Début du segment"
},
"sponsorEnd": {
"message": "Fin du segment"
@@ -121,11 +115,33 @@
"submitCheck": {
"message": "Êtes-vous sûr de vouloir soumettre ces segments?"
},
"whitelistChannel": {
"message": "Whitelister la chaîne"
},
"removeFromWhitelist": {
"message": "Enlever la chaîne de la liste blanche"
},
"voteOnTime": {
"message": "Voter pour un segment"
},
"here": {
"message": "ici"
"Submissions": {
"message": "Contributions"
},
"savedPeopleFrom": {
"message": "Vous avez sauvé les utilisateurs de "
},
"viewLeaderboard": {
"message": "Classement"
},
"recordTimesDescription": {
"message": "Envoyer"
},
"submissionEditHint": {
"message": "Le menu d'édition du segment apparaîtra après que vous ayez cliqué sur envoyer",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "Astuce : Vous pouvez configurer des raccourcis clavier dans les options"
},
"clearTimesButton": {
"message": "Supprimer les temps"
@@ -234,6 +250,12 @@
"0": {
"message": "Délai de connexion dépassé. Vérifiez votre connexion internet. Si votre connexion internet fonctionne, le serveur est probablement surchargé ou hors service."
},
"disableSkipping": {
"message": "Le saut de segment est activé"
},
"enableSkipping": {
"message": "Le saut de segment est désactivé"
},
"yourWork": {
"message": "Votre travail",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -289,6 +311,9 @@
"hoursLower": {
"message": "heures"
},
"youHaveSavedTime": {
"message": "Les utilisateurs ont gagné"
},
"statusReminder": {
"message": "Vérifiez status.sponsor.ajay.app pour le status du serveur."
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Učitavanje..."
},
"Mins": {
"message": "Minute"
},
"Secs": {
"message": "Sekunde"
},
"Hide": {
"message": "Nikad ne prikazuj"
},
@@ -118,9 +112,6 @@
"voteOnTime": {
"message": "Glasaj za isječak"
},
"here": {
"message": "ovdje"
},
"clearTimesButton": {
"message": "Očisti vremena"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Betöltés..."
},
"Mins": {
"message": "Perc"
},
"Secs": {
"message": "Másodperc"
},
"Hide": {
"message": "Ne mutassa többé"
},
@@ -121,9 +115,6 @@
"voteOnTime": {
"message": "Szavazzon a szegmensről"
},
"here": {
"message": "itt"
},
"clearTimesButton": {
"message": "Időpontok törlése"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Memuat..."
},
"Mins": {
"message": "Menit"
},
"Secs": {
"message": "Detik"
},
"Hide": {
"message": "Jangan tampilkan"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "Segmen Berakhir Sekarang"
},
"noVideoID": {
"message": "Video YouTube tidak ditemukan.\nJika ini salah, segarkan tab."
},
"success": {
"message": "Sukses!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "Apakah anda yakin ingin mengirim ini?"
},
"whitelistChannel": {
"message": "Saluran daftar putih"
},
"removeFromWhitelist": {
"message": "Hapus saluran dari daftar putih"
},
"voteOnTime": {
"message": "Beri Segmen Vote"
},
"here": {
"message": "disini"
"Submissions": {
"message": "Kiriman"
},
"savedPeopleFrom": {
"message": "Anda telah menyelamatkan orang dari "
},
"viewLeaderboard": {
"message": "Papan peringkat"
},
"recordTimesDescription": {
"message": "Kirim"
},
"submissionEditHint": {
"message": "Pengeditan bagian akan muncul setelah anda mengklik kirim",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "Petunjuk: Anda dapat mengatur keybinds untuk dikirim dalam opsi"
},
"clearTimesButton": {
"message": "Hapus Waktu"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "Ini digunakan di halaman statistik publik untuk menampilkan berapa banyak anda berkontribusi. Lihat disini"
},
"Username": {
"message": "Nama pengguna"
},
"setUsername": {
"message": "Atur Nama Pengguna"
},
@@ -228,6 +250,12 @@
"0": {
"message": "Koneksi Timeout. Cek koneksi internet anda. Jika internet anda berfungsi, server mungkin kewalahan atau down."
},
"disableSkipping": {
"message": "Melewati diaktifkan"
},
"enableSkipping": {
"message": "Melewati dinonaktifkan"
},
"yourWork": {
"message": "Hasil Kerja Anda",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "Waktu ini muncul di dalam kurung disamping waktu asli di bilah waktu. Ini menunjukkan durasi total video yang tidak termasuk segmen apapun. Ini termasuk segmen yang ditandai hanya \"Tampilkan Di Bilah Waktu\"."
},
"youHaveSkipped": {
"message": "Anda telah melewatkan "
},
"youHaveSaved": {
"message": "Anda sudah menghemat waktu "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "jam"
},
"youHaveSavedTime": {
"message": "Anda telah menyelamatkan orang"
},
"youHaveSavedTimeEnd": {
"message": " dalam hidup mereka"
},
"statusReminder": {
"message": "Cek status.sponsor.ajay.app untuk status server."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "Pilih Kategori"
},
"enableThisCategoryFirst": {
"message": "Untuk mengirimkan segmen dengan kategori \"{0}\", Anda harus mengaktifkannya di opsi. Anda akan diarahkan ke opsi sekarang.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Anda harus memilih kategori untuk semua segmen yang anda kirimkan!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "Tampilkan notifikasi saat kamu meninggalkan video dengan segmen yang belum diunggah"
},
"help": {
"message": "Bantuan"
}
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Caricamento..."
},
"Mins": {
"message": "Minuti"
},
"Secs": {
"message": "Secondi"
},
"Hide": {
"message": "Non mostrare più"
},
@@ -133,9 +127,6 @@
"viewLeaderboard": {
"message": "Classifica"
},
"here": {
"message": "qui"
},
"recordTimesDescription": {
"message": "Invia"
},

View File

@@ -21,12 +21,6 @@
"Loading": {
"message": "読み込み中..."
},
"Mins": {
"message": "分"
},
"Secs": {
"message": "秒"
},
"unskip": {
"message": "スキップしない"
},
@@ -39,9 +33,6 @@
"closePopup": {
"message": "ポップアップを閉じる"
},
"here": {
"message": "こちら"
},
"Options": {
"message": "オプション"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "로딩 중..."
},
"Mins": {
"message": "분"
},
"Secs": {
"message": "초"
},
"Hide": {
"message": "보지 않기"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "광고 구간 끝"
},
"noVideoID": {
"message": "찾은 유튜브 비디오가 없습니다.\n이 것이 틀리다면, 텝을 새로고침 하세요."
},
"success": {
"message": "성공!"
},
@@ -118,12 +115,15 @@
"submitCheck": {
"message": "정말로 제출하시겠습니까?"
},
"whitelistChannel": {
"message": "화이트리스트 체널"
},
"removeFromWhitelist": {
"message": "항목을 화이트리스트에서 삭제하기"
},
"voteOnTime": {
"message": "구간 투표"
},
"here": {
"message": "링크"
},
"clearTimesButton": {
"message": "시간 초기화"
},
@@ -595,5 +595,8 @@
},
"unsubmittedWarningDescription": {
"message": "업로드되지 않은 구간이 있는 영상이 있을 때 알림을 보냅니다"
},
"help": {
"message": "도움"
}
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "ലോഡിംഗ്..."
},
"Mins": {
"message": "മിനിറ്റ്"
},
"Secs": {
"message": "സെക്കൻഡ്"
},
"Hide": {
"message": "ഒരിക്കലും കാണിക്കരുത്"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "സെഗ്മെന്റ് ഇപ്പോൾ അവസാനിക്കുന്നു"
},
"noVideoID": {
"message": "YouTube വീഡിയോകളൊന്നും കണ്ടെത്തിയില്ല.\nഇത് തെറ്റാണെങ്കിൽ, ടാബ് പുതുക്കുക."
},
"success": {
"message": "വിജയം!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "ഇത് സമർപ്പിക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?"
},
"whitelistChannel": {
"message": "വൈറ്റ്‌ലിസ്റ്റ് ചാനൽ"
},
"removeFromWhitelist": {
"message": "വൈറ്റ്‌ലിസ്റ്റിൽ നിന്ന് ചാനൽ നീക്കംചെയ്യുക"
},
"voteOnTime": {
"message": "ഒരു സെഗ്‌മെന്റിൽ വോട്ടുചെയ്യുക"
},
"here": {
"message": "ഇവിടെ"
"Submissions": {
"message": "സമർപ്പിക്കലുകൾ"
},
"savedPeopleFrom": {
"message": "നിങ്ങൾ ആളുകളെ സംരക്ഷിച്ചു "
},
"viewLeaderboard": {
"message": "ലീഡർബോർഡ്"
},
"recordTimesDescription": {
"message": "സമർപ്പിക്കുക"
},
"submissionEditHint": {
"message": "സമർപ്പിക്കുക ക്ലിക്കുചെയ്‌തതിനുശേഷം വിഭാഗം എഡിറ്റിംഗ് ദൃശ്യമാകും",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "സൂചന: ഓപ്ഷനുകളിൽ സമർപ്പിക്കുന്നതിന് നിങ്ങൾക്ക് കീബൈൻഡുകൾ സജ്ജമാക്കാൻ കഴിയും"
},
"clearTimesButton": {
"message": "ടൈംസ് മായ്‌ക്കുക"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "നിങ്ങൾ എത്രമാത്രം സംഭാവന നൽകി എന്ന് കാണിക്കുന്നതിന് ഇത് പൊതു സ്ഥിതിവിവരക്കണക്ക് പേജിൽ ഉപയോഗിക്കുന്നു. അത് കാണുക"
},
"Username": {
"message": "ഉപയോക്തൃനാമം"
},
"setUsername": {
"message": "ഉപയോക്തൃനാമം സജ്ജമാക്കുക"
},
@@ -228,6 +250,12 @@
"0": {
"message": "കണക്ഷൻ കാലഹരണപ്പെട്ടു. നിങ്ങളുടെ ഇന്റർനെറ്റ് കണക്ഷൻ പരിശോധിക്കുക. നിങ്ങളുടെ ഇൻറർനെറ്റ് പ്രവർത്തിക്കുന്നുണ്ടെങ്കിൽ, സെർവർ ഓവർലോഡ് അല്ലെങ്കിൽ ഡ. ൺ ആയിരിക്കാം."
},
"disableSkipping": {
"message": "ഒഴിവാക്കൽ പ്രാപ്തമാക്കി"
},
"enableSkipping": {
"message": "ഒഴിവാക്കുന്നത് പ്രവർത്തനരഹിതമാക്കി"
},
"yourWork": {
"message": "നിങ്ങളുടെ ജോലി",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "സീക്ക്ബാറിന് താഴെയുള്ള നിലവിലെ സമയത്തിന് അടുത്തുള്ള ബ്രാക്കറ്റുകളിൽ ഈ സമയം ദൃശ്യമാകുന്നു. ഏത് സെഗ്‌മെന്റുകളുടെയും മൈനസ് മൊത്തം വീഡിയോ ദൈർഘ്യം ഇത് കാണിക്കുന്നു. \"സീക്ക്ബാറിൽ കാണിക്കുക\" എന്ന് മാത്രം അടയാളപ്പെടുത്തിയ സെഗ്‌മെന്റുകൾ ഇതിൽ ഉൾപ്പെടുന്നു."
},
"youHaveSkipped": {
"message": "നിങ്ങൾ ഒഴിവാക്കി "
},
"youHaveSaved": {
"message": "നിങ്ങൾ സ്വയം രക്ഷിച്ചു "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "മണിക്കൂറുകൾ"
},
"youHaveSavedTime": {
"message": "നിങ്ങൾ ആളുകളെ സംരക്ഷിച്ചു"
},
"youHaveSavedTimeEnd": {
"message": " അവരുടെ ജീവിതത്തിന്റെ"
},
"statusReminder": {
"message": "സെർവർ നിലയ്ക്കായി status.sponsor.ajay.app പരിശോധിക്കുക."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "ഒരു വിഭാഗം തിരഞ്ഞെടുക്കുക"
},
"enableThisCategoryFirst": {
"message": "\"{0}\" വിഭാഗത്തിൽ സെഗ്‌മെന്റുകൾ സമർപ്പിക്കുന്നതിന്, നിങ്ങൾ ഇത് ഓപ്ഷനുകളിൽ പ്രവർത്തനക്ഷമമാക്കണം. നിങ്ങളെ ഇപ്പോൾ ഓപ്ഷനുകളിലേക്ക് റീഡയറക്ട് ചെയ്യും.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "നിങ്ങൾ സമർപ്പിക്കുന്ന എല്ലാ സെഗ്‌മെന്റുകൾക്കും നിങ്ങൾ ഒരു വിഭാഗം തിരഞ്ഞെടുക്കണം!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "അപ്‌ലോഡ് ചെയ്യാത്ത സെഗ്‌മെന്റുകളുള്ള ഒരു വീഡിയോ നിങ്ങൾ ഉപേക്ഷിക്കുമ്പോൾ ഒരു അറിയിപ്പ് അയയ്‌ക്കുക"
},
"help": {
"message": "സഹായം"
}
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Memuat..."
},
"Mins": {
"message": "Minit"
},
"Secs": {
"message": "Detik"
},
"Hide": {
"message": "Jangan Tunjukkan"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "Segmen Berakhir Sekarang"
},
"noVideoID": {
"message": "Tiada video YouTube dijumpai.\nSekiranya ini tidak betul, muat semula tab."
},
"success": {
"message": "Berjaya!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "Adakah anda pasti mahu menghantarnya?"
},
"whitelistChannel": {
"message": "Saluran senarai putih"
},
"removeFromWhitelist": {
"message": "Alih keluar saluran dari senarai putih"
},
"voteOnTime": {
"message": "Undi Segmen"
},
"here": {
"message": "di sini"
"Submissions": {
"message": "Penyerahan"
},
"savedPeopleFrom": {
"message": "Anda telah menyelamatkan orang dari "
},
"viewLeaderboard": {
"message": "Papan pendahulu"
},
"recordTimesDescription": {
"message": "Hantar"
},
"submissionEditHint": {
"message": "Penyuntingan bahagian akan muncul setelah anda mengklik hantar",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "Petunjuk: Anda boleh menetapkan kunci untuk dihantar dalam pilihan"
},
"clearTimesButton": {
"message": "Jelas Masa"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "Ini digunakan di halaman statistik awam untuk menunjukkan berapa banyak yang anda sumbangkan. Lihatlah"
},
"Username": {
"message": "Nama pengguna"
},
"setUsername": {
"message": "Tetapkan Nama Pengguna"
},
@@ -228,6 +250,12 @@
"0": {
"message": "Masa sambungan telah tamat. Periksa sambungan internet anda. Sekiranya internet anda berfungsi, pelayan mungkin berlebihan atau tidak berfungsi."
},
"disableSkipping": {
"message": "Melangkau diaktifkan"
},
"enableSkipping": {
"message": "Melangkau dilumpuhkan"
},
"yourWork": {
"message": "Kerja awak",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "Kali ini muncul dalam tanda kurung di sebelah waktu semasa di bawah bar carian. Ini menunjukkan jumlah durasi video tolak segmen mana pun. Ini merangkumi segmen yang ditandai sebagai \"Show In Seekbar\" sahaja."
},
"youHaveSkipped": {
"message": "Anda telah melangkau "
},
"youHaveSaved": {
"message": "Anda telah menyelamatkan diri "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "jam"
},
"youHaveSavedTime": {
"message": "Anda telah menyelamatkan orang"
},
"youHaveSavedTimeEnd": {
"message": " kehidupan mereka"
},
"statusReminder": {
"message": "Periksa status.sponsor.ajay.app untuk status pelayan."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "Pilih Kategori"
},
"enableThisCategoryFirst": {
"message": "Untuk menghantar segmen dengan kategori \"{0}\", anda mesti mengaktifkannya dalam pilihan. Anda akan diarahkan ke pilihan sekarang.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Anda mesti memilih kategori untuk semua segmen yang anda kirimkan!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "Kirim pemberitahuan ketika anda meninggalkan video dengan segmen yang tidak diunggah"
},
"help": {
"message": "Bantuan"
}
}

View File

@@ -4,7 +4,7 @@
"description": "Name of the extension."
},
"Description": {
"message": "Overslaan van sponsoring, abonneer-herinneringen en meer in YouTube-video's. Rapporteer sponsors in video's die u bekijkt om anderen tijd te besparen.",
"message": "Overslaan van sponsors, vragen om te abonneren en meer in YouTube-video's. Rapporteer sponsors in video's die u bekijkt om anderen tijd te besparen.",
"description": "Description of the extension."
},
"400": {
@@ -14,7 +14,7 @@
"message": "U heeft te veel sponsortijdstippen ingediend voor deze video. Weet u zeker dat er zoveel zijn?"
},
"409": {
"message": "Dit is al een keer ingediend"
"message": "Dit is al eerder ingediend"
},
"channelWhitelisted": {
"message": "Kanaal gewhitelist!"
@@ -40,12 +40,6 @@
"Loading": {
"message": "Laden..."
},
"Mins": {
"message": "Minuten"
},
"Secs": {
"message": "Seconden"
},
"Hide": {
"message": "Nooit weergeven"
},
@@ -139,9 +133,6 @@
"viewLeaderboard": {
"message": "Ranglijst"
},
"here": {
"message": "hier"
},
"recordTimesDescription": {
"message": "Indienen"
},
@@ -581,6 +572,10 @@
"chooseACategory": {
"message": "Een categorie kiezen"
},
"enableThisCategoryFirst": {
"message": "Om segmenten met de categorie \"{0}\" in te dienen, moet u deze in de opties inschakelen. U wordt nu doorgestuurd naar de opties.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "U moet een categorie selecteren voor alle segmenten die u indient!"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Ładowanie..."
},
"Mins": {
"message": "Minuty"
},
"Secs": {
"message": "Sekundy"
},
"Hide": {
"message": "Nie pokazuj więcej"
},
@@ -65,10 +59,10 @@
"message": "Timer zatrzymany"
},
"confirmMSG": {
"message": "Żeby zmienić lub usunąć wartości, kliknij na guzik informacji lub otwórz okienko rozszerzenia klikając w ikonę rozszerzenia znajdującą się w prawym górnym rogu."
"message": "Aby edytować lub usuwać poszczególne wartości, kliknij na przycisk informacyjny lub otwórz okienko rozszerzenia, klikając na ikonę rozszerzenia w prawym górnym rogu."
},
"clearThis": {
"message": "Jesteś pewien, że chcesz to usunąć?\n\n"
"message": "Czy na pewno chcesz to usunąć?\n\n"
},
"Unknown": {
"message": "Wystąpił błąd podczas przesyłania twojego segmentu. Proszę spróbować ponownie później."
@@ -86,7 +80,7 @@
"message": "Koniec segmentu"
},
"noVideoID": {
"message": "Nie znaleziono filmu YouTube.\nJeśli jest to niepoprawne, odśwież stronę."
"message": "Nie znaleziono filmu YouTube.\nJeśli jest to nieprawidłowe, odśwież stronę."
},
"success": {
"message": "Sukces!"
@@ -101,10 +95,10 @@
"message": "Błąd z połączeniem. Kod błędu: "
},
"wantToSubmit": {
"message": "Czy chcesz zamieścić dla filmu o ID"
"message": "Czy chcesz coś zamieścić dla filmu o ID"
},
"leftTimes": {
"message": "Wygląda na to, że zostawiłeś pewne niewysłane segmenty. Cofnij się do tamtej strony, by je zamieścić (nie zostały usunięte)."
"message": "Wygląda na to, że pozostawiono pewne niewysłane segmenty. Cofnij się do tamtej strony, aby je zamieścić (nie zostały usunięte)."
},
"clearTimes": {
"message": "Wyczyść segmenty"
@@ -139,14 +133,11 @@
"viewLeaderboard": {
"message": "Ranking"
},
"here": {
"message": "tutaj"
},
"recordTimesDescription": {
"message": "Wyślij"
},
"submissionEditHint": {
"message": "Edycja sekcji pojawi się po kliknięciu wyślij",
"message": "Edycja sekcji pojawi się po kliknięciu",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
@@ -366,7 +357,7 @@
"message": "Dodaj instancje Invidious"
},
"addInvidiousInstanceDescription": {
"message": "Dodaj niestandardową instancje Invidious. W formie domeny. Na przykład: invidious.ajay.app"
"message": "Dodaj niestandardową instancję Invidious. Musi to być w formie samej domeny. Przykładowo: invidious.ajay.app"
},
"add": {
"message": "Dodaj"
@@ -581,6 +572,10 @@
"chooseACategory": {
"message": "Wybierz kategorię"
},
"enableThisCategoryFirst": {
"message": "Aby przesyłać segmenty o kategorii \"{0}\", musisz ją włączyć w opcjach. Zostaniesz przekierowany do ustawień.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Musisz wybrać kategorię dla każdego segmentu, który zamieszczasz!"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Carregando..."
},
"Mins": {
"message": "Minutos"
},
"Secs": {
"message": "Segundos"
},
"Hide": {
"message": "Nunca mostrar"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "O segmento termina agora"
},
"noVideoID": {
"message": "Nenhum vídeo do YouTube encontrado.\nSe isto estiver incorreto, atualize a aba."
},
"success": {
"message": "Sucesso!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "Você tem certeza que deseja enviar isto?"
},
"whitelistChannel": {
"message": "Canal na lista branca"
},
"removeFromWhitelist": {
"message": "Remover canal da lista branca"
},
"voteOnTime": {
"message": "Votar Em Um Segmento"
},
"here": {
"message": "aqui"
"Submissions": {
"message": "Envios"
},
"savedPeopleFrom": {
"message": "Você salvou pessoas de "
},
"viewLeaderboard": {
"message": "Classificação"
},
"recordTimesDescription": {
"message": "Enviar"
},
"submissionEditHint": {
"message": "A edição da seção aparecerá depois que você clicar em enviar",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "Dica: Você pode configurar os atalhos de tecla para enviar nas opções"
},
"clearTimesButton": {
"message": "Limpar Intervalos"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "Isso é usado na página pública de estatísticas que mostra o quanto você já contríbuíu. Veja-a"
},
"Username": {
"message": "Usuário"
},
"setUsername": {
"message": "Definir nome de usuário"
},
@@ -228,6 +250,12 @@
"0": {
"message": "Tempo limite de conexão excedida. Cheque a sua conexão de internet. Se a sua internet estiver funcionando, o servidor está sobrecarregado ou fora do ar."
},
"disableSkipping": {
"message": "Pular está habilitado"
},
"enableSkipping": {
"message": "Pular está desabilitado"
},
"yourWork": {
"message": "Suas submissões",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "Esta vez aparece em parênteses ao lado da hora atual abaixo da barra de procura. Isto mostra a duração total do vídeo menos qualquer segmento. Isto inclui segmentos marcados como apenas \"Mostrar em Seekbar\"."
},
"youHaveSkipped": {
"message": "Você pulou "
},
"youHaveSaved": {
"message": "Você poupou "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "horas"
},
"youHaveSavedTime": {
"message": "Você poupou das pessoas"
},
"youHaveSavedTimeEnd": {
"message": " de suas vidas"
},
"statusReminder": {
"message": "Verifique status.sponsor.ajay.app para o status do servidor."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "Selecione uma Categoria"
},
"enableThisCategoryFirst": {
"message": "Para enviar os segmentos com a categoria de \"{0}\", você deve ativá-lo nas opções. Você será redirecionado para as opções agora.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Você deve selecionar uma categoria para todos os segmentos que você está enviando!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "Enviar uma notificação quando você sair de um vídeo com segmentos que não foram enviados"
},
"help": {
"message": "Ajuda"
}
}

View File

@@ -24,12 +24,6 @@
"Loading": {
"message": "A carregar..."
},
"Mins": {
"message": "Minutos"
},
"Secs": {
"message": "Segundos"
},
"Hide": {
"message": "Nunca mostrar"
},
@@ -69,9 +63,6 @@
"submitCheck": {
"message": "Tem a certeza que pretende submeter?"
},
"here": {
"message": "aqui"
},
"clearTimesButton": {
"message": "Limpar Intervalos"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Se încarcă..."
},
"Mins": {
"message": "Minute"
},
"Secs": {
"message": "Secunde"
},
"Hide": {
"message": "Nu mai afișa niciodată"
},
@@ -121,9 +115,6 @@
"voteOnTime": {
"message": "Votează pe un Segment"
},
"here": {
"message": "aici"
},
"clearTimesButton": {
"message": "Șterge Timpii"
},

View File

@@ -40,14 +40,8 @@
"Loading": {
"message": "Загрузка..."
},
"Mins": {
"message": "мин"
},
"Secs": {
"message": "сек"
},
"Hide": {
"message": "Никогда не показывать"
"message": "Больше не показывать"
},
"hitGoBack": {
"message": "Нажмите «Назад», чтобы вернуться обратно."
@@ -121,6 +115,12 @@
"submitCheck": {
"message": "Вы уверены, что хотите отправить эту информацию?"
},
"whitelistChannel": {
"message": "Добавить канал в белый список"
},
"removeFromWhitelist": {
"message": "Удалить канал из белого списка"
},
"voteOnTime": {
"message": "Проголосовать за сегмент"
},
@@ -133,9 +133,6 @@
"viewLeaderboard": {
"message": "Доска почёта"
},
"here": {
"message": "здесь"
},
"recordTimesDescription": {
"message": "Отправить"
},
@@ -204,7 +201,7 @@
"message": "Эта возможность отслеживает, какие сегменты Вы пропустили, чтобы помочь пользователям узнать, насколько их вклад помог другим, и, наряду с голосами, используется как метрика, чтобы убедиться, что спам не попадает в базу данных. Расширение отправляет сообщение на сервер каждый раз, когда Вы пропускаете сегмент. Надеемся, большая часть пользователей не поменяет эту настройку, так что у нас будет точная статистика просмотров. :)"
},
"enableQueryByHashPrefix": {
"message": "Поиск по части хэша"
"message": "Запрос по префиксу хэша"
},
"whatQueryByHashPrefix": {
"message": "Вместо отправки на сервер ID видео, для получения сегментов будут использоваться первые 4 символа хэша ID. Сервер вернёт данные для всех видео с похожими хэшами."
@@ -575,6 +572,10 @@
"chooseACategory": {
"message": "Выберите категорию"
},
"enableThisCategoryFirst": {
"message": "Чтобы отправить сегменты категории \"{0}\", вы должны включить её в настройках. Сейчас вы будете туда перенаправлены.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Вы должны выбрать категорию для всех сегментов, которые вы отправляете!"
},

View File

@@ -24,12 +24,6 @@
"Loading": {
"message": "Načítavanie..."
},
"Mins": {
"message": "Minúty"
},
"Secs": {
"message": "Sekundy"
},
"Hide": {
"message": "Nikdy nezobrazovať"
},
@@ -72,9 +66,6 @@
"closePopup": {
"message": "Zavrieť okno"
},
"here": {
"message": "tu"
},
"hideThis": {
"message": "Skryť"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Laddar..."
},
"Mins": {
"message": "Minuter"
},
"Secs": {
"message": "Sekunder"
},
"Hide": {
"message": "Visa Aldrig"
},
@@ -121,18 +115,24 @@
"submitCheck": {
"message": "Är du säker på att du vill rapportera detta?"
},
"whitelistChannel": {
"message": "Vitlistkanal"
},
"removeFromWhitelist": {
"message": "Ta bort kanal från vitlistan"
},
"voteOnTime": {
"message": "Rösta på ett segment"
},
"Submissions": {
"message": "Inskickade"
},
"savedPeopleFrom": {
"message": "Du har sparat andra från "
},
"viewLeaderboard": {
"message": "Topplista"
},
"here": {
"message": "här"
},
"recordTimesDescription": {
"message": "Skicka"
},
@@ -196,6 +196,12 @@
"enableQueryByHashPrefix": {
"message": "Fråga efter hash-prefix"
},
"enableRefetchWhenNotFound": {
"message": "Uppdatera segment på nya videor"
},
"whatRefetchWhenNotFound": {
"message": "Om videon är ny och inga segment hittades då kommer den att uppdatera med några minuter mellanrum medan du tittar på videon."
},
"showNotice": {
"message": "Visa Notisen Igen"
},
@@ -476,6 +482,9 @@
"category_interaction": {
"message": "Interaktionspåminnelse (Prenumerera)"
},
"category_interaction_short": {
"message": "Interaktionspåminnelse"
},
"category_selfpromo": {
"message": "Obetald/självbefodran"
},
@@ -488,6 +497,9 @@
"category_music_offtopic_short": {
"message": "Icke-musik"
},
"category_livestream_messages": {
"message": "Liveström: Donations-/meddelandeavläsningar"
},
"category_livestream_messages_short": {
"message": "Läser meddelande"
},
@@ -500,6 +512,9 @@
"showOverlay": {
"message": "Visa Lager Ovanpå Spelare"
},
"colorFormatIncorrect": {
"message": "Din färg är felaktigt formaterad. Det ska vara en 3- eller 6-siffrig hex-kod med en siffra i början."
},
"previewColor": {
"message": "Förhandsgranskningsfärg",
"description": "Referring to submissions that have not been sent to the server yet."
@@ -532,6 +547,10 @@
"chooseACategory": {
"message": "Välj en kategori"
},
"enableThisCategoryFirst": {
"message": "För att skicka segment med kategorin \"{0}\" måste du först aktivera det i alternativen. Du kommer nu att bli omdirigerad till alternativen.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Du måste välja en kategori för alla segment du skickar in!"
},
@@ -565,6 +584,9 @@
"incorrectCategory": {
"message": "Fel kategori"
},
"nonMusicCategoryOnMusic": {
"message": "Den här videon kategoriseras som musik. Är du säker på att denna har en sponsor? Om detta faktiskt är ett \"icke-musiksegment\", öppna tilläggsalternativen och aktivera denna kategori. Då kan du skicka in detta segment som \"icke-musik\" i stället för sponsor. Läs riktlinjerna om något är oklart."
},
"multipleSegments": {
"message": "Flera segment"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "ஏற்றுகிறது..."
},
"Mins": {
"message": "நிமிடம்"
},
"Secs": {
"message": "நொடிகள்"
},
"Hide": {
"message": "ஒருபோதும் அனுமதிக்காதே"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "பிரிவு இப்போது முடிகிறது"
},
"noVideoID": {
"message": "YouTube வீடியோ எதுவும் கிடைக்கவில்லை.\nஇது தவறாக இருந்தால், தாவலைப் புதுப்பிக்கவும்."
},
"success": {
"message": "வெற்றி!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "இதை நிச்சயமாக சமர்ப்பிக்க விரும்புகிறீர்களா?"
},
"whitelistChannel": {
"message": "அனுமதிப்பட்டியல் சேனல்"
},
"removeFromWhitelist": {
"message": "அனுமதிப்பட்டியலில் இருந்து சேனலை அகற்று"
},
"voteOnTime": {
"message": "ஒரு பிரிவில் வாக்களியுங்கள்"
},
"here": {
"message": "இங்கே"
"Submissions": {
"message": "சமர்ப்பிப்புகள்"
},
"savedPeopleFrom": {
"message": "நீங்கள் மக்களை காப்பாற்றியுள்ளீர்கள் "
},
"viewLeaderboard": {
"message": "லீடர்போர்டு"
},
"recordTimesDescription": {
"message": "சமர்ப்பிக்கவும்"
},
"submissionEditHint": {
"message": "நீங்கள் சமர்ப்பி என்பதைக் கிளிக் செய்த பிறகு பிரிவு எடிட்டிங் தோன்றும்",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "குறிப்பு: விருப்பங்களில் சமர்ப்பிக்க விசைப்பலகைகளை அமைக்கலாம்"
},
"clearTimesButton": {
"message": "நேரங்களை அழி"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "நீங்கள் எவ்வளவு பங்களித்தீர்கள் என்பதைக் காட்ட இது பொது புள்ளிவிவரங்கள் பக்கத்தில் பயன்படுத்தப்படுகிறது. அதை பார்"
},
"Username": {
"message": "பயனர்பெயர்"
},
"setUsername": {
"message": "பயனர்பெயரை அமைக்கவும்"
},
@@ -228,6 +250,12 @@
"0": {
"message": "இணைப்பு நேரம் முடிந்தது. உங்கள் இணைய இணைப்பைச் சரிபார்க்கவும். உங்கள் இணையம் இயங்கினால், சேவையகம் அதிக சுமை அல்லது கீழே இருக்கும்."
},
"disableSkipping": {
"message": "ஸ்கிப்பிங் இயக்கப்பட்டது"
},
"enableSkipping": {
"message": "ஸ்கிப்பிங் முடக்கப்பட்டுள்ளது"
},
"yourWork": {
"message": "உங்கள் வேலை",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "இந்த நேரம் தற்போதைய நேரத்திற்கு அடுத்த அடைப்புக்குறிக்குள் தோன்றும். இது எந்தவொரு வீடியோவிற்கும் கழித்த மொத்த வீடியோ கால அளவைக் காட்டுகிறது. இதில் \"சீக்பாரில் காண்பி\" என்று மட்டுமே குறிக்கப்பட்ட பகுதிகள் அடங்கும்."
},
"youHaveSkipped": {
"message": "நீங்கள் தவிர்த்துவிட்டீர்கள் "
},
"youHaveSaved": {
"message": "உங்களை நீங்களே காப்பாற்றிக் கொண்டீர்கள் "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "மணி"
},
"youHaveSavedTime": {
"message": "நீங்கள் மக்களைக் காப்பாற்றியுள்ளீர்கள்"
},
"youHaveSavedTimeEnd": {
"message": " அவர்களின் வாழ்க்கையில்"
},
"statusReminder": {
"message": "சேவையக நிலைக்கு status.sponsor.ajay.app ஐச் சரிபார்க்கவும்."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "ஒரு வகையைத் தேர்வுசெய்க"
},
"enableThisCategoryFirst": {
"message": "\"{0}\" வகையுடன் பிரிவுகளைச் சமர்ப்பிக்க, நீங்கள் அதை விருப்பங்களில் இயக்க வேண்டும். நீங்கள் இப்போது விருப்பங்களுக்கு திருப்பி விடப்படுவீர்கள்.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "நீங்கள் சமர்ப்பிக்கும் அனைத்து பிரிவுகளுக்கும் ஒரு வகையைத் தேர்ந்தெடுக்க வேண்டும்!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "பதிவேற்றப்படாத பிரிவுகளுடன் வீடியோவை விட்டு வெளியேறும்போது அறிவிப்பை அனுப்பவும்"
},
"help": {
"message": "உதவி"
}
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "లోడ్ అవుతుంది..."
},
"Mins": {
"message": "నిమిషాలు"
},
"Secs": {
"message": "సెకన్లు"
},
"Hide": {
"message": "నెవర్ షో"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "సెగ్మెంట్ ఇప్పుడు ముగుస్తుంది"
},
"noVideoID": {
"message": "YouTube వీడియో కనుగొనబడలేదు.\nఇది తప్పు అయితే, టాబ్‌ను రిఫ్రెష్ చేయండి."
},
"success": {
"message": "విజయం!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "మీరు దీన్ని ఖచ్చితంగా సమర్పించాలనుకుంటున్నారా?"
},
"whitelistChannel": {
"message": "వైట్‌లిస్ట్ ఛానెల్"
},
"removeFromWhitelist": {
"message": "వైట్‌లిస్ట్ నుండి ఛానెల్‌ని తొలగించండి"
},
"voteOnTime": {
"message": "ఒక విభాగంలో ఓటు వేయండి"
},
"here": {
"message": "ఇక్కడ"
"Submissions": {
"message": "సమర్పణలు"
},
"savedPeopleFrom": {
"message": "మీరు ప్రజలను రక్షించారు "
},
"viewLeaderboard": {
"message": "లీడర్‌బోర్డ్"
},
"recordTimesDescription": {
"message": "సమర్పించండి"
},
"submissionEditHint": {
"message": "మీరు సమర్పించు క్లిక్ చేసిన తర్వాత విభాగం సవరణ కనిపిస్తుంది",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "సూచన: మీరు ఎంపికలలో సమర్పించడానికి కీబైండ్లను సెటప్ చేయవచ్చు"
},
"clearTimesButton": {
"message": "టైమ్స్ క్లియర్"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "మీరు ఎంత సహకరించారో చూపించడానికి ఇది పబ్లిక్ గణాంకాల పేజీలో ఉపయోగించబడుతుంది. ఇది చూడు"
},
"Username": {
"message": "వినియోగదారు పేరు"
},
"setUsername": {
"message": "వినియోగదారు పేరును సెట్ చేయండి"
},
@@ -228,6 +250,12 @@
"0": {
"message": "అనుసంధాన సమయం సమాప్తం. మీ ఇంటర్నెట్ కనెక్షన్‌ను తనిఖీ చేయండి. మీ ఇంటర్నెట్ పనిచేస్తుంటే, సర్వర్ ఓవర్‌లోడ్ లేదా డౌన్ అయి ఉండవచ్చు."
},
"disableSkipping": {
"message": "దాటవేయడం ప్రారంభించబడింది"
},
"enableSkipping": {
"message": "దాటవేయడం నిలిపివేయబడింది"
},
"yourWork": {
"message": "నీ పని",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "ఈ సమయం సీక్ బార్ క్రింద ప్రస్తుత సమయం పక్కన బ్రాకెట్లలో కనిపిస్తుంది. ఇది మొత్తం వీడియో వ్యవధి మైనస్ ఏదైనా విభాగాలను చూపుతుంది. ఇందులో \"సీక్బార్లో చూపించు\" అని మాత్రమే గుర్తించబడిన విభాగాలు ఉన్నాయి."
},
"youHaveSkipped": {
"message": "మీరు దాటవేశారు "
},
"youHaveSaved": {
"message": "మీరు మీరే రక్షించుకున్నారు "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "గంటలు"
},
"youHaveSavedTime": {
"message": "మీరు ప్రజలను రక్షించారు"
},
"youHaveSavedTimeEnd": {
"message": " వారి జీవితాల"
},
"statusReminder": {
"message": "సర్వర్ స్థితి కోసం status.sponsor.ajay.app ని తనిఖీ చేయండి."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "వర్గాన్ని ఎంచుకోండి"
},
"enableThisCategoryFirst": {
"message": "\"{0}\" వర్గంతో విభాగాలను సమర్పించడానికి, మీరు దీన్ని ఎంపికలలో ప్రారంభించాలి. మీరు ఇప్పుడు ఎంపికలకు మళ్ళించబడతారు.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "మీరు సమర్పించే అన్ని విభాగాల కోసం మీరు తప్పనిసరిగా ఒక వర్గాన్ని ఎంచుకోవాలి!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "మీరు అప్‌లోడ్ చేయని విభాగాలతో వీడియోను వదిలివేసినప్పుడు నోటిఫికేషన్ పంపండి"
},
"help": {
"message": "సహాయం"
}
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "กำลังโหลด..."
},
"Mins": {
"message": "นาที"
},
"Secs": {
"message": "วินาที"
},
"Hide": {
"message": "ไม่แสดงเสมอ"
},
@@ -70,9 +64,6 @@
"sponsor404": {
"message": "ไม่พบส่วนในวีดีโอนี้"
},
"here": {
"message": "ที่นี่"
},
"clearTimesButton": {
"message": "ล้างเวลา"
}

View File

@@ -23,7 +23,7 @@
"message": "kısım"
},
"Segments": {
"message": "kısımlar"
"message": "kısım"
},
"upvoteButtonInfo": {
"message": "Bu öneriye oy ver"
@@ -40,12 +40,6 @@
"Loading": {
"message": "Yükleniyor..."
},
"Mins": {
"message": "Dakika"
},
"Secs": {
"message": "Saniye"
},
"Hide": {
"message": "Asla Gösterme"
},
@@ -74,7 +68,7 @@
"message": "Sponsor sürelerini yollarken bir sorun oluştur, lütfen tekrar deneyin."
},
"sponsorFound": {
"message": "Bu videonun veri tabanımızda kısımları mevcut!"
"message": "Bu video için veritabanımızda kısımlar mevcut!"
},
"sponsor404": {
"message": "Kısımlar bulunamadı"
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "Kısım Şimdi Bitiyor"
},
"noVideoID": {
"message": "YouTube videosu bulunamadı.\nHatalı olduğunu düşünüyorsanız sayfayı yenileyin."
},
"success": {
"message": "Başarılı!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "Bunu göndermek istediğinize emin misiniz?"
},
"whitelistChannel": {
"message": "Kanalı beyaz listeye ekle"
},
"removeFromWhitelist": {
"message": "Kanalı beyaz listeden çıkar"
},
"voteOnTime": {
"message": "Bir Kısmı Oyla"
},
"here": {
"message": "burada"
"Submissions": {
"message": "Gönderimleriniz"
},
"savedPeopleFrom": {
"message": "İnsanları şu kadar kısımdan kurtardınız "
},
"viewLeaderboard": {
"message": "Lider Tablosu"
},
"recordTimesDescription": {
"message": "Gönder"
},
"submissionEditHint": {
"message": "Kısım düzenlemesi Gönder'e tıkladığınızda görünecek",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "İpucu: Gönderim için ayarlardan kısayol tuşu atayabilirsiniz"
},
"clearTimesButton": {
"message": "Süreleri Temizle"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "Bu, ne kadar katkı sağladığınızı göstermek için herkese açık istatistik sayfasında kullanılacaktır. Görün"
},
"Username": {
"message": "Kullanıcı adı"
},
"setUsername": {
"message": "Kullanıcı Adı Belirle"
},
@@ -228,6 +250,12 @@
"0": {
"message": "Bağlantı zaman aşımına uğradı. İnternet bağlantınızı kontrol ediniz. Eğer internetiniz çalışıyor ise, büyük ihtimalle sunucuya erişilemiyor veya sunucuya aşırı yüklenilmiş olabilir."
},
"disableSkipping": {
"message": "Atlama etkin"
},
"enableSkipping": {
"message": "Atlama devre dışı"
},
"yourWork": {
"message": "Çalışmalarınız",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "Bu süre, video ilerleme çubuğunun altındaki geçerli zamanın yanında parantez içinde görüntülenir. Bu, videodaki kısımların silinmiş toplam video süresini gösterir. Bu, yalnızca \"Video İlerleme Çubuğunda Göster\" olarak işaretlenen kısımları içerir."
},
"youHaveSkipped": {
"message": "Şu kadar kısım atladınız "
},
"youHaveSaved": {
"message": "Şu kadar süre kazandınız "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "saat"
},
"youHaveSavedTime": {
"message": "İnsanların şu kadar vaktini kurtardınız:"
},
"youHaveSavedTimeEnd": {
"message": " ömürden"
},
"statusReminder": {
"message": "Sunucu durumu için status.sponsor.ajay.app kontrol edin."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "Bir Kategori Seç"
},
"enableThisCategoryFirst": {
"message": "\"{0}\" kategorisinde bir kısım göndermek için onu ayarlardan açmalısınız. Ayarlara yönlendiriliyorsunuz.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Göndereceğin tüm kısımlar için bir kategori seçmelisin!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "Bir videodan kısımları göndermeden ayrılırsan bir bildirim gönderir"
},
"help": {
"message": "Yardım"
}
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Завантаження..."
},
"Mins": {
"message": "хв"
},
"Secs": {
"message": "сек"
},
"Hide": {
"message": "Не відображати"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "Сегмент закінчується тут"
},
"noVideoID": {
"message": "Відео YouTube, не знайдено.\nЯкщо це не так, поновіть вкладку."
},
"success": {
"message": "Успіх!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "Ви впевнені, що хочете надіслати цю інформацію?"
},
"whitelistChannel": {
"message": "Додати канал в білий список"
},
"removeFromWhitelist": {
"message": "Видалити канал з білого списку"
},
"voteOnTime": {
"message": "Проголосувати за сегмент"
},
"here": {
"message": "тут"
"Submissions": {
"message": "Надіслано сегментів"
},
"savedPeopleFrom": {
"message": "Ви допомогли людям пропустити "
},
"viewLeaderboard": {
"message": "Дошка пошани"
},
"recordTimesDescription": {
"message": "Надіслати"
},
"submissionEditHint": {
"message": "Редагування сегментів з'явиться після натискання на кнопку \"Надіслати\"",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "Підказка: Ви можете налаштувати комбінації клавіш для надсилання в опціях"
},
"clearTimesButton": {
"message": "Очистити час"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "Воно використовується на публічній сторінці статистики, щоб показати Ваш внесок. Її можна подивитися"
},
"Username": {
"message": "Ім'я користувача"
},
"setUsername": {
"message": "Встановити ім'я користувача"
},
@@ -228,6 +250,12 @@
"0": {
"message": "Таймаут підключення. Перевірте ваше з'єднання з інтернетом. Якщо ваш інтернет працює, сервер, швидше за все, перевантажений або лежить."
},
"disableSkipping": {
"message": "Пропуск увімкнено"
},
"enableSkipping": {
"message": "Пропуск вимкнено"
},
"yourWork": {
"message": "Ваша робота",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -265,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "Ця тривалість відображається в дужках поруч з фактичної під смугою прокрутки. Показує тривалість відео без сегментів. Включає сегменти, для яких вибрано \"Відображати в смузі прокрутки\"."
},
"youHaveSkipped": {
"message": "Ви пропустили "
},
"youHaveSaved": {
"message": "Ви заощадили "
},
@@ -280,6 +311,12 @@
"hoursLower": {
"message": "годин"
},
"youHaveSavedTime": {
"message": "Ви заощадили людям"
},
"youHaveSavedTimeEnd": {
"message": " їх життів"
},
"statusReminder": {
"message": "Дивіться стан сервера на status.sponsor.ajay.app."
},
@@ -535,6 +572,10 @@
"chooseACategory": {
"message": "Оберіть категорію"
},
"enableThisCategoryFirst": {
"message": "Щоб надіслати сегменти категорії \"{0}\", ви повинні включити її в налаштуваннях. Зараз ви будете туди перенаправлені.",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"youMustSelectACategory": {
"message": "Ви повинні обрати категорію для всіх сегментів, які ви відправляєте!"
},
@@ -595,5 +636,8 @@
},
"unsubmittedWarningDescription": {
"message": "Надсилати повідомлення, коли ви йдете зі сторінки відео, сегменти до якого Ви не надіслали"
},
"help": {
"message": "Допомога"
}
}

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "Đang tải..."
},
"Mins": {
"message": "Phút"
},
"Secs": {
"message": "Giây"
},
"Hide": {
"message": "Không hiển thị nữa"
},
@@ -121,9 +115,6 @@
"voteOnTime": {
"message": "Bầu chọn một đoạn quảng cáo"
},
"here": {
"message": "ở đây"
},
"clearTimesButton": {
"message": "Xóa thời gian"
},

View File

@@ -30,12 +30,6 @@
"Loading": {
"message": "加载中..."
},
"Mins": {
"message": "分钟"
},
"Secs": {
"message": "秒"
},
"Hide": {
"message": "不再显示"
},
@@ -81,9 +75,6 @@
"submitCheck": {
"message": "您确定要提交它吗?"
},
"here": {
"message": "这里"
},
"clearTimesButton": {
"message": "清除时间"
},

View File

@@ -40,12 +40,6 @@
"Loading": {
"message": "載入中…"
},
"Mins": {
"message": "分"
},
"Secs": {
"message": "秒"
},
"Hide": {
"message": "永不顯示"
},
@@ -85,6 +79,9 @@
"sponsorEnd": {
"message": "片段現在結束"
},
"noVideoID": {
"message": "找不到 YouTube 影片。\n如果這是不正確的重新整理此分頁"
},
"success": {
"message": "成功!"
},
@@ -118,11 +115,33 @@
"submitCheck": {
"message": "您確定要提交這個嗎?"
},
"whitelistChannel": {
"message": "將頻道列入白名單"
},
"removeFromWhitelist": {
"message": "將頻道從白名單移除"
},
"voteOnTime": {
"message": "為分段投票"
},
"here": {
"message": "這裡"
"Submissions": {
"message": "提交數"
},
"savedPeopleFrom": {
"message": "您已為大家節省 "
},
"viewLeaderboard": {
"message": "排行榜"
},
"recordTimesDescription": {
"message": "提交"
},
"submissionEditHint": {
"message": "段落編輯會在您提交之後出現",
"description": "Appears in the popup to inform them that editing has been moved to the video player."
},
"popupHint": {
"message": "小提醒:你可以為提交在設定裡綁定一個按鍵"
},
"clearTimesButton": {
"message": "清除時間"
@@ -133,6 +152,9 @@
"publicStats": {
"message": "這會被公開的統計頁面來展示您的貢獻。查看它"
},
"Username": {
"message": "使用者名稱"
},
"setUsername": {
"message": "設定使用者名稱"
},
@@ -228,6 +250,12 @@
"0": {
"message": "連線超時。請檢查您的網路連線。若您的網路運作正常,則可能是伺服器超載或離線"
},
"disableSkipping": {
"message": "跳過已啟用"
},
"enableSkipping": {
"message": "跳過已停用"
},
"yourWork": {
"message": "您的成果",
"description": "Used to describe the section that will show you the statistics from your submissions."
@@ -241,6 +269,9 @@
"skip": {
"message": "略過"
},
"skip_category": {
"message": "跳過 {0}"
},
"skipped": {
"message": "已跳過"
},
@@ -262,6 +293,9 @@
"showTimeWithSkipsDescription": {
"message": "這個時間會出現在現在時間旁邊的括號內。這會顯示總影片長度減掉任何片段後的結果。這只會包含標為\"在時間條中顯示\"的片段"
},
"youHaveSkipped": {
"message": "您已跳過 "
},
"youHaveSaved": {
"message": "您為自己節省了 "
},
@@ -277,6 +311,12 @@
"hoursLower": {
"message": "小時"
},
"youHaveSavedTime": {
"message": "您已為大家節省"
},
"youHaveSavedTimeEnd": {
"message": " 的生命。"
},
"statusReminder": {
"message": "在 status.sponsor.ajay.app 檢查伺服器狀態"
},
@@ -592,5 +632,8 @@
},
"unsubmittedWarningDescription": {
"message": "在您離開含有未提交的片段的影片時寄送通知"
},
"help": {
"message": "幫助"
}
}

View File

@@ -11,11 +11,6 @@
z-index: 40;
}
.sbHidden {
display: none !important;
}
.previewbar {
display: inline-block;
height: 100%;
@@ -23,12 +18,29 @@
/* Preview Bar page hacks */
.sbTooltipTwoTitleThumbnailOffset {
bottom: -5px !important;
.ytp-tooltip:not(.sponsorCategoryTooltipVisible) .sponsorCategoryTooltip {
display: none !important;
}
.sbTooltipOneTitleThumbnailOffset {
bottom: 10px !important;
.ytp-tooltip.sponsorCategoryTooltipVisible {
transform: translateY(-1em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible {
transform: translateY(-2em) !important;
}
#movie_player:not(.ytp-big-mode) .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper {
transform: translateY(1em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper {
transform: translateY(0.5em) !important;
}
.ytp-big-mode .ytp-tooltip.sponsorCategoryTooltipVisible > .ytp-tooltip-text-wrapper > .ytp-tooltip-text {
display: block !important;
transform: translateY(1em) !important;
}
/* */

View File

@@ -10,6 +10,18 @@
display: none !important;
}
@media only screen and (max-width: 600px) {
#sponsorBlockPopupBody {
width: 100%;
}
}
#sponsorBlockPopupBody {
margin: auto;
width: 374px;
background: var(--sb-main-bg-color);
}
#sponsorblockPopup {
color: var(--sb-main-fg-color);
font-family: 'Source Sans Pro', sans-serif;

View File

@@ -2,9 +2,11 @@
<title>__MSG_openPopup__</title>
<link id="sponsorBlockPopupFont" rel="stylesheet" type="text/css" href="/libs/Source+Sans+Pro.css">
<link id="sponsorBlockStyleSheet" rel="stylesheet" type="text/css" href="popup.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin: auto; width: 374px; background: var(--sb-main-bg-color);">
<body id="sponsorBlockPopupBody">
<div id="sponsorblockPopup" class="sponsorBlockPageBody preload">
<div class="logoText bottomSpace">
<img src="icons/IconSponsorBlocker256px.png" height="40px" id="sponsorBlockPopupLogo">

View File

@@ -2,8 +2,10 @@ import * as CompileConfig from "../config.json";
import Config from "./config";
import { Registration } from "./types";
// Make the config public for debugging purposes
(<any> window).SB = Config;
window.SB = Config;
import Utils from "./utils";
const utils = new Utils({
@@ -70,7 +72,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) {
});
//add help page on install
chrome.runtime.onInstalled.addListener(function (object) {
chrome.runtime.onInstalled.addListener(function () {
// This let's the config sync to run fully before checking.
// This is required on Firefox
setTimeout(function() {

View File

@@ -1,6 +1,5 @@
import * as React from "react";
import Config from "../config"
import * as CompileConfig from "../../config.json";
import CategorySkipOptionsComponent from "./CategorySkipOptionsComponent";

View File

@@ -2,9 +2,6 @@ import * as React from "react";
import Config from "../config"
import { CategorySkipOption } from "../types";
import Utils from "../utils";
const utils = new Utils();
export interface CategorySkipOptionsProps {
category: string;

View File

@@ -28,7 +28,7 @@ export interface NoticeState {
class NoticeComponent extends React.Component<NoticeProps, NoticeState> {
countdownInterval: NodeJS.Timeout;
idSuffix: any;
idSuffix: string;
amountOfPreviousNotices: number;

View File

@@ -3,7 +3,7 @@ import * as React from "react";
export interface NoticeTextSelectionProps {
text: string,
idSuffix: string,
onClick?: (event: React.MouseEvent) => any
onClick?: (event: React.MouseEvent) => unknown
}
export interface NoticeTextSelectionState {

View File

@@ -2,10 +2,6 @@ import * as React from "react";
import * as CompileConfig from "../../config.json";
import Config from "../config"
import { ContentContainer, SponsorHideType, SponsorTime } from "../types";
import Utils from "../utils";
const utils = new Utils();
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
@@ -42,7 +38,7 @@ export interface SkipNoticeState {
downvoting: boolean;
choosingCategory: boolean;
thanksForVotingText: boolean; //null until the voting buttons should be hidden
thanksForVotingText: string; //null until the voting buttons should be hidden
actionState: SkipNoticeAction;
}
@@ -447,25 +443,21 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
});
}
getUnskippedModeInfo(index: number, buttonText: string) {
const self = this;
const maxCountdownTime = function() {
const sponsorTime = self.segments[index];
const duration = Math.round((sponsorTime.segment[1] - self.contentContainer().v.currentTime) * (1 / self.contentContainer().v.playbackRate));
getUnskippedModeInfo(index: number, buttonText: string): SkipNoticeState {
const maxCountdownTime = () => {
const sponsorTime = this.segments[index];
const duration = Math.round((sponsorTime.segment[1] - this.contentContainer().v.currentTime) * (1 / this.contentContainer().v.playbackRate));
return Math.max(duration, 4);
};
return {
unskipText: buttonText,
unskipCallback: (index) => this.reskip(index),
//change max duration to however much of the sponsor is left
// change max duration to however much of the sponsor is left
maxCountdownTime: maxCountdownTime,
countdownTime: maxCountdownTime()
}
} as SkipNoticeState;
}
reskip(index: number): void {
@@ -508,7 +500,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
}
setNoticeInfoMessageWithOnClick(onClick: (event: React.MouseEvent) => any, ...messages: string[]): void {
setNoticeInfoMessageWithOnClick(onClick: (event: React.MouseEvent) => unknown, ...messages: string[]): void {
this.setState({
messages,
messageOnClick: (event) => onClick(event)
@@ -521,7 +513,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
});
}
addVoteButtonInfo(message): void {
addVoteButtonInfo(message: string): void {
this.setState({
thanksForVotingText: message
});

View File

@@ -23,6 +23,8 @@ export interface SponsorTimeEditState {
sponsorTimeEdits: [string, string];
}
const DEFAULT_CATEGORY = "chooseACategory";
class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, SponsorTimeEditState> {
idSuffix: string;
@@ -217,27 +219,17 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
getCategoryOptions(): React.ReactElement[] {
const elements = [(
<option value={"chooseACategory"}
key={"chooseACategory"}>
{chrome.i18n.getMessage("chooseACategory")}
<option value={DEFAULT_CATEGORY}
key={DEFAULT_CATEGORY}>
{chrome.i18n.getMessage(DEFAULT_CATEGORY)}
</option>
)];
for (const category of Config.config.categorySelections) {
for (const category of CompileConfig.categoryList) {
elements.push(
<option value={category.name}
key={category.name}>
{chrome.i18n.getMessage("category_" + category.name)}
</option>
);
}
if (elements.length < CompileConfig.categoryList.length) {
// Add show more button
elements.push(
<option value={"moreCategories"}
key={"moreCategories"}>
{chrome.i18n.getMessage("moreCategories")}
<option value={category}
key={category}>
{chrome.i18n.getMessage("category_" + category)}
</option>
);
}
@@ -247,15 +239,20 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
categorySelectionChange(event: React.ChangeEvent<HTMLSelectElement>): void {
// See if show more categories was pressed
if (event.target.value === "moreCategories") {
// Open options page
chrome.runtime.sendMessage({"message": "openConfig"});
// Reset option to previous
event.target.value = this.props.contentContainer().sponsorTimesSubmitting[this.props.index].category;
if (!Config.config.categorySelections.some((category) => category.name === event.target.value)) {
const chosenCategory = event.target.value;
event.target.value = DEFAULT_CATEGORY;
// Alert that they have to enable this category first
if (confirm(chrome.i18n.getMessage("enableThisCategoryFirst")
.replace("{0}", chrome.i18n.getMessage("category_" + chosenCategory)))) {
// Open options page
chrome.runtime.sendMessage({"message": "openConfig"});
}
return;
}
this.saveEditTimes();
}

View File

@@ -32,6 +32,8 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
videoObserver: MutationObserver;
showingYouCapNotice: boolean;
constructor(props: SubmissionNoticeProps) {
super(props);
this.noticeRef = React.createRef();
@@ -45,7 +47,7 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
this.state = {
noticeTitle,
messages: [],
idSuffix: "SubmissionNotice"
idSuffix: "SubmissionNotice",
}
}
@@ -87,6 +89,8 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
</td>
</tr>
{this.getYouCapMessage()}
{/* Last Row */}
<tr id={"sponsorSkipNoticeSecondRow" + this.state.idSuffix}>
@@ -113,6 +117,36 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S
);
}
/** TODO: Remove */
getYouCapMessage(): JSX.Element {
if (Config.config.sponsorTimesContributed < 20
|| (Config.config.hasShownYouCapNotice && !this.showingYouCapNotice)) {
return;
}
Config.config.hasShownYouCapNotice = true;
if (!this.showingYouCapNotice) {
this.showingYouCapNotice = true;
}
return (
<tr style={{textAlign: "center"}}>
<p style={{width: "300px", textAlign: "center", display: "inline-block"}}>
Like contributing to crowdsourced projects?
Consider checking out <a href="https://youcap.video/" style={{textDecoration: "underline"}}>YouCap</a>,
a new open-source replacement for YouTube{"'"}s now defunct community captions.
YouCap is NOT made by me, but I think it looks like a cool idea.
</p>
<img src={chrome.extension.getURL("icons/close.png")}
style={{padding: "0", margin: "auto"}}
className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeCloseButton"
onClick={() => { this.showingYouCapNotice = false; this.forceUpdate(); }}>
</img>
</tr>
);
}
getSponsorTimeMessages(): JSX.Element[] | JSX.Element {
const elements: JSX.Element[] = [];
this.timeEditRefs = [];

View File

@@ -1,5 +1,5 @@
import * as CompileConfig from "../config.json";
import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime } from "./types";
import { CategorySelection, CategorySkipOption, PreviewBarOption, SponsorTime, StorageChangesObject } from "./types";
import Utils from "./utils";
const utils = new Utils();
@@ -55,11 +55,13 @@ interface SBConfig {
"preview-selfpromo": PreviewBarOption,
"music_offtopic": PreviewBarOption,
"preview-music_offtopic": PreviewBarOption,
}
},
hasShownYouCapNotice: boolean
}
interface SBObject {
configListeners: Array<Function>;
export interface SBObject {
configListeners: Array<(changes: StorageChangesObject) => unknown>;
defaults: SBConfig;
localConfig: SBConfig;
config: SBConfig;
@@ -229,7 +231,9 @@ const Config: SBObject = {
color: "#a6634a",
opacity: "0.7"
}
}
},
hasShownYouCapNotice: false
},
localConfig: null,
config: null,
@@ -275,8 +279,8 @@ function decodeStoredItem<T>(id: string, data: T): T | SBMap<string, SponsorTime
return data;
}
function configProxy(): any {
chrome.storage.onChanged.addListener((changes, namespace) => {
function configProxy(): SBConfig {
chrome.storage.onChanged.addListener((changes: {[key: string]: chrome.storage.StorageChange}) => {
for (const key in changes) {
Config.localConfig[key] = decodeStoredItem(key, changes[key].newValue);
}
@@ -286,8 +290,8 @@ function configProxy(): any {
}
});
const handler: ProxyHandler<any> = {
set(obj, prop, value) {
const handler: ProxyHandler<SBConfig> = {
set<K extends keyof SBConfig>(obj: SBConfig, prop: K, value: SBConfig[K]) {
Config.localConfig[prop] = value;
chrome.storage.sync.set({
@@ -297,13 +301,13 @@ function configProxy(): any {
return true;
},
get(obj, prop): any {
get<K extends keyof SBConfig>(obj: SBConfig, prop: K): SBConfig[K] {
const data = Config.localConfig[prop];
return obj[prop] || data;
},
deleteProperty(obj, prop) {
deleteProperty(obj: SBConfig, prop: keyof SBConfig) {
chrome.storage.sync.remove(<string> prop);
return true;
@@ -311,11 +315,11 @@ function configProxy(): any {
};
return new Proxy({handler}, handler);
return new Proxy<SBConfig>({handler} as unknown as SBConfig, handler);
}
function fetchConfig(): Promise<void> {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
chrome.storage.sync.get(null, function(items) {
Config.localConfig = <SBConfig> <unknown> items; // Data is ready
resolve();
@@ -439,11 +443,6 @@ async function setupConfig() {
Config.config = config;
}
// Reset config
function resetConfig() {
Config.config = Config.defaults;
}
function convertJSON(): void {
Object.keys(Config.localConfig).forEach(key => {
Config.localConfig[key] = decodeStoredItem(key, Config.localConfig[key]);

View File

@@ -1,6 +1,6 @@
import Config from "./config";
import { SponsorTime, CategorySkipOption, CategorySelection, VideoID, SponsorHideType, FetchResponse } from "./types";
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, FetchResponse, VideoInfo, StorageChangesObject } from "./types";
import { ContentContainer } from "./types";
import Utils from "./utils";
@@ -8,10 +8,11 @@ const utils = new Utils();
import runThePopup from "./popup";
import PreviewBar from "./js-components/previewBar";
import PreviewBar, {PreviewBarSegment} from "./js-components/previewBar";
import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent";
import SubmissionNotice from "./render/SubmissionNotice";
import { Message, MessageResponse } from "./messageTypes";
// Hack to get the CSS loaded on permission-based sites (Invidious)
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
@@ -25,9 +26,9 @@ let sponsorTimes: SponsorTime[] = null;
let sponsorVideoID: VideoID = null;
// JSON video info
let videoInfo: any = null;
let videoInfo: VideoInfo = null;
//the channel this video is about
let channelID;
let channelID: string;
// Skips are scheduled to ensure precision.
// Skips are rescheduled every seeking event.
@@ -115,7 +116,7 @@ const skipNoticeContentContainer: ContentContainer = () => ({
//get messages from the background script and the popup
chrome.runtime.onMessage.addListener(messageListener);
function messageListener(request: any, sender: any, sendResponse: (response: any) => void): void {
function messageListener(request: Message, sender: unknown, sendResponse: (response: MessageResponse) => void): void {
//messages from popup script
switch(request.message){
case "update":
@@ -172,7 +173,6 @@ function messageListener(request: any, sender: any, sendResponse: (response: any
break;
case "submitTimes":
submitSponsorTimes();
break;
}
}
@@ -182,7 +182,7 @@ function messageListener(request: any, sender: any, sendResponse: (response: any
*
* @param {String} changes
*/
function contentConfigUpdateListener(changes) {
function contentConfigUpdateListener(changes: StorageChangesObject) {
for (const key in changes) {
switch(key) {
case "hideVideoPlayerControls":
@@ -234,7 +234,7 @@ function resetValues() {
//empty the preview bar
if (previewBar !== null) {
previewBar.set([], [], 0);
previewBar.clear();
}
//reset sponsor data found check
@@ -293,12 +293,18 @@ async function videoIDChange(id) {
if (onMobileYouTube) {
// Mobile YouTube workaround
const observer = new MutationObserver(handleMobileControlsMutations);
let controlsContainer = null;
observer.observe(document.getElementById("player-control-container"), {
attributes: true,
childList: true,
subtree: true
});
utils.wait(() => {
controlsContainer = document.getElementById("player-control-container")
return controlsContainer !== null
}).then(() => {
observer.observe(document.getElementById("player-control-container"), {
attributes: true,
childList: true,
subtree: true
});
}).catch();
} else {
utils.wait(getControls).then(createPreviewBar);
}
@@ -352,23 +358,13 @@ async function videoIDChange(id) {
}
function handleMobileControlsMutations(): void {
const mobileYouTubeSelector = ".progress-bar-background";
updateVisibilityOfPlayerControlsButton().then((createdButtons) => {
if (createdButtons) {
if (sponsorTimesSubmitting != null && sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length >= 2) {
changeStartSponsorButton(true, true);
} else if (sponsorTimesSubmitting != null && sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length < 2) {
changeStartSponsorButton(false, true);
} else {
changeStartSponsorButton(true, false);
}
}
});
if (previewBar !== null) {
if (document.body.contains(previewBar.container)) {
updatePreviewBarPositionMobile(document.getElementsByClassName(mobileYouTubeSelector)[0]);
const progressBarBackground = document.querySelector<HTMLElement>(".progress-bar-background");
if (progressBarBackground !== null) {
updatePreviewBarPositionMobile(progressBarBackground);
}
return;
} else {
@@ -399,11 +395,11 @@ function createPreviewBar(): void {
];
for (const selector of progressElementSelectors) {
const el = document.querySelectorAll(selector);
const el = document.querySelector<HTMLElement>(selector);
if (el) {
previewBar = new PreviewBar(el, onMobileYouTube, onInvidious);
if (el && el.length && el[0]) {
previewBar = new PreviewBar(el[0], onMobileYouTube, onInvidious);
updatePreviewBar();
break;
@@ -752,18 +748,18 @@ function startSkipScheduleCheckingForStartSponsors() {
/**
* Get the video info for the current tab from YouTube
*/
function getVideoInfo() {
sendRequestToCustomServer('GET', "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID, function(xmlhttp, error) {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
const decodedData = decodeURIComponent(xmlhttp.responseText).match(/player_response=([^&]*)/)[1];
if (!decodedData) {
console.error("[SB] Failed at getting video info from YouTube.");
return;
}
async function getVideoInfo(): Promise<void> {
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID);
videoInfo = JSON.parse(decodedData);
if (result.ok) {
const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1];
if (!decodedData) {
console.error("[SB] Failed at getting video info from YouTube.");
return;
}
});
videoInfo = JSON.parse(decodedData);
}
}
function getYouTubeVideoID(url: string) {
@@ -811,46 +807,53 @@ function getYouTubeVideoID(url: string) {
/**
* This function is required on mobile YouTube and will keep getting called whenever the preview bar disapears
*/
function updatePreviewBarPositionMobile(parent: Element) {
function updatePreviewBarPositionMobile(parent: HTMLElement) {
if (document.getElementById("previewbar") === null) {
previewBar.updatePosition(parent);
}
}
function updatePreviewBar(): void {
if(isAdPlaying) {
previewBar.set([], [], 0);
if (previewBar === null) return;
if (isAdPlaying) {
previewBar.clear();
return;
}
if (previewBar === null || video === null) return;
if (video === null) return;
let localSponsorTimes = sponsorTimes;
if (localSponsorTimes == null) localSponsorTimes = [];
const previewBarSegments: PreviewBarSegment[] = [];
const allSponsorTimes = localSponsorTimes.concat(sponsorTimesSubmitting);
//create an array of the sponsor types
const types = [];
for (let i = 0; i < localSponsorTimes.length; i++) {
if (localSponsorTimes[i].hidden === SponsorHideType.Visible) {
types.push(localSponsorTimes[i].category);
} else {
// Don't show this sponsor
types.push(null);
}
}
for (let i = 0; i < sponsorTimesSubmitting.length; i++) {
types.push("preview-" + sponsorTimesSubmitting[i].category);
if (sponsorTimes) {
sponsorTimes.forEach((segment) => {
if (segment.hidden !== SponsorHideType.Visible) return;
previewBarSegments.push({
segment: segment.segment as [number, number],
category: segment.category,
preview: false,
});
});
}
previewBar.set(utils.getSegmentsFromSponsorTimes(allSponsorTimes), types, video.duration)
sponsorTimesSubmitting.forEach((segment) => {
previewBarSegments.push({
segment: segment.segment as [number, number],
category: segment.category,
preview: true,
});
});
previewBar.set(previewBarSegments, video.duration)
if (Config.config.showTimeWithSkips) {
showTimeWithoutSkips(allSponsorTimes);
const skippedDuration = utils.getTimestampsDuration(previewBarSegments.map(({segment}) => segment));
showTimeWithoutSkips(skippedDuration);
}
//update last video id
// Update last video id
lastPreviewBarUpdate = sponsorVideoID;
}
@@ -1052,6 +1055,8 @@ function unskipSponsorTime(segment: SponsorTime) {
function reskipSponsorTime(segment: SponsorTime) {
video.currentTime = segment.segment[1];
startSponsorSchedule(true, segment.segment[1], false);
}
function createButton(baseID, title, callback, imageName, isDraggable=false): boolean {
@@ -1064,7 +1069,7 @@ function createButton(baseID, title, callback, imageName, isDraggable=false): bo
newButton.classList.add("playerButton");
newButton.classList.add("ytp-button");
newButton.setAttribute("title", chrome.i18n.getMessage(title));
newButton.addEventListener("click", (event: Event) => {
newButton.addEventListener("click", () => {
callback();
});
@@ -1131,6 +1136,7 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<boolean> {
if (!sponsorVideoID) return false;
const createdButtons = await createButtons();
if (!createdButtons) return;
if (Config.config.hideVideoPlayerControls || onInvidious) {
document.getElementById("startSponsorButton").style.display = "none";
@@ -1181,6 +1187,7 @@ function startSponsorClicked() {
if (sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length < 2) {
//it is an end time
sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment[1] = getRealCurrentTime();
sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.sort((a, b) => a > b ? 1 : (a < b ? -1 : 0));
} else {
//it is a start time
sponsorTimesSubmitting.push({
@@ -1222,8 +1229,8 @@ function updateSponsorTimesSubmitting(getFromConfig = true) {
}
}
async function changeStartSponsorButton(showStartSponsor, uploadButtonVisible) {
if(!sponsorVideoID) return false;
async function changeStartSponsorButton(showStartSponsor: boolean, uploadButtonVisible: boolean): Promise<boolean> {
if(!sponsorVideoID || onMobileYouTube) return false;
//if it isn't visible, there is no data
const shouldHide = (uploadButtonVisible && !(Config.config.hideDeleteButtonPlayerControls || onInvidious)) ? "unset" : "none"
@@ -1423,7 +1430,7 @@ function dontShowNoticeAgain() {
closeAllSkipNotices();
}
function sponsorMessageStarted(callback) {
function sponsorMessageStarted(callback: (response: MessageResponse) => void) {
video = document.querySelector('video');
//send back current time
@@ -1448,8 +1455,6 @@ function submitSponsorTimes() {
//it can't update to this info yet
closeInfoMenu();
const currentVideoID = sponsorVideoID;
if (sponsorTimesSubmitting !== undefined && sponsorTimesSubmitting.length > 0) {
submissionNotice = new SubmissionNotice(skipNoticeContentContainer, sendSubmitMessage);
}
@@ -1596,7 +1601,7 @@ function sendRequestToCustomServer(type, fullAddress, callback) {
callback(xmlhttp, false);
};
xmlhttp.onerror = function(ev) {
xmlhttp.onerror = function() {
callback(xmlhttp, true);
};
}
@@ -1618,37 +1623,28 @@ function updateAdFlag(): void {
}
}
function showTimeWithoutSkips(allSponsorTimes): void {
function showTimeWithoutSkips(skippedDuration: number): void {
if (onMobileYouTube || onInvidious) return;
let skipDuration = 0;
// Calculate skipDuration based from the segments in the preview bar
for (let i = 0; i < allSponsorTimes.length; i++) {
// If an end time exists
if (allSponsorTimes[i].segment[1]) {
skipDuration += allSponsorTimes[i].segment[1] - allSponsorTimes[i].segment[0];
}
}
// YouTube player time display
const display = document.getElementsByClassName("ytp-time-display notranslate")[0];
if (!display) return;
const formatedTime = utils.getFormattedTime(video.duration - skipDuration);
const durationID = "sponsorBlockDurationAfterSkips";
if (isNaN(skippedDuration) || skippedDuration < 0) {
skippedDuration = 0;
}
// YouTube player time display
const display = document.querySelector(".ytp-time-display.notranslate");
if (!display) return;
const durationID = "sponsorBlockDurationAfterSkips";
let duration = document.getElementById(durationID);
// Create span if needed
if(duration === null) {
duration = document.createElement('span');
// Create span if needed
if (duration === null) {
duration = document.createElement('span');
duration.id = durationID;
duration.classList.add("ytp-time-duration");
display.appendChild(duration);
}
duration.innerText = (skipDuration <= 0 || isNaN(skipDuration) || formatedTime.includes("NaN")) ? "" : " ("+formatedTime+")";
display.appendChild(duration);
}
duration.innerText = skippedDuration <= 0 ? "" : " (" + utils.getFormattedTime(video.duration - skippedDuration) + ")";
}

19
src/globals.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
import { SBObject } from "./config";
declare global {
interface Window { SB: SBObject; }
// Remove this once the API becomes stable and types are shipped in @types/chrome
namespace chrome {
namespace declarativeContent {
export interface RequestContentScriptOptions {
allFrames?: boolean;
css?: string[];
instanceType?: "declarativeContent.RequestContentScript";
js?: string[];
matchAboutBlanck?: boolean;
}
export class RequestContentScript {
constructor(options: RequestContentScriptOptions);
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
This is based on code from VideoSegments.
https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd51f62a4e7fef4d4/segmentsbar/segmentsbar.js
This is based on code from VideoSegments.
https://github.com/videosegments/videosegments/commits/f1e111bdfe231947800c6efdd51f62a4e7fef4d4/segmentsbar/segmentsbar.js
*/
'use strict';
@@ -9,179 +9,218 @@ import Config from "../config";
import Utils from "../utils";
const utils = new Utils();
class PreviewBar {
container: HTMLUListElement;
parent: any;
onMobileYouTube: boolean;
onInvidious: boolean;
const TOOLTIP_VISIBLE_CLASS = 'sponsorCategoryTooltipVisible';
timestamps: number[][];
types: string[];
constructor(parent: any, onMobileYouTube: boolean, onInvidious: boolean) {
this.container = document.createElement('ul');
this.container.id = 'previewbar';
this.parent = parent;
this.onMobileYouTube = onMobileYouTube;
this.onInvidious = onInvidious;
this.updatePosition(parent);
this.setupHoverText();
}
setupHoverText(): void {
if (this.onMobileYouTube || this.onInvidious) return;
const seekBar = document.querySelector(".ytp-progress-bar-container");
// Create label placeholder
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper");
const titleTooltip = document.querySelector(".ytp-tooltip-title");
const categoryTooltip = document.createElement("div");
categoryTooltip.className = "sbHidden ytp-tooltip-title";
categoryTooltip.id = "sponsor-block-category-tooltip"
tooltipTextWrapper.insertBefore(categoryTooltip, titleTooltip.nextSibling);
let mouseOnSeekBar = false;
seekBar.addEventListener("mouseenter", (event) => {
mouseOnSeekBar = true;
});
seekBar.addEventListener("mouseleave", (event) => {
mouseOnSeekBar = false;
categoryTooltip.classList.add("sbHidden");
});
const observer = new MutationObserver((mutations, observer) => {
if (!mouseOnSeekBar) return;
// See if mutation observed is only this ID (if so, ignore)
if (mutations.length == 1 && (mutations[0].target as HTMLElement).id === "sponsor-block-category-tooltip") {
return;
}
const tooltips = document.querySelectorAll(".ytp-tooltip-text");
for (const tooltip of tooltips) {
const splitData = tooltip.textContent.split(":");
if (splitData.length === 2 && !isNaN(parseInt(splitData[0])) && !isNaN(parseInt(splitData[1]))) {
// Add label
const timeInSeconds = parseInt(splitData[0]) * 60 + parseInt(splitData[1]);
// Find category at that location
let category = null;
for (let i = 0; i < this.timestamps?.length; i++) {
if (this.timestamps[i][0] < timeInSeconds && this.timestamps[i][1] > timeInSeconds){
category = this.types[i];
}
}
if (category === null && !categoryTooltip.classList.contains("sbHidden")) {
categoryTooltip.classList.add("sbHidden");
tooltipTextWrapper.classList.remove("sbTooltipTwoTitleThumbnailOffset");
tooltipTextWrapper.classList.remove("sbTooltipOneTitleThumbnailOffset");
} else if (category !== null) {
categoryTooltip.classList.remove("sbHidden");
categoryTooltip.textContent = utils.shortCategoryName(category)
|| (chrome.i18n.getMessage("preview") + " " + utils.shortCategoryName(category.split("preview-")[1]));
// There is a title now
tooltip.classList.remove("ytp-tooltip-text-no-title");
// Add the correct offset for the number of titles there are
if (titleTooltip.textContent !== "") {
if (!tooltipTextWrapper.classList.contains("sbTooltipTwoTitleThumbnailOffset")) {
tooltipTextWrapper.classList.add("sbTooltipTwoTitleThumbnailOffset");
}
} else if (!tooltipTextWrapper.classList.contains("sbTooltipOneTitleThumbnailOffset")) {
tooltipTextWrapper.classList.add("sbTooltipOneTitleThumbnailOffset");
}
}
break;
}
}
});
observer.observe(tooltipTextWrapper, {
childList: true,
subtree: true
});
}
updatePosition(parent: any): void {
//below the seek bar
// this.parent.insertAdjacentElement("afterEnd", this.container);
this.parent = parent;
if (this.onMobileYouTube) {
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
parent.style.opacity = "1";
this.container.style.transform = "none";
}
//on the seek bar
this.parent.insertAdjacentElement("afterBegin", this.container);
}
updateColor(segment: string, color: string, opacity: string): void {
const bars = <NodeListOf<HTMLElement>> document.querySelectorAll('[data-vs-segment-type=' + segment + ']');
for (const bar of bars) {
bar.style.backgroundColor = color;
bar.style.opacity = opacity;
}
}
set(timestamps: number[][], types: string[], duration: number): void {
while (this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
if (!timestamps || !types) {
return;
}
this.timestamps = timestamps;
this.types = types;
// to avoid rounding error resulting in width more than 100%
duration = Math.floor(duration * 100) / 100;
let width;
for (let i = 0; i < timestamps.length; i++) {
if (types[i] == null) continue;
width = (timestamps[i][1] - timestamps[i][0]) / duration * 100;
width = Math.floor(width * 100) / 100;
const bar = this.createBar();
bar.setAttribute('data-vs-segment-type', types[i]);
bar.style.backgroundColor = Config.config.barTypes[types[i]].color;
if (!this.onMobileYouTube) bar.style.opacity = Config.config.barTypes[types[i]].opacity;
bar.style.width = width + '%';
bar.style.left = (timestamps[i][0] / duration * 100) + "%";
bar.style.position = "absolute"
this.container.insertAdjacentElement("beforeend", bar);
}
}
createBar(): HTMLLIElement {
const bar = document.createElement('li');
bar.classList.add('previewbar');
bar.innerHTML = '&nbsp;';
return bar;
}
remove(): void {
this.container.remove();
this.container = undefined;
}
export interface PreviewBarSegment {
segment: [number, number];
category: string;
preview: boolean;
}
export default PreviewBar;
class PreviewBar {
container: HTMLUListElement;
categoryTooltip?: HTMLDivElement;
tooltipContainer?: HTMLElement;
parent: HTMLElement;
onMobileYouTube: boolean;
onInvidious: boolean;
segments: PreviewBarSegment[] = [];
videoDuration = 0;
constructor(parent: HTMLElement, onMobileYouTube: boolean, onInvidious: boolean) {
this.container = document.createElement('ul');
this.container.id = 'previewbar';
this.parent = parent;
this.onMobileYouTube = onMobileYouTube;
this.onInvidious = onInvidious;
this.updatePosition(parent);
this.setupHoverText();
}
setupHoverText(): void {
if (this.onMobileYouTube || this.onInvidious) return;
// Create label placeholder
this.categoryTooltip = document.createElement("div");
this.categoryTooltip.className = "ytp-tooltip-title sponsorCategoryTooltip";
const tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper");
if (!tooltipTextWrapper || !tooltipTextWrapper.parentElement) return;
// Grab the tooltip from the text wrapper as the tooltip doesn't have its classes on init
this.tooltipContainer = tooltipTextWrapper.parentElement;
const titleTooltip = tooltipTextWrapper.querySelector(".ytp-tooltip-title");
if (!this.tooltipContainer || !titleTooltip) return;
tooltipTextWrapper.insertBefore(this.categoryTooltip, titleTooltip.nextSibling);
const seekBar = document.querySelector(".ytp-progress-bar-container");
if (!seekBar) return;
let mouseOnSeekBar = false;
seekBar.addEventListener("mouseenter", () => {
mouseOnSeekBar = true;
});
seekBar.addEventListener("mouseleave", () => {
mouseOnSeekBar = false;
});
const observer = new MutationObserver((mutations) => {
if (!mouseOnSeekBar || !this.categoryTooltip || !this.tooltipContainer) return;
// If the mutation observed is only for our tooltip text, ignore
if (mutations.length === 1 && (mutations[0].target as HTMLElement).classList.contains("sponsorCategoryTooltip")) {
return;
}
const tooltipTextElements = tooltipTextWrapper.querySelectorAll(".ytp-tooltip-text");
let timeInSeconds: number | null = null;
let noYoutubeChapters = false;
for (const tooltipTextElement of tooltipTextElements) {
if (tooltipTextElement.classList.contains('ytp-tooltip-text-no-title')) noYoutubeChapters = true;
const tooltipText = tooltipTextElement.textContent;
if (tooltipText === null || tooltipText.length === 0) continue;
timeInSeconds = utils.getFormattedTimeToSeconds(tooltipText);
if (timeInSeconds !== null) break;
}
if (timeInSeconds === null) return;
// Find the segment at that location, using the shortest if multiple found
let segment: PreviewBarSegment | null = null;
let currentSegmentLength = Infinity;
for (const seg of this.segments) {
if (seg.segment[0] <= timeInSeconds && seg.segment[1] > timeInSeconds) {
const segmentLength = seg.segment[1] - seg.segment[0];
if (segmentLength < currentSegmentLength) {
currentSegmentLength = segmentLength;
segment = seg;
}
}
}
if (segment === null && this.tooltipContainer.classList.contains(TOOLTIP_VISIBLE_CLASS)) {
this.tooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
} else if (segment !== null) {
this.tooltipContainer.classList.add(TOOLTIP_VISIBLE_CLASS);
if (segment.preview) {
this.categoryTooltip.textContent = chrome.i18n.getMessage("preview") + " " + utils.shortCategoryName(segment.category);
} else {
this.categoryTooltip.textContent = utils.shortCategoryName(segment.category);
}
// Use the class if the timestamp text uses it to prevent overlapping
this.categoryTooltip.classList.toggle("ytp-tooltip-text-no-title", noYoutubeChapters);
}
});
observer.observe(tooltipTextWrapper, {
childList: true,
subtree: true,
});
}
updatePosition(parent: HTMLElement): void {
this.parent = parent;
if (this.onMobileYouTube) {
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
parent.style.opacity = "1";
this.container.style.transform = "none";
}
// On the seek bar
this.parent.prepend(this.container);
}
// TODO: call on config changes
updateColor(segmentType: string, color: string, opacity: number): void {
const bars = <NodeListOf<HTMLElement>> document.querySelectorAll('[data-vs-segment-type=' + segmentType + ']');
for (const bar of bars) {
bar.style.backgroundColor = color;
bar.style.opacity = String(opacity);
}
}
clear(): void {
this.videoDuration = 0;
this.segments = [];
while (this.container.firstChild) {
this.container.removeChild(this.container.firstChild);
}
}
set(segments: PreviewBarSegment[], videoDuration: number): void {
this.clear();
if (!segments) return;
this.segments = segments;
this.videoDuration = videoDuration;
this.segments.sort(({segment: a}, {segment: b}) => {
// Sort longer segments before short segments to make shorter segments render later
return (b[1] - b[0]) - (a[1] - a[0]);
}).forEach((segment) => {
const bar = this.createBar(segment);
this.container.appendChild(bar);
});
}
createBar({category, preview, segment}: PreviewBarSegment): HTMLLIElement {
const bar = document.createElement('li');
bar.classList.add('previewbar');
bar.innerHTML = '&nbsp;';
const barSegmentType = (preview ? 'preview-' : '') + category;
bar.setAttribute('data-vs-segment-type', barSegmentType);
bar.style.backgroundColor = Config.config.barTypes[barSegmentType].color;
if (!this.onMobileYouTube) bar.style.opacity = Config.config.barTypes[barSegmentType].opacity;
bar.style.position = "absolute";
bar.style.width = this.timeToPercentage(segment[1] - segment[0]);
bar.style.left = this.timeToPercentage(segment[0]);
return bar;
}
remove(): void {
this.container.remove();
if (this.categoryTooltip) {
this.categoryTooltip.remove();
this.categoryTooltip = undefined;
}
if (this.tooltipContainer) {
this.tooltipContainer.classList.remove(TOOLTIP_VISIBLE_CLASS);
this.tooltipContainer = undefined;
}
}
timeToPercentage(time: number): string {
return Math.min(100, time / this.videoDuration * 100) + '%';
}
}
export default PreviewBar;

63
src/messageTypes.ts Normal file
View File

@@ -0,0 +1,63 @@
//
// Message and Response Types
//
import { SponsorTime } from "./types";
interface BaseMessage {
from?: string;
}
interface DefaultMessage {
message:
"update"
| "sponsorStart"
| "sponsorDataChanged"
| "isInfoFound"
| "getVideoID"
| "getChannelID"
| "isChannelWhitelisted"
| "submitTimes";
}
interface BoolValueMessage {
message: "whitelistChange";
value: boolean;
}
interface ChangeStartSponsorButtonMessage {
message: "changeStartSponsorButton";
showStartSponsor: boolean;
uploadButtonVisible: boolean;
}
export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | ChangeStartSponsorButtonMessage);
interface IsInfoFoundMessageResponse {
found: boolean;
sponsorTimes: SponsorTime[];
}
interface GetVideoIdResponse {
videoID: string;
}
interface GetChannelIDResponse {
channelID: string;
}
interface SponsorStartResponse {
time: number;
}
interface IsChannelWhitelistedResponse {
value: boolean;
}
export type MessageResponse =
IsInfoFoundMessageResponse
| GetVideoIdResponse
| GetChannelIDResponse
| SponsorStartResponse
| IsChannelWhitelistedResponse;

View File

@@ -2,7 +2,7 @@ import Config from "./config";
import * as CompileConfig from "../config.json";
// Make the config public for debugging purposes
(<any> window).SB = Config;
window.SB = Config;
import Utils from "./utils";
import CategoryChooser from "./render/CategoryChooser";
@@ -107,7 +107,7 @@ async function init() {
// Permission needed on Firefox
if (utils.isFirefox()) {
const permissionSuccess = await new Promise((resolve, reject) => {
const permissionSuccess = await new Promise((resolve) => {
chrome.permissions.request({
origins: [textChangeInput.value + "/"],
permissions: []
@@ -202,7 +202,7 @@ async function init() {
*
* @param {String} element
*/
function optionsConfigUpdateListener(changes) {
function optionsConfigUpdateListener() {
const optionsContainer = document.getElementById("options");
const optionsElements = optionsContainer.querySelectorAll("*");
@@ -243,7 +243,7 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
const button = element.querySelector(".trigger-button");
const setButton = element.querySelector(".text-change-set");
setButton.addEventListener("click", async function(e) {
setButton.addEventListener("click", async function() {
if (textBox.value == "" || textBox.value.includes("/") || textBox.value.includes("http")) {
alert(chrome.i18n.getMessage("addInvidiousInstanceError"));
} else {
@@ -269,7 +269,7 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
});
const resetButton = element.querySelector(".invidious-instance-reset");
resetButton.addEventListener("click", function(e) {
resetButton.addEventListener("click", function() {
if (confirm(chrome.i18n.getMessage("resetInvidiousInstanceAlert"))) {
// Set to a clone of the default
Config.config[option] = Config.defaults[option].slice(0);
@@ -536,7 +536,7 @@ function copyDebugOutputToClipboard() {
.then(() => {
alert(chrome.i18n.getMessage("copyDebugInformationComplete"));
})
.catch((err) => {
.catch(() => {
alert(chrome.i18n.getMessage("copyDebugInformationFailed"));
});
}
}

View File

@@ -2,11 +2,12 @@ import Config from "./config";
import Utils from "./utils";
import { SponsorTime, SponsorHideType } from "./types";
import { Message, MessageResponse } from "./messageTypes";
const utils = new Utils();
interface MessageListener {
(request: any, sender: any, callback: (response: any) => void): void;
}
(request: Message, sender: unknown, sendResponse: (response: MessageResponse) => void): void;
}
class MessageHandler {
messageListener: MessageListener;
@@ -15,7 +16,7 @@ class MessageHandler {
this.messageListener = messageListener;
}
sendMessage(id: number, request, callback?) {
sendMessage(id: number, request: Message, callback?) {
if (this.messageListener) {
this.messageListener(request, null, callback);
} else {
@@ -37,6 +38,8 @@ class MessageHandler {
}
}
//make this a function to allow this to run on the content page
async function runThePopup(messageListener?: MessageListener): Promise<void> {
const messageHandler = new MessageHandler(messageListener);
@@ -45,7 +48,14 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
await utils.wait(() => Config.config !== null);
const PageElements: any = {};
type InputPageElements = {
whitelistToggle?: HTMLInputElement,
toggleSwitch?: HTMLInputElement,
usernameInput?: HTMLInputElement,
};
type PageElements = { [key: string]: HTMLElement } & InputPageElements
const PageElements: PageElements = {};
[
"sponsorblockPopup",

View File

@@ -3,7 +3,7 @@ import SkipNoticeComponent from "./components/SkipNoticeComponent";
interface ContentContainer {
(): {
vote: (type: any, UUID: any, category?: string, skipNotice?: SkipNoticeComponent) => void,
vote: (type: number, UUID: string, category?: string, skipNotice?: SkipNoticeComponent) => void,
dontShowNoticeAgain: () => void,
unskipSponsorTime: (segment: SponsorTime) => void,
sponsorTimes: SponsorTime[],
@@ -15,9 +15,9 @@ interface ContentContainer {
onMobileYouTube: boolean,
sponsorSubmissionNotice: SubmissionNotice,
resetSponsorSubmissionNotice: () => void,
changeStartSponsorButton: (showStartSponsor: any, uploadButtonVisible: any) => Promise<boolean>,
changeStartSponsorButton: (showStartSponsor: boolean, uploadButtonVisible: boolean) => Promise<boolean>,
previewTime: (time: number, unpause?: boolean) => void,
videoInfo: any,
videoInfo: VideoInfo,
getRealCurrentTime: () => number
}
}
@@ -78,8 +78,86 @@ interface BackgroundScriptContainer {
unregisterFirefoxContentScript: (id: string) => void
}
interface VideoInfo {
responseContext: {
serviceTrackingParams: Array<{service: string, params: Array<{key: string, value: string}>}>,
webResponseContextExtensionData: {
hasDecorated: boolean
}
},
playabilityStatus: {
status: string,
playableInEmbed: boolean,
miniplayer: {
miniplayerRenderer: {
playbackMode: string
}
}
};
streamingData: unknown;
playbackTracking: unknown;
videoDetails: {
videoId: string,
title: string,
lengthSeconds: string,
keywords: string[],
channelId: string,
isOwnerViewing: boolean,
shortDescription: string,
isCrawlable: boolean,
thumbnail: {
thumbnails: Array<{url: string, width: number, height: number}>
},
averageRating: number,
allowRatings: boolean,
viewCount: string,
author: string,
isPrivate: boolean,
isUnpluggedCorpus: boolean,
isLiveContent: boolean,
};
playerConfig: unknown;
storyboards: unknown;
microformat: {
playerMicroformatRenderer: {
thumbnail: {
thumbnails: Array<{url: string, width: number, height: number}>
},
embed: {
iframeUrl: string,
flashUrl: string,
width: number,
height: number,
flashSecureUrl: string,
},
title: {
simpleText: string,
},
description: {
simpleText: string,
},
lengthSeconds: string,
ownerProfileUrl: string,
externalChannelId: string,
availableCountries: string[],
isUnlisted: boolean,
hasYpcMetadata: boolean,
viewCount: string,
category: string,
publishDate: string,
ownerChannelName: string,
uploadDate: string,
}
};
trackingParams: string;
attestation: unknown;
messages: unknown;
}
type VideoID = string;
type StorageChangesObject = { [key: string]: chrome.storage.StorageChange };
export {
FetchResponse,
VideoDurationResponse,
@@ -91,5 +169,7 @@ export {
SponsorHideType,
PreviewBarOption,
Registration,
BackgroundScriptContainer
BackgroundScriptContainer,
VideoInfo,
StorageChangesObject,
};

View File

@@ -54,18 +54,16 @@ class Utils {
setupExtraSitePermissions(callback: (granted: boolean) => void): void {
// Request permission
let permissions = ["declarativeContent"];
if (this.isFirefox()) permissions = [];
const self = this;
if (this.isFirefox()) permissions = [];
chrome.permissions.request({
origins: this.getInvidiousInstancesRegex(),
permissions: permissions
}, async function (granted) {
}, async (granted) => {
if (granted) {
self.setupExtraSiteContentScripts();
this.setupExtraSiteContentScripts();
} else {
self.removeExtraSiteRegistration();
this.removeExtraSiteRegistration();
}
callback(granted);
@@ -80,7 +78,6 @@ class Utils {
* For now, it is just SB.config.invidiousInstances.
*/
setupExtraSiteContentScripts(): void {
const self = this;
if (this.isFirefox()) {
const firefoxJS = [];
@@ -107,9 +104,9 @@ class Utils {
chrome.runtime.sendMessage(registration);
}
} else {
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], function() {
chrome.declarativeContent.onPageChanged.removeRules(["invidious"], () => {
const conditions = [];
for (const regex of self.getInvidiousInstancesRegex()) {
for (const regex of this.getInvidiousInstancesRegex()) {
conditions.push(new chrome.declarativeContent.PageStateMatcher({
pageUrl: { urlMatches: regex }
}));
@@ -119,11 +116,10 @@ class Utils {
const rule = {
id: "invidious",
conditions,
// This API is experimental and not visible by the TypeScript compiler
actions: [new (<any> chrome.declarativeContent).RequestContentScript({
actions: [new chrome.declarativeContent.RequestContentScript({
allFrames: true,
js: self.js,
css: self.css
js: this.js,
css: this.css
})]
};
@@ -158,17 +154,54 @@ class Utils {
}
/**
* Gets just the timestamps from a sponsorTimes array
*
* @param sponsorTimes
* Merges any overlapping timestamp ranges into single segments and returns them as a new array.
*/
getSegmentsFromSponsorTimes(sponsorTimes: SponsorTime[]): number[][] {
const segments: number[][] = [];
for (const sponsorTime of sponsorTimes) {
segments.push(sponsorTime.segment);
}
getMergedTimestamps(timestamps: number[][]): [number, number][] {
let deduped: [number, number][] = [];
return segments;
// Cases ([] = another segment, <> = current range):
// [<]>, <[>], <[]>, [<>], [<][>]
timestamps.forEach((range) => {
// Find segments the current range overlaps
const startOverlaps = deduped.findIndex((other) => range[0] >= other[0] && range[0] <= other[1]);
const endOverlaps = deduped.findIndex((other) => range[1] >= other[0] && range[1] <= other[1]);
if (~startOverlaps && ~endOverlaps) {
// [<][>] Both the start and end of this range overlap another segment
// [<>] This range is already entirely contained within an existing segment
if (startOverlaps === endOverlaps) return;
// Remove the range with the higher index first to avoid the index shifting
const other1 = deduped.splice(Math.max(startOverlaps, endOverlaps), 1)[0];
const other2 = deduped.splice(Math.min(startOverlaps, endOverlaps), 1)[0];
// Insert a new segment spanning the start and end of the range
deduped.push([Math.min(other1[0], other2[0]), Math.max(other1[1], other2[1])]);
} else if (~startOverlaps) {
// [<]> The start of this range overlaps another segment, extend its end
deduped[startOverlaps][1] = range[1];
} else if (~endOverlaps) {
// <[>] The end of this range overlaps another segment, extend its beginning
deduped[endOverlaps][0] = range[0];
} else {
// No overlaps, just push in a copy
deduped.push(range.slice() as [number, number]);
}
// <[]> Remove other segments contained within this range
deduped = deduped.filter((other) => !(other[0] > range[0] && other[1] < range[1]));
});
return deduped;
}
/**
* Returns the total duration of the timestamps, taking into account overlaps.
*/
getTimestampsDuration(timestamps: number[][]): number {
return this.getMergedTimestamps(timestamps).reduce((acc, range) => {
return acc + range[1] - range[0];
}, 0);
}
getSponsorIndexFromUUID(sponsorTimes: SponsorTime[], UUID: string): number {