mirror of
https://github.com/iv-org/invidious.git
synced 2026-05-16 11:34:25 +05:30
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3540d7e7f | |||
| 0693f663cd | |||
| 35ee713cd8 | |||
| e76e46ad67 | |||
| e35531f181 |
@@ -29,7 +29,7 @@ jobs:
|
||||
- os: ubuntu-24.04-arm
|
||||
platform: linux/arm64/v8
|
||||
name: "ARM64"
|
||||
dockerfile: "docker/Dockerfile"
|
||||
dockerfile: "docker/Dockerfile.arm64"
|
||||
tag_suffix: "-arm64"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -39,10 +39,10 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: quay.io/invidious/invidious
|
||||
flavor: |
|
||||
@@ -62,34 +62,13 @@ jobs:
|
||||
quay.expires-after=12w
|
||||
|
||||
- name: Build and push Docker ${{ matrix.name }} image for Push Event
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
"release=1"
|
||||
|
||||
combine-multiarch-images:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_PASSWORD }}
|
||||
|
||||
# https://github.com/marketplace/actions/docker-manifest-create-action
|
||||
- name: Create and push manifest
|
||||
uses: int128/docker-manifest-create-action@v2.20.0
|
||||
with:
|
||||
push: true
|
||||
tags: quay.io/invidious/invidious:master
|
||||
sources: |
|
||||
quay.io/invidious/invidious:master
|
||||
quay.io/invidious/invidious:master-arm64
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- os: ubuntu-24.04-arm
|
||||
platform: linux/arm64/v8
|
||||
name: "ARM64"
|
||||
dockerfile: "docker/Dockerfile"
|
||||
dockerfile: "docker/Dockerfile.arm64"
|
||||
tag_suffix: "-arm64"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -30,10 +30,10 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: quay.io/invidious/invidious
|
||||
flavor: |
|
||||
@@ -54,34 +54,13 @@ jobs:
|
||||
quay.expires-after=12w
|
||||
|
||||
- name: Build and push Docker ${{ matrix.name }} image for Push Event
|
||||
uses: docker/build-push-action@v7
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
build-args: |
|
||||
"release=1"
|
||||
|
||||
combine-multiarch-images:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_PASSWORD }}
|
||||
|
||||
# https://github.com/marketplace/actions/docker-manifest-create-action
|
||||
- name: Create and push manifest
|
||||
uses: int128/docker-manifest-create-action@v2.20.0
|
||||
with:
|
||||
push: true
|
||||
tags: quay.io/invidious/invidious:latest
|
||||
sources: |
|
||||
quay.io/invidious/invidious:latest
|
||||
quay.io/invidious/invidious:latest-arm64
|
||||
|
||||
@@ -43,8 +43,6 @@ jobs:
|
||||
- 1.16.3
|
||||
- 1.17.1
|
||||
- 1.18.2
|
||||
- 1.19.2
|
||||
- 1.20.1
|
||||
include:
|
||||
- crystal: nightly
|
||||
stable: false
|
||||
@@ -60,7 +58,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Install Crystal
|
||||
uses: crystal-lang/install-crystal@v1.9.2
|
||||
uses: crystal-lang/install-crystal@v1.9.1
|
||||
with:
|
||||
crystal: ${{ matrix.crystal }}
|
||||
|
||||
@@ -82,7 +80,7 @@ jobs:
|
||||
run: crystal spec
|
||||
|
||||
- name: Build
|
||||
run: crystal build --warnings all --error-on-warnings --stats --time --progress --error-trace src/invidious.cr
|
||||
run: crystal build --warnings all --error-on-warnings --error-trace src/invidious.cr
|
||||
|
||||
build-docker:
|
||||
strategy:
|
||||
@@ -100,6 +98,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Use ARM64 Dockerfile if ARM64
|
||||
if: ${{ matrix.name == 'ARM64' }}
|
||||
run: sed -i 's/Dockerfile/Dockerfile.arm64/' docker-compose.yml
|
||||
|
||||
- name: Build Docker
|
||||
run: docker compose build
|
||||
|
||||
@@ -132,7 +134,7 @@ jobs:
|
||||
|
||||
- name: Install Crystal
|
||||
id: lint_step_install_crystal
|
||||
uses: crystal-lang/install-crystal@v1.9.2
|
||||
uses: crystal-lang/install-crystal@v1.9.1
|
||||
with:
|
||||
crystal: latest
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# CHANGELOG
|
||||
|
||||
## vX.Y.0 (future)
|
||||
|
||||
## v2.20260207.0
|
||||
|
||||
### Wrap-up
|
||||
|
||||
@@ -211,9 +211,9 @@ window.helpers = window.helpers || {
|
||||
helpers.storage.remove(key);
|
||||
}
|
||||
},
|
||||
set: function (key, value) {
|
||||
set: function (key, value) {
|
||||
let encoded_value = encodeURIComponent(JSON.stringify(value))
|
||||
localStorage.setItem(key, encoded_value);
|
||||
localStorage.setItem(key, encoded_value);
|
||||
},
|
||||
remove: function (key) { localStorage.removeItem(key); }
|
||||
};
|
||||
|
||||
+7
-14
@@ -104,15 +104,14 @@ if (video_data.params.quality === 'dash') {
|
||||
*
|
||||
* @param {String} url
|
||||
* @param {String} [base]
|
||||
* @param {'t' | 'start'} param
|
||||
* @returns {URL} urlWithTimeArg
|
||||
*/
|
||||
function addCurrentTimeToURL(url, base, param = 't') {
|
||||
function addCurrentTimeToURL(url, base) {
|
||||
var urlUsed = new URL(url, base);
|
||||
urlUsed.searchParams.delete('start');
|
||||
var currentTime = Math.ceil(player.currentTime());
|
||||
if (currentTime > 0)
|
||||
urlUsed.searchParams.set(param, currentTime);
|
||||
urlUsed.searchParams.set('t', currentTime);
|
||||
else if (urlUsed.searchParams.has('t'))
|
||||
urlUsed.searchParams.delete('t');
|
||||
return urlUsed;
|
||||
@@ -144,11 +143,11 @@ player.on('timeupdate', function () {
|
||||
let base_url_yt_watch = elem_yt_watch.getAttribute('data-base-url');
|
||||
elem_yt_watch.href = addCurrentTimeToURL(base_url_yt_watch);
|
||||
}
|
||||
|
||||
|
||||
let elem_yt_embed = document.getElementById('link-yt-embed');
|
||||
if (elem_yt_embed) {
|
||||
let base_url_yt_embed = elem_yt_embed.getAttribute('data-base-url');
|
||||
elem_yt_embed.href = addCurrentTimeToURL(base_url_yt_embed, undefined, 'start');
|
||||
elem_yt_embed.href = addCurrentTimeToURL(base_url_yt_embed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,18 +160,12 @@ player.on('timeupdate', function () {
|
||||
let base_url_iv_embed = elem_iv_embed.getAttribute('data-base-url');
|
||||
elem_iv_embed.href = addCurrentTimeToURL(base_url_iv_embed, domain);
|
||||
}
|
||||
|
||||
|
||||
let elem_iv_other = document.getElementById('link-iv-other');
|
||||
if (elem_iv_other) {
|
||||
let base_url_iv_other = elem_iv_other.getAttribute('data-base-url');
|
||||
elem_iv_other.href = addCurrentTimeToURL(base_url_iv_other, domain);
|
||||
}
|
||||
|
||||
let elem_iv_listen = document.getElementById('link-iv-listen');
|
||||
if (elem_iv_listen) {
|
||||
let base_url_iv_listen = elem_iv_listen.getAttribute('data-base-url');
|
||||
elem_iv_listen.href = addCurrentTimeToURL(base_url_iv_listen, domain);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -635,7 +628,7 @@ function toggle_caption_window() {
|
||||
player.textTrackSettings.setValues({ windowOpacity: options.windowOpacity[newIndex] });
|
||||
update_captions();
|
||||
}
|
||||
|
||||
|
||||
function toggle_caption_opacity() {
|
||||
const numOptions = options.textOpacity.length;
|
||||
const textOpacity = player.textTrackSettings.getValues().textOpacity || '1';
|
||||
@@ -740,7 +733,7 @@ addEventListener('keydown', function (e) {
|
||||
|
||||
case '>': action = increase_playback_rate.bind(this, 1); break;
|
||||
case '<': action = increase_playback_rate.bind(this, -1); break;
|
||||
|
||||
|
||||
case '=': action = increase_caption_size.bind(this, 1); break;
|
||||
case '-': action = increase_caption_size.bind(this, -1); break;
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ db:
|
||||
##
|
||||
## When this setting is commented out, Invidious companion is not used.
|
||||
## Otherwise, Invidious will proxy the requests to Invidious companion.
|
||||
##
|
||||
##
|
||||
## Note: multiple URL can be configured. In this case, Invidious will
|
||||
## randomly pick one every time video data needs to be retrieved. This
|
||||
## URL is then kept in the video metadata cache to allow video playback
|
||||
@@ -63,7 +63,7 @@ db:
|
||||
## The parameter private_url is required for the internal communication
|
||||
## between Invidious companion and Invidious.
|
||||
##
|
||||
## The optional parameter public_url is the public URL from which
|
||||
## The optional parameter public_url is the public URL from which
|
||||
## Invidious companion is listening to the requests from the user(s).
|
||||
## When this setting is commented out, Invidious proxy all requests to
|
||||
## Invidious companion. Useful for simple setups.
|
||||
@@ -232,7 +232,7 @@ https_only: false
|
||||
## Configuration for using a HTTP proxy
|
||||
## If unset, then no HTTP proxy will be used.
|
||||
## Proxy type supported: HTTP, HTTPS
|
||||
##
|
||||
##
|
||||
## This is not used for loading the video streams from YouTube servers (circumvent YouTube restrictions)
|
||||
## Please instead configure the proxy in Invidious companion:
|
||||
## https://github.com/iv-org/invidious-companion/blob/master/config/config.example.toml
|
||||
@@ -885,7 +885,7 @@ default_user_preferences:
|
||||
## Default: true
|
||||
##
|
||||
#vr_mode: true
|
||||
|
||||
|
||||
##
|
||||
## Save the playback position
|
||||
## Allow to continue watching at the previous position when
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
# https://github.com/openssl/openssl/releases/tag/openssl-3.6.2
|
||||
ARG OPENSSL_VERSION='3.6.2'
|
||||
ARG OPENSSL_SHA256='aaf51a1fe064384f811daeaeb4ec4dce7340ec8bd893027eee676af31e83a04f'
|
||||
# https://github.com/openssl/openssl/releases/tag/openssl-3.5.2
|
||||
ARG OPENSSL_VERSION='3.5.2'
|
||||
ARG OPENSSL_SHA256='c53a47e5e441c930c3928cf7bf6fb00e5d129b630e0aa873b08258656e7345ec'
|
||||
|
||||
FROM crystallang/crystal:1.20.1-alpine AS dependabot-crystal
|
||||
FROM crystallang/crystal:1.16.3-alpine AS dependabot-crystal
|
||||
|
||||
# We compile openssl ourselves due to a memory leak in how crystal interacts
|
||||
# with openssl
|
||||
@@ -43,7 +43,7 @@ COPY ./assets/ ./assets/
|
||||
COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||
|
||||
RUN crystal spec --warnings all \
|
||||
--link-flags "-lxml2 -llzma"
|
||||
--link-flags "-lxml2 -llzma"
|
||||
|
||||
ARG OPENSSL_VERSION
|
||||
COPY --from=openssl-builder /openssl-${OPENSSL_VERSION} /openssl-${OPENSSL_VERSION}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# https://github.com/openssl/openssl/releases/tag/openssl-3.5.2
|
||||
ARG OPENSSL_VERSION='3.5.2'
|
||||
ARG OPENSSL_SHA256='c53a47e5e441c930c3928cf7bf6fb00e5d129b630e0aa873b08258656e7345ec'
|
||||
|
||||
FROM alpine:3.22 AS dependabot-alpine
|
||||
|
||||
# We compile openssl ourselves due to a memory leak in how crystal interacts
|
||||
# with openssl
|
||||
# Reference: https://github.com/iv-org/invidious/issues/1438#issuecomment-3087636228
|
||||
FROM dependabot-alpine AS openssl-builder
|
||||
RUN apk add --no-cache curl perl linux-headers build-base
|
||||
|
||||
WORKDIR /
|
||||
|
||||
ARG OPENSSL_VERSION
|
||||
ARG OPENSSL_SHA256
|
||||
RUN curl -Ls "https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz" --output openssl-${OPENSSL_VERSION}.tar.gz
|
||||
RUN echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c
|
||||
RUN tar -xzvf openssl-${OPENSSL_VERSION}.tar.gz
|
||||
|
||||
RUN cd openssl-${OPENSSL_VERSION} && ./Configure --openssldir=/etc/ssl && make -j$(nproc)
|
||||
|
||||
FROM dependabot-alpine AS builder
|
||||
RUN apk add --no-cache 'crystal=1.16.3-r0' shards \
|
||||
sqlite-static yaml-static yaml-dev \
|
||||
pcre2-static gc-static \
|
||||
libxml2-static zlib-static \
|
||||
openssl-libs-static openssl-dev musl-dev xz-static
|
||||
|
||||
ARG release
|
||||
|
||||
WORKDIR /invidious
|
||||
COPY ./shard.yml ./shard.yml
|
||||
COPY ./shard.lock ./shard.lock
|
||||
RUN shards install --production
|
||||
|
||||
COPY ./src/ ./src/
|
||||
# TODO: .git folder is required for building – this is destructive.
|
||||
# See definition of CURRENT_BRANCH, CURRENT_COMMIT and CURRENT_VERSION.
|
||||
COPY ./.git/ ./.git/
|
||||
|
||||
# Required for fetching player dependencies
|
||||
COPY ./scripts/ ./scripts/
|
||||
COPY ./assets/ ./assets/
|
||||
COPY ./videojs-dependencies.yml ./videojs-dependencies.yml
|
||||
|
||||
RUN crystal spec --warnings all \
|
||||
--link-flags "-lxml2 -llzma"
|
||||
|
||||
ARG OPENSSL_VERSION
|
||||
COPY --from=openssl-builder /openssl-${OPENSSL_VERSION} /openssl-${OPENSSL_VERSION}
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/crystal if [[ "${release}" == 1 ]] ; then \
|
||||
PKG_CONFIG_PATH=/openssl-${OPENSSL_VERSION} \
|
||||
crystal build ./src/invidious.cr \
|
||||
--release \
|
||||
--static --warnings all \
|
||||
--link-flags "-lxml2 -llzma"; \
|
||||
else \
|
||||
PKG_CONFIG_PATH=/openssl-${OPENSSL_VERSION} \
|
||||
crystal build ./src/invidious.cr \
|
||||
--static --warnings all \
|
||||
--link-flags "-lxml2 -llzma"; \
|
||||
fi
|
||||
|
||||
FROM alpine:3.22
|
||||
RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
|
||||
WORKDIR /invidious
|
||||
RUN addgroup -g 1000 -S invidious && \
|
||||
adduser -u 1000 -S invidious -G invidious
|
||||
COPY --chown=invidious ./config/config.* ./config/
|
||||
RUN mv -n config/config.example.yml config/config.yml
|
||||
RUN sed -i 's/host: \(127.0.0.1\|localhost\)/host: invidious-db/' config/config.yml
|
||||
COPY ./config/sql/ ./config/sql/
|
||||
COPY ./locales/ ./locales/
|
||||
COPY --from=builder /invidious/assets ./assets/
|
||||
COPY --from=builder /invidious/invidious .
|
||||
RUN chmod o+rX -R ./assets ./config ./locales
|
||||
|
||||
EXPOSE 3000
|
||||
USER invidious
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD [ "/invidious/invidious" ]
|
||||
@@ -24,7 +24,7 @@ def create_licence_tr(path, file_name, licence_name, licence_link, source_locati
|
||||
"<tr>
|
||||
<td><a href=\\"/#{path}\\">#{file_name}</a></td>
|
||||
<td><a href=\\"#{licence_link}\\">#{licence_name}</a></td>
|
||||
<td><a href=\\"#{source_location}\\">\#{I18n.translate(locale, "source")}</a></td>
|
||||
<td><a href=\\"#{source_location}\\">\#{translate(locale, "source")}</a></td>
|
||||
</tr>"
|
||||
HTML
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# Crystal linter
|
||||
# This is a modified version of the pre-commit hook from the crystal repo. https://github.com/crystal-lang/crystal/blob/master/scripts/git/pre-commit
|
||||
# Please refer to that if you'd like an version that doesn't automatically format staged files.
|
||||
# Please refer to that if you'd like an version that doesn't automatically format staged files.
|
||||
changed_cr_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.cr$')
|
||||
if [ ! -z "$changed_cr_files" ]; then
|
||||
if [ -x bin/crystal ]; then
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: invidious
|
||||
version: 2.20260207.0-dev
|
||||
version: 2.20260207.0
|
||||
|
||||
authors:
|
||||
- Invidious team <contact@invidious.io>
|
||||
|
||||
@@ -48,7 +48,9 @@ FEATURE_FILTERS = {
|
||||
|
||||
SORT_FILTERS = {
|
||||
Invidious::Search::Filters::Sort::Relevance => "8AEB",
|
||||
Invidious::Search::Filters::Sort::Date => "CALwAQE%3D",
|
||||
Invidious::Search::Filters::Sort::Views => "CAPwAQE%3D",
|
||||
Invidious::Search::Filters::Sort::Rating => "CAHwAQE%3D",
|
||||
}
|
||||
|
||||
Spectator.describe Invidious::Search::Filters do
|
||||
|
||||
@@ -38,7 +38,7 @@ struct ChannelVideo
|
||||
json.field "authorId", self.ucid
|
||||
json.field "authorUrl", "/channel/#{self.ucid}"
|
||||
json.field "published", self.published.to_unix
|
||||
json.field "publishedText", I18n.translate(locale, "`x` ago", recode_date(self.published, locale))
|
||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
|
||||
|
||||
json.field "viewCount", self.views
|
||||
end
|
||||
|
||||
@@ -127,11 +127,11 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_sing
|
||||
|
||||
reply_count = short_text_to_number(post.dig?("actionButtons", "commentActionButtonsRenderer", "replyButton", "buttonRenderer", "text", "simpleText").try &.as_s || "0")
|
||||
|
||||
json.field "content", Helpers.html_to_content(content_html)
|
||||
json.field "content", html_to_content(content_html)
|
||||
json.field "contentHtml", content_html
|
||||
|
||||
json.field "published", published.to_unix
|
||||
json.field "publishedText", I18n.translate(locale, "`x` ago", recode_date(published, locale))
|
||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
|
||||
|
||||
json.field "likeCount", like_count
|
||||
json.field "replyCount", reply_count
|
||||
|
||||
@@ -254,7 +254,7 @@ module Invidious::Comments
|
||||
end
|
||||
|
||||
content_html = html_content || ""
|
||||
json.field "content", Helpers.html_to_content(content_html)
|
||||
json.field "content", html_to_content(content_html)
|
||||
json.field "contentHtml", content_html
|
||||
|
||||
if published_text != nil
|
||||
@@ -268,7 +268,7 @@ module Invidious::Comments
|
||||
end
|
||||
|
||||
json.field "published", published.to_unix
|
||||
json.field "publishedText", I18n.translate(locale, "`x` ago", recode_date(published, locale))
|
||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
|
||||
end
|
||||
|
||||
if node_replies && !response["commentRepliesContinuation"]?
|
||||
|
||||
@@ -28,14 +28,14 @@ module Invidious::Frontend::ChannelPage
|
||||
|
||||
if tab == selected_tab
|
||||
str << "\t<b>"
|
||||
str << I18n.translate(locale, "channel_tab_#{tab_name}_label")
|
||||
str << translate(locale, "channel_tab_#{tab_name}_label")
|
||||
str << "</b>\n"
|
||||
else
|
||||
# Video tab doesn't have the last path component
|
||||
url = tab.videos? ? base_url : "#{base_url}/#{tab_name}"
|
||||
|
||||
str << %(\t<a href=") << url << %(">)
|
||||
str << I18n.translate(locale, "channel_tab_#{tab_name}_label")
|
||||
str << translate(locale, "channel_tab_#{tab_name}_label")
|
||||
str << "</a>\n"
|
||||
end
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ module Invidious::Frontend::Comments
|
||||
<p>
|
||||
<a href="javascript:void(0)" data-onclick="toggle_parent">[ − ]</a>
|
||||
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b>
|
||||
#{I18n.translate_count(locale, "comments_points_count", child.score, I18n::NumberFormatting::Separator)}
|
||||
<span title="#{child.created_utc.to_s("%a %B %-d %T %Y UTC")}">#{I18n.translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span>
|
||||
<a href="https://www.reddit.com#{child.permalink}" title="#{I18n.translate(locale, "permalink")}">#{I18n.translate(locale, "permalink")}</a>
|
||||
#{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)}
|
||||
<span title="#{child.created_utc.to_s("%a %B %-d %T %Y UTC")}">#{translate(locale, "`x` ago", recode_date(child.created_utc, locale))}</span>
|
||||
<a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a>
|
||||
</p>
|
||||
<div>
|
||||
#{body_html}
|
||||
|
||||
@@ -6,10 +6,10 @@ module Invidious::Frontend::Comments
|
||||
root = comments["comments"].as_a
|
||||
root.each do |child|
|
||||
if child["replies"]?
|
||||
replies_count_text = I18n.translate_count(locale,
|
||||
replies_count_text = translate_count(locale,
|
||||
"comments_view_x_replies",
|
||||
child["replies"]["replyCount"].as_i64 || 0,
|
||||
I18n::NumberFormatting::Separator
|
||||
NumberFormatting::Separator
|
||||
)
|
||||
|
||||
replies_html = <<-END_HTML
|
||||
@@ -25,10 +25,10 @@ module Invidious::Frontend::Comments
|
||||
END_HTML
|
||||
elsif comments["authorId"]? && !comments["singlePost"]?
|
||||
# for posts we should display a link to the post
|
||||
replies_count_text = I18n.translate_count(locale,
|
||||
replies_count_text = translate_count(locale,
|
||||
"comments_view_x_replies",
|
||||
child["replyCount"].as_i64 || 0,
|
||||
I18n::NumberFormatting::Separator
|
||||
NumberFormatting::Separator
|
||||
)
|
||||
|
||||
replies_html = <<-END_HTML
|
||||
@@ -61,7 +61,7 @@ module Invidious::Frontend::Comments
|
||||
sponsor_icon = String.build do |str|
|
||||
str << %(<img alt="" )
|
||||
str << %(src="/ggpht) << URI.parse(child["sponsorIconUrl"].as_s).request_target << "\" "
|
||||
str << %(title=") << I18n.translate(locale, "Channel Sponsor") << "\" "
|
||||
str << %(title=") << translate(locale, "Channel Sponsor") << "\" "
|
||||
str << %(width="16" height="16" />)
|
||||
end
|
||||
end
|
||||
@@ -110,14 +110,14 @@ module Invidious::Frontend::Comments
|
||||
when "multiImage"
|
||||
html << <<-END_HTML
|
||||
<section class="carousel">
|
||||
<a class="skip-link" href="#skip-#{child["commentId"]}">#{I18n.translate(locale, "carousel_skip")}</a>
|
||||
<a class="skip-link" href="#skip-#{child["commentId"]}">#{translate(locale, "carousel_skip")}</a>
|
||||
<div class="slides">
|
||||
END_HTML
|
||||
image_array = attachment["images"].as_a
|
||||
|
||||
image_array.each_index do |i|
|
||||
html << <<-END_HTML
|
||||
<div class="slides-item slide-#{i + 1}" id="#{child["commentId"]}-slide-#{i + 1}" aria-label="#{I18n.translate(locale, "carousel_slide", {"current" => (i + 1).to_s, "total" => image_array.size.to_s})}" tabindex="0">
|
||||
<div class="slides-item slide-#{i + 1}" id="#{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_slide", {"current" => (i + 1).to_s, "total" => image_array.size.to_s})}" tabindex="0">
|
||||
<img loading="lazy" src="/ggpht#{URI.parse(image_array[i][1]["url"].as_s).request_target}" alt="" />
|
||||
</div>
|
||||
END_HTML
|
||||
@@ -129,7 +129,7 @@ module Invidious::Frontend::Comments
|
||||
END_HTML
|
||||
attachment["images"].as_a.each_index do |i|
|
||||
html << <<-END_HTML
|
||||
<a class="slider-nav" href="##{child["commentId"]}-slide-#{i + 1}" aria-label="#{I18n.translate(locale, "carousel_go_to", (i + 1).to_s)}" tabindex="-1" aria-hidden="true">#{i + 1}</a>
|
||||
<a class="slider-nav" href="##{child["commentId"]}-slide-#{i + 1}" aria-label="#{translate(locale, "carousel_go_to", (i + 1).to_s)}" tabindex="-1" aria-hidden="true">#{i + 1}</a>
|
||||
END_HTML
|
||||
end
|
||||
html << <<-END_HTML
|
||||
@@ -143,18 +143,18 @@ module Invidious::Frontend::Comments
|
||||
|
||||
html << <<-END_HTML
|
||||
<p>
|
||||
<span title="#{Time.unix(child["published"].as_i64).to_s(I18n.translate(locale, "%A %B %-d, %Y"))}">#{I18n.translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? I18n.translate(locale, "(edited)") : ""}</span>
|
||||
<span title="#{Time.unix(child["published"].as_i64).to_s(translate(locale, "%A %B %-d, %Y"))}">#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""}</span>
|
||||
|
|
||||
END_HTML
|
||||
|
||||
if comments["videoId"]?
|
||||
html << <<-END_HTML
|
||||
<a rel="noreferrer noopener" href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{I18n.translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||
<a rel="noreferrer noopener" href="https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||
|
|
||||
END_HTML
|
||||
elsif comments["authorId"]?
|
||||
html << <<-END_HTML
|
||||
<a rel="noreferrer noopener" href="https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" title="#{I18n.translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||
<a rel="noreferrer noopener" href="https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" title="#{translate(locale, "YouTube comment permalink")}">[YT]</a>
|
||||
|
|
||||
END_HTML
|
||||
end
|
||||
@@ -172,7 +172,7 @@ module Invidious::Frontend::Comments
|
||||
|
||||
html << <<-END_HTML
|
||||
|
||||
<span class="creator-heart-container" title="#{I18n.translate(locale, "`x` marked it with a ❤", child["creatorHeart"]["creatorName"].as_s)}">
|
||||
<span class="creator-heart-container" title="#{translate(locale, "`x` marked it with a ❤", child["creatorHeart"]["creatorName"].as_s)}">
|
||||
<span class="creator-heart">
|
||||
<img loading="lazy" class="creator-heart-background-hearted" src="#{creator_thumbnail}" alt="" />
|
||||
<span class="creator-heart-small-hearted">
|
||||
@@ -197,7 +197,7 @@ module Invidious::Frontend::Comments
|
||||
<div class="pure-u-1">
|
||||
<p>
|
||||
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}"
|
||||
data-onclick="get_youtube_replies" data-load-more #{"data-load-replies" if is_replies}>#{I18n.translate(locale, "Load more")}</a>
|
||||
data-onclick="get_youtube_replies" data-load-more #{"data-load-replies" if is_replies}>#{translate(locale, "Load more")}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,16 +6,16 @@ module Invidious::Frontend::Pagination
|
||||
private def first_page(str : String::Builder, locale : String?, url : String)
|
||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
||||
|
||||
if I18n.locale_is_rtl?(locale)
|
||||
if locale_is_rtl?(locale)
|
||||
# Inverted arrow ("first" points to the right)
|
||||
str << I18n.translate(locale, "First page")
|
||||
str << translate(locale, "First page")
|
||||
str << " "
|
||||
str << %(<i class="icon ion-ios-arrow-forward"></i>)
|
||||
else
|
||||
# Regular arrow ("first" points to the left)
|
||||
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
||||
str << " "
|
||||
str << I18n.translate(locale, "First page")
|
||||
str << translate(locale, "First page")
|
||||
end
|
||||
|
||||
str << "</a>"
|
||||
@@ -25,16 +25,16 @@ module Invidious::Frontend::Pagination
|
||||
# Link
|
||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
||||
|
||||
if I18n.locale_is_rtl?(locale)
|
||||
if locale_is_rtl?(locale)
|
||||
# Inverted arrow ("previous" points to the right)
|
||||
str << I18n.translate(locale, "Previous page")
|
||||
str << translate(locale, "Previous page")
|
||||
str << " "
|
||||
str << %(<i class="icon ion-ios-arrow-forward"></i>)
|
||||
else
|
||||
# Regular arrow ("previous" points to the left)
|
||||
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
||||
str << " "
|
||||
str << I18n.translate(locale, "Previous page")
|
||||
str << translate(locale, "Previous page")
|
||||
end
|
||||
|
||||
str << "</a>"
|
||||
@@ -44,14 +44,14 @@ module Invidious::Frontend::Pagination
|
||||
# Link
|
||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
||||
|
||||
if I18n.locale_is_rtl?(locale)
|
||||
if locale_is_rtl?(locale)
|
||||
# Inverted arrow ("next" points to the left)
|
||||
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
||||
str << " "
|
||||
str << I18n.translate(locale, "Next page")
|
||||
str << translate(locale, "Next page")
|
||||
else
|
||||
# Regular arrow ("next" points to the right)
|
||||
str << I18n.translate(locale, "Next page")
|
||||
str << translate(locale, "Next page")
|
||||
str << " "
|
||||
str << %(<i class="icon ion-ios-arrow-forward"></i>)
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ module Invidious::Frontend::SearchFilters
|
||||
return String.build(8000) do |str|
|
||||
str << "<div id='filters'>\n"
|
||||
str << "\t<details id='filters-collapse'>"
|
||||
str << "\t\t<summary>" << I18n.translate(locale, "search_filters_title") << "</summary>\n"
|
||||
str << "\t\t<summary>" << translate(locale, "search_filters_title") << "</summary>\n"
|
||||
|
||||
str << "\t\t<div id='filters-box'><form action='/search' method='get'>\n"
|
||||
|
||||
@@ -25,7 +25,7 @@ module Invidious::Frontend::SearchFilters
|
||||
|
||||
str << "\t\t\t<div id='filters-apply'>"
|
||||
str << "<button type='submit' class=\"pure-button pure-button-primary\">"
|
||||
str << I18n.translate(locale, "search_filters_apply_button")
|
||||
str << translate(locale, "search_filters_apply_button")
|
||||
str << "</button></div>\n"
|
||||
|
||||
str << "\t\t</form></div>\n"
|
||||
@@ -41,7 +41,7 @@ module Invidious::Frontend::SearchFilters
|
||||
str << "\t\t\t\t<div class=\"filter-column\"><fieldset>\n"
|
||||
|
||||
str << "\t\t\t\t\t<legend><div class=\"filter-name underlined\">"
|
||||
str << I18n.translate(locale, "search_filters_{{name}}_label")
|
||||
str << translate(locale, "search_filters_{{name}}_label")
|
||||
str << "</div></legend>\n"
|
||||
|
||||
str << "\t\t\t\t\t<div class=\"filter-options\">\n"
|
||||
@@ -62,7 +62,7 @@ module Invidious::Frontend::SearchFilters
|
||||
str << '>'
|
||||
|
||||
str << "<label for='filter-date-{{date}}'>"
|
||||
str << I18n.translate(locale, "search_filters_date_option_{{date}}")
|
||||
str << translate(locale, "search_filters_date_option_{{date}}")
|
||||
str << "</label></div>\n"
|
||||
{% end %}
|
||||
end
|
||||
@@ -78,7 +78,7 @@ module Invidious::Frontend::SearchFilters
|
||||
str << '>'
|
||||
|
||||
str << "<label for='filter-type-{{type}}'>"
|
||||
str << I18n.translate(locale, "search_filters_type_option_{{type}}")
|
||||
str << translate(locale, "search_filters_type_option_{{type}}")
|
||||
str << "</label></div>\n"
|
||||
{% end %}
|
||||
end
|
||||
@@ -94,7 +94,7 @@ module Invidious::Frontend::SearchFilters
|
||||
str << '>'
|
||||
|
||||
str << "<label for='filter-duration-{{duration}}'>"
|
||||
str << I18n.translate(locale, "search_filters_duration_option_{{duration}}")
|
||||
str << translate(locale, "search_filters_duration_option_{{duration}}")
|
||||
str << "</label></div>\n"
|
||||
{% end %}
|
||||
end
|
||||
@@ -111,7 +111,7 @@ module Invidious::Frontend::SearchFilters
|
||||
str << '>'
|
||||
|
||||
str << "<label for='filter-feature-{{feature}}'>"
|
||||
str << I18n.translate(locale, "search_filters_features_option_{{feature}}")
|
||||
str << translate(locale, "search_filters_features_option_{{feature}}")
|
||||
str << "</label></div>\n"
|
||||
{% end %}
|
||||
{% end %}
|
||||
@@ -128,7 +128,7 @@ module Invidious::Frontend::SearchFilters
|
||||
str << '>'
|
||||
|
||||
str << "<label for='filter-sort-{{sort}}'>"
|
||||
str << I18n.translate(locale, "search_filters_sort_option_{{sort}}")
|
||||
str << translate(locale, "search_filters_sort_option_{{sort}}")
|
||||
str << "</label></div>\n"
|
||||
{% end %}
|
||||
end
|
||||
|
||||
@@ -20,11 +20,11 @@ module Invidious::Frontend::WatchPage
|
||||
|
||||
def download_widget(locale : String, video : Video, video_assets : VideoAssets) : String
|
||||
if CONFIG.disabled?("downloads")
|
||||
return "<p id=\"download\">#{I18n.translate(locale, "Download is disabled")}</p>"
|
||||
return "<p id=\"download\">#{translate(locale, "Download is disabled")}</p>"
|
||||
end
|
||||
|
||||
if CONFIG.dmca_content.includes?(video.id)
|
||||
return "<p id=\"download\">#{I18n.translate(locale, "dmca_content")}</p>"
|
||||
return "<p id=\"download\">#{translate(locale, "dmca_content")}</p>"
|
||||
end
|
||||
|
||||
url = "/download"
|
||||
@@ -49,7 +49,7 @@ module Invidious::Frontend::WatchPage
|
||||
str << "\t<div class=\"pure-control-group\">\n"
|
||||
|
||||
str << "\t\t<label for='download_widget'>"
|
||||
str << I18n.translate(locale, "Download as: ")
|
||||
str << translate(locale, "Download as: ")
|
||||
str << "</label>\n"
|
||||
|
||||
str << "\t\t<select name='download_widget' id='download_widget'>\n"
|
||||
@@ -98,7 +98,7 @@ module Invidious::Frontend::WatchPage
|
||||
value = {"label": caption.name, "ext": "#{caption.language_code}.vtt"}.to_json
|
||||
|
||||
str << "\t\t\t<option value='" << value << "'>"
|
||||
str << I18n.translate(locale, "download_subtitles", I18n.translate(locale, caption.name))
|
||||
str << translate(locale, "download_subtitles", translate(locale, caption.name))
|
||||
str << "</option>\n"
|
||||
end
|
||||
|
||||
@@ -108,7 +108,7 @@ module Invidious::Frontend::WatchPage
|
||||
str << "\t</div>\n"
|
||||
|
||||
str << "\t<button type=\"submit\" class=\"pure-button pure-button-primary\">\n"
|
||||
str << "\t\t<b>" << I18n.translate(locale, "Download") << "</b>\n"
|
||||
str << "\t\t<b>" << translate(locale, "Download") << "</b>\n"
|
||||
str << "\t</button>\n"
|
||||
|
||||
str << "</form>\n"
|
||||
|
||||
@@ -63,19 +63,19 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
|
||||
|
||||
error_message = <<-END_HTML
|
||||
<div class="error_message">
|
||||
<h2>#{I18n.translate(locale, "crash_page_you_found_a_bug")}</h2>
|
||||
<h2>#{translate(locale, "crash_page_you_found_a_bug")}</h2>
|
||||
<br/><br/>
|
||||
|
||||
<p><b>#{I18n.translate(locale, "crash_page_before_reporting")}</b></p>
|
||||
<p><b>#{translate(locale, "crash_page_before_reporting")}</b></p>
|
||||
<ul>
|
||||
<li>#{I18n.translate(locale, "crash_page_refresh", env.request.resource)}</li>
|
||||
<li>#{I18n.translate(locale, "crash_page_switch_instance", url_switch)}</li>
|
||||
<li>#{I18n.translate(locale, "crash_page_read_the_faq", url_faq)}</li>
|
||||
<li>#{I18n.translate(locale, "crash_page_search_issue", url_search_issues)}</li>
|
||||
<li>#{translate(locale, "crash_page_refresh", env.request.resource)}</li>
|
||||
<li>#{translate(locale, "crash_page_switch_instance", url_switch)}</li>
|
||||
<li>#{translate(locale, "crash_page_read_the_faq", url_faq)}</li>
|
||||
<li>#{translate(locale, "crash_page_search_issue", url_search_issues)}</li>
|
||||
</ul>
|
||||
|
||||
<br/>
|
||||
<p>#{I18n.translate(locale, "crash_page_report_issue", url_new_issue)}</p>
|
||||
<p>#{translate(locale, "crash_page_report_issue", url_new_issue)}</p>
|
||||
|
||||
<!-- TODO: Add a "copy to clipboard" button -->
|
||||
<pre class="error-issue-template">#{issue_template}</pre>
|
||||
@@ -95,7 +95,7 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, mess
|
||||
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
|
||||
error_message = I18n.translate(locale, message)
|
||||
error_message = translate(locale, message)
|
||||
next_steps = error_redirect_helper(env)
|
||||
|
||||
return templated "error"
|
||||
@@ -186,10 +186,10 @@ def error_redirect_helper(env : HTTP::Server::Context)
|
||||
|
||||
if request_path.starts_with?("/search") || request_path.starts_with?("/watch") ||
|
||||
request_path.starts_with?("/channel") || request_path.starts_with?("/playlist?list=PL")
|
||||
next_steps_text = I18n.translate(locale, "next_steps_error_message")
|
||||
refresh = I18n.translate(locale, "next_steps_error_message_refresh")
|
||||
go_to_youtube = I18n.translate(locale, "next_steps_error_message_go_to_youtube")
|
||||
switch_instance = I18n.translate(locale, "Switch Invidious Instance")
|
||||
next_steps_text = translate(locale, "next_steps_error_message")
|
||||
refresh = translate(locale, "next_steps_error_message_refresh")
|
||||
go_to_youtube = translate(locale, "next_steps_error_message_go_to_youtube")
|
||||
switch_instance = translate(locale, "Switch Invidious Instance")
|
||||
|
||||
return <<-END_HTML
|
||||
<p style="margin-bottom: 4px;">#{next_steps_text}</p>
|
||||
|
||||
+132
-136
@@ -1,5 +1,7 @@
|
||||
require "./macros"
|
||||
|
||||
TEST_IDS = {"AgbeGFYluEA", "BaW_jenozKc", "a9LDPn-MO4I", "ddFvjfvPnqk", "iqKdEhx-dD4"}
|
||||
|
||||
struct Nonce
|
||||
include DB::Serializable
|
||||
|
||||
@@ -22,124 +24,60 @@ struct Annotation
|
||||
property annotations : String
|
||||
end
|
||||
|
||||
module Helpers
|
||||
extend self
|
||||
def html_to_content(description_html : String)
|
||||
description = description_html.gsub(/(<br>)|(<br\/>)/, {
|
||||
"<br>": "\n",
|
||||
"<br/>": "\n",
|
||||
})
|
||||
|
||||
private TEST_IDS = {"AgbeGFYluEA", "BaW_jenozKc", "a9LDPn-MO4I", "ddFvjfvPnqk", "iqKdEhx-dD4"}
|
||||
|
||||
def html_to_content(description_html : String)
|
||||
description = description_html.gsub(/(<br>)|(<br\/>)/, {
|
||||
"<br>": "\n",
|
||||
"<br/>": "\n",
|
||||
})
|
||||
|
||||
if !description.empty?
|
||||
description = XML.parse_html(description).content.strip("\n ")
|
||||
end
|
||||
|
||||
return description
|
||||
if !description.empty?
|
||||
description = XML.parse_html(description).content.strip("\n ")
|
||||
end
|
||||
|
||||
def cache_annotation(id, annotations)
|
||||
if !CONFIG.cache_annotations
|
||||
return
|
||||
end
|
||||
return description
|
||||
end
|
||||
|
||||
body = XML.parse(annotations)
|
||||
nodeset = body.xpath_nodes(%q(/document/annotations/annotation))
|
||||
|
||||
return if nodeset == 0
|
||||
|
||||
has_legacy_annotations = false
|
||||
nodeset.each do |node|
|
||||
if !{"branding", "card", "drawer"}.includes? node["type"]?
|
||||
has_legacy_annotations = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
Invidious::Database::Annotations.insert(id, annotations) if has_legacy_annotations
|
||||
def cache_annotation(id, annotations)
|
||||
if !CONFIG.cache_annotations
|
||||
return
|
||||
end
|
||||
|
||||
def create_notification_stream(env, topics, connection_channel)
|
||||
connection = Channel(PQ::Notification).new(8)
|
||||
connection_channel.send({true, connection})
|
||||
body = XML.parse(annotations)
|
||||
nodeset = body.xpath_nodes(%q(/document/annotations/annotation))
|
||||
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
return if nodeset == 0
|
||||
|
||||
since = env.params.query["since"]?.try &.to_i?
|
||||
id = 0
|
||||
|
||||
if topics.includes? "debug"
|
||||
spawn do
|
||||
begin
|
||||
loop do
|
||||
time_span = [0, 0, 0, 0]
|
||||
time_span[rand(4)] = rand(30) + 5
|
||||
published = Time.utc - Time::Span.new(days: time_span[0], hours: time_span[1], minutes: time_span[2], seconds: time_span[3])
|
||||
video_id = TEST_IDS[rand(TEST_IDS.size)]
|
||||
|
||||
video = get_video(video_id)
|
||||
video.published = published
|
||||
response = JSON.parse(video.to_json(locale, nil))
|
||||
|
||||
env.response.puts "id: #{id}"
|
||||
env.response.puts "data: #{response.to_json}"
|
||||
env.response.puts
|
||||
env.response.flush
|
||||
|
||||
id += 1
|
||||
|
||||
sleep 1.minute
|
||||
Fiber.yield
|
||||
end
|
||||
rescue ex
|
||||
end
|
||||
end
|
||||
has_legacy_annotations = false
|
||||
nodeset.each do |node|
|
||||
if !{"branding", "card", "drawer"}.includes? node["type"]?
|
||||
has_legacy_annotations = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
spawn do
|
||||
begin
|
||||
if since
|
||||
since_unix = Time.unix(since.not_nil!)
|
||||
Invidious::Database::Annotations.insert(id, annotations) if has_legacy_annotations
|
||||
end
|
||||
|
||||
topics.try &.each do |topic|
|
||||
case topic
|
||||
when .match(/UC[A-Za-z0-9_-]{22}/)
|
||||
Invidious::Database::ChannelVideos.select_notfications(topic, since_unix).each do |video|
|
||||
response = JSON.parse(video.to_json(locale))
|
||||
def create_notification_stream(env, topics, connection_channel)
|
||||
connection = Channel(PQ::Notification).new(8)
|
||||
connection_channel.send({true, connection})
|
||||
|
||||
env.response.puts "id: #{id}"
|
||||
env.response.puts "data: #{response.to_json}"
|
||||
env.response.puts
|
||||
env.response.flush
|
||||
locale = env.get("preferences").as(Preferences).locale
|
||||
|
||||
id += 1
|
||||
end
|
||||
else
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
since = env.params.query["since"]?.try &.to_i?
|
||||
id = 0
|
||||
|
||||
if topics.includes? "debug"
|
||||
spawn do
|
||||
begin
|
||||
loop do
|
||||
event = connection.receive
|
||||
|
||||
notification = JSON.parse(event.payload)
|
||||
topic = notification["topic"].as_s
|
||||
video_id = notification["videoId"].as_s
|
||||
published = notification["published"].as_i64
|
||||
|
||||
if !topics.try &.includes? topic
|
||||
next
|
||||
end
|
||||
time_span = [0, 0, 0, 0]
|
||||
time_span[rand(4)] = rand(30) + 5
|
||||
published = Time.utc - Time::Span.new(days: time_span[0], hours: time_span[1], minutes: time_span[2], seconds: time_span[3])
|
||||
video_id = TEST_IDS[rand(TEST_IDS.size)]
|
||||
|
||||
video = get_video(video_id)
|
||||
video.published = Time.unix(published)
|
||||
video.published = published
|
||||
response = JSON.parse(video.to_json(locale, nil))
|
||||
|
||||
env.response.puts "id: #{id}"
|
||||
@@ -148,20 +86,65 @@ module Helpers
|
||||
env.response.flush
|
||||
|
||||
id += 1
|
||||
|
||||
sleep 1.minute
|
||||
Fiber.yield
|
||||
end
|
||||
rescue ex
|
||||
ensure
|
||||
connection_channel.send({false, connection})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
spawn do
|
||||
begin
|
||||
if since
|
||||
since_unix = Time.unix(since.not_nil!)
|
||||
|
||||
topics.try &.each do |topic|
|
||||
case topic
|
||||
when .match(/UC[A-Za-z0-9_-]{22}/)
|
||||
Invidious::Database::ChannelVideos.select_notfications(topic, since_unix).each do |video|
|
||||
response = JSON.parse(video.to_json(locale))
|
||||
|
||||
env.response.puts "id: #{id}"
|
||||
env.response.puts "data: #{response.to_json}"
|
||||
env.response.puts
|
||||
env.response.flush
|
||||
|
||||
id += 1
|
||||
end
|
||||
else
|
||||
# TODO
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
spawn do
|
||||
begin
|
||||
# Send heartbeat
|
||||
loop do
|
||||
env.response.puts ":keepalive #{Time.utc.to_unix}"
|
||||
event = connection.receive
|
||||
|
||||
notification = JSON.parse(event.payload)
|
||||
topic = notification["topic"].as_s
|
||||
video_id = notification["videoId"].as_s
|
||||
published = notification["published"].as_i64
|
||||
|
||||
if !topics.try &.includes? topic
|
||||
next
|
||||
end
|
||||
|
||||
video = get_video(video_id)
|
||||
video.published = Time.unix(published)
|
||||
response = JSON.parse(video.to_json(locale, nil))
|
||||
|
||||
env.response.puts "id: #{id}"
|
||||
env.response.puts "data: #{response.to_json}"
|
||||
env.response.puts
|
||||
env.response.flush
|
||||
sleep (20 + rand(11)).seconds
|
||||
|
||||
id += 1
|
||||
end
|
||||
rescue ex
|
||||
ensure
|
||||
@@ -169,38 +152,51 @@ module Helpers
|
||||
end
|
||||
end
|
||||
|
||||
def extract_initial_data(body) : Hash(String, JSON::Any)
|
||||
return JSON.parse(body.match(/(window\["ytInitialData"\]|var\s*ytInitialData)\s*=\s*(?<info>{.*?});<\/script>/mx).try &.["info"] || "{}").as_h
|
||||
end
|
||||
|
||||
def proxy_file(response, env)
|
||||
if response.headers.includes_word?("Content-Encoding", "gzip")
|
||||
Compress::Gzip::Writer.open(env.response) do |deflate|
|
||||
IO.copy response.body_io, deflate
|
||||
end
|
||||
elsif response.headers.includes_word?("Content-Encoding", "deflate")
|
||||
Compress::Deflate::Writer.open(env.response) do |deflate|
|
||||
IO.copy response.body_io, deflate
|
||||
end
|
||||
else
|
||||
IO.copy response.body_io, env.response
|
||||
begin
|
||||
# Send heartbeat
|
||||
loop do
|
||||
env.response.puts ":keepalive #{Time.utc.to_unix}"
|
||||
env.response.puts
|
||||
env.response.flush
|
||||
sleep (20 + rand(11)).seconds
|
||||
end
|
||||
end
|
||||
|
||||
# Fetch the playback requests tracker from the statistics endpoint.
|
||||
#
|
||||
# Creates a new tracker when unavailable.
|
||||
def get_playback_statistic
|
||||
if (tracker = Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"]) && tracker.as(Hash).empty?
|
||||
tracker = {
|
||||
"totalRequests" => 0_i64,
|
||||
"successfulRequests" => 0_i64,
|
||||
"ratio" => 0_f64,
|
||||
}
|
||||
|
||||
Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"] = tracker
|
||||
end
|
||||
|
||||
return tracker.as(Hash(String, Int64 | Float64))
|
||||
rescue ex
|
||||
ensure
|
||||
connection_channel.send({false, connection})
|
||||
end
|
||||
end
|
||||
|
||||
def extract_initial_data(body) : Hash(String, JSON::Any)
|
||||
return JSON.parse(body.match(/(window\["ytInitialData"\]|var\s*ytInitialData)\s*=\s*(?<info>{.*?});<\/script>/mx).try &.["info"] || "{}").as_h
|
||||
end
|
||||
|
||||
def proxy_file(response, env)
|
||||
if response.headers.includes_word?("Content-Encoding", "gzip")
|
||||
Compress::Gzip::Writer.open(env.response) do |deflate|
|
||||
IO.copy response.body_io, deflate
|
||||
end
|
||||
elsif response.headers.includes_word?("Content-Encoding", "deflate")
|
||||
Compress::Deflate::Writer.open(env.response) do |deflate|
|
||||
IO.copy response.body_io, deflate
|
||||
end
|
||||
else
|
||||
IO.copy response.body_io, env.response
|
||||
end
|
||||
end
|
||||
|
||||
# Fetch the playback requests tracker from the statistics endpoint.
|
||||
#
|
||||
# Creates a new tracker when unavailable.
|
||||
def get_playback_statistic
|
||||
if (tracker = Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"]) && tracker.as(Hash).empty?
|
||||
tracker = {
|
||||
"totalRequests" => 0_i64,
|
||||
"successfulRequests" => 0_i64,
|
||||
"ratio" => 0_f64,
|
||||
}
|
||||
|
||||
Invidious::Jobs::StatisticsRefreshJob::STATISTICS["playback"] = tracker
|
||||
end
|
||||
|
||||
return tracker.as(Hash(String, Int64 | Float64))
|
||||
end
|
||||
|
||||
+178
-182
@@ -1,203 +1,199 @@
|
||||
module I18n
|
||||
extend self
|
||||
# Languages requiring a better level of translation (at least 20%)
|
||||
# to be added to the list below:
|
||||
#
|
||||
# "af" => "", # Afrikaans
|
||||
# "az" => "", # Azerbaijani
|
||||
# "be" => "", # Belarusian
|
||||
# "bn_BD" => "", # Bengali (Bangladesh)
|
||||
# "ia" => "", # Interlingua
|
||||
# "or" => "", # Odia
|
||||
# "tk" => "", # Turkmen
|
||||
# "tok => "", # Toki Pona
|
||||
#
|
||||
LOCALES_LIST = {
|
||||
"ar" => "العربية", # Arabic
|
||||
"bg" => "български", # Bulgarian
|
||||
"bn" => "বাংলা", # Bengali
|
||||
"ca" => "Català", # Catalan
|
||||
"cs" => "Čeština", # Czech
|
||||
"cy" => "Cymraeg", # Welsh
|
||||
"da" => "Dansk", # Danish
|
||||
"de" => "Deutsch", # German
|
||||
"el" => "Ελληνικά", # Greek
|
||||
"en-US" => "English", # English
|
||||
"eo" => "Esperanto", # Esperanto
|
||||
"es" => "Español", # Spanish
|
||||
"et" => "Eesti keel", # Estonian
|
||||
"eu" => "Euskara", # Basque
|
||||
"fa" => "فارسی", # Persian
|
||||
"fi" => "Suomi", # Finnish
|
||||
"fr" => "Français", # French
|
||||
"he" => "עברית", # Hebrew
|
||||
"hi" => "हिन्दी", # Hindi
|
||||
"hr" => "Hrvatski", # Croatian
|
||||
"hu-HU" => "Magyar Nyelv", # Hungarian
|
||||
"id" => "Bahasa Indonesia", # Indonesian
|
||||
"is" => "Íslenska", # Icelandic
|
||||
"it" => "Italiano", # Italian
|
||||
"ja" => "日本語", # Japanese
|
||||
"ko" => "한국어", # Korean
|
||||
"lmo" => "Lombard", # Lombard
|
||||
"lt" => "Lietuvių", # Lithuanian
|
||||
"nb-NO" => "Norsk bokmål", # Norwegian Bokmål
|
||||
"nl" => "Nederlands", # Dutch
|
||||
"pl" => "Polski", # Polish
|
||||
"pt" => "Português", # Portuguese
|
||||
"pt-BR" => "Português Brasileiro", # Portuguese (Brazil)
|
||||
"pt-PT" => "Português de Portugal", # Portuguese (Portugal)
|
||||
"ro" => "Română", # Romanian
|
||||
"ru" => "Русский", # Russian
|
||||
"si" => "සිංහල", # Sinhala
|
||||
"sk" => "Slovenčina", # Slovak
|
||||
"sl" => "Slovenščina", # Slovenian
|
||||
"sq" => "Shqip", # Albanian
|
||||
"sr" => "Srpski (latinica)", # Serbian (Latin)
|
||||
"sr_Cyrl" => "Српски (ћирилица)", # Serbian (Cyrillic)
|
||||
"sv-SE" => "Svenska", # Swedish
|
||||
"ta" => "தமிழ்", # Tamil
|
||||
"tr" => "Türkçe", # Turkish
|
||||
"uk" => "Українська", # Ukrainian
|
||||
"vi" => "Tiếng Việt", # Vietnamese
|
||||
"zh-CN" => "汉语", # Chinese (Simplified)
|
||||
"zh-TW" => "漢語", # Chinese (Traditional)
|
||||
}
|
||||
|
||||
# Languages requiring a better level of translation (at least 20%)
|
||||
# to be added to the list below:
|
||||
#
|
||||
# "af" => "", # Afrikaans
|
||||
# "az" => "", # Azerbaijani
|
||||
# "be" => "", # Belarusian
|
||||
# "bn_BD" => "", # Bengali (Bangladesh)
|
||||
# "ia" => "", # Interlingua
|
||||
# "or" => "", # Odia
|
||||
# "tk" => "", # Turkmen
|
||||
# "tok => "", # Toki Pona
|
||||
#
|
||||
LOCALES_LIST = {
|
||||
"ar" => "العربية", # Arabic
|
||||
"bg" => "български", # Bulgarian
|
||||
"bn" => "বাংলা", # Bengali
|
||||
"ca" => "Català", # Catalan
|
||||
"cs" => "Čeština", # Czech
|
||||
"cy" => "Cymraeg", # Welsh
|
||||
"da" => "Dansk", # Danish
|
||||
"de" => "Deutsch", # German
|
||||
"el" => "Ελληνικά", # Greek
|
||||
"en-US" => "English", # English
|
||||
"eo" => "Esperanto", # Esperanto
|
||||
"es" => "Español", # Spanish
|
||||
"et" => "Eesti keel", # Estonian
|
||||
"eu" => "Euskara", # Basque
|
||||
"fa" => "فارسی", # Persian
|
||||
"fi" => "Suomi", # Finnish
|
||||
"fr" => "Français", # French
|
||||
"he" => "עברית", # Hebrew
|
||||
"hi" => "हिन्दी", # Hindi
|
||||
"hr" => "Hrvatski", # Croatian
|
||||
"hu-HU" => "Magyar Nyelv", # Hungarian
|
||||
"id" => "Bahasa Indonesia", # Indonesian
|
||||
"is" => "Íslenska", # Icelandic
|
||||
"it" => "Italiano", # Italian
|
||||
"ja" => "日本語", # Japanese
|
||||
"ko" => "한국어", # Korean
|
||||
"lmo" => "Lombard", # Lombard
|
||||
"lt" => "Lietuvių", # Lithuanian
|
||||
"nb-NO" => "Norsk bokmål", # Norwegian Bokmål
|
||||
"nl" => "Nederlands", # Dutch
|
||||
"pl" => "Polski", # Polish
|
||||
"pt" => "Português", # Portuguese
|
||||
"pt-BR" => "Português Brasileiro", # Portuguese (Brazil)
|
||||
"pt-PT" => "Português de Portugal", # Portuguese (Portugal)
|
||||
"ro" => "Română", # Romanian
|
||||
"ru" => "Русский", # Russian
|
||||
"si" => "සිංහල", # Sinhala
|
||||
"sk" => "Slovenčina", # Slovak
|
||||
"sl" => "Slovenščina", # Slovenian
|
||||
"sq" => "Shqip", # Albanian
|
||||
"sr" => "Srpski (latinica)", # Serbian (Latin)
|
||||
"sr_Cyrl" => "Српски (ћирилица)", # Serbian (Cyrillic)
|
||||
"sv-SE" => "Svenska", # Swedish
|
||||
"ta" => "தமிழ்", # Tamil
|
||||
"tr" => "Türkçe", # Turkish
|
||||
"uk" => "Українська", # Ukrainian
|
||||
"vi" => "Tiếng Việt", # Vietnamese
|
||||
"zh-CN" => "汉语", # Chinese (Simplified)
|
||||
"zh-TW" => "漢語", # Chinese (Traditional)
|
||||
}
|
||||
LOCALES = load_all_locales()
|
||||
|
||||
LOCALES = load_all_locales()
|
||||
CONTENT_REGIONS = {
|
||||
"AE", "AR", "AT", "AU", "AZ", "BA", "BD", "BE", "BG", "BH", "BO", "BR", "BY",
|
||||
"CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE",
|
||||
"EG", "ES", "FI", "FR", "GB", "GE", "GH", "GR", "GT", "HK", "HN", "HR", "HU",
|
||||
"ID", "IE", "IL", "IN", "IQ", "IS", "IT", "JM", "JO", "JP", "KE", "KR", "KW",
|
||||
"KZ", "LB", "LI", "LK", "LT", "LU", "LV", "LY", "MA", "ME", "MK", "MT", "MX",
|
||||
"MY", "NG", "NI", "NL", "NO", "NP", "NZ", "OM", "PA", "PE", "PG", "PH", "PK",
|
||||
"PL", "PR", "PT", "PY", "QA", "RO", "RS", "RU", "SA", "SE", "SG", "SI", "SK",
|
||||
"SN", "SV", "TH", "TN", "TR", "TW", "TZ", "UA", "UG", "US", "UY", "VE", "VN",
|
||||
"YE", "ZA", "ZW",
|
||||
}
|
||||
|
||||
CONTENT_REGIONS = {
|
||||
"AE", "AR", "AT", "AU", "AZ", "BA", "BD", "BE", "BG", "BH", "BO", "BR", "BY",
|
||||
"CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE",
|
||||
"EG", "ES", "FI", "FR", "GB", "GE", "GH", "GR", "GT", "HK", "HN", "HR", "HU",
|
||||
"ID", "IE", "IL", "IN", "IQ", "IS", "IT", "JM", "JO", "JP", "KE", "KR", "KW",
|
||||
"KZ", "LB", "LI", "LK", "LT", "LU", "LV", "LY", "MA", "ME", "MK", "MT", "MX",
|
||||
"MY", "NG", "NI", "NL", "NO", "NP", "NZ", "OM", "PA", "PE", "PG", "PH", "PK",
|
||||
"PL", "PR", "PT", "PY", "QA", "RO", "RS", "RU", "SA", "SE", "SG", "SI", "SK",
|
||||
"SN", "SV", "TH", "TN", "TR", "TW", "TZ", "UA", "UG", "US", "UY", "VE", "VN",
|
||||
"YE", "ZA", "ZW",
|
||||
}
|
||||
# Enum for the different types of number formats
|
||||
enum NumberFormatting
|
||||
None # Print the number as-is
|
||||
Separator # Use a separator for thousands
|
||||
Short # Use short notation (k/M/B)
|
||||
HtmlSpan # Surround with <span id="count"></span>
|
||||
end
|
||||
|
||||
# Enum for the different types of number formats
|
||||
enum NumberFormatting
|
||||
None # Print the number as-is
|
||||
Separator # Use a separator for thousands
|
||||
Short # Use short notation (k/M/B)
|
||||
HtmlSpan # Surround with <span id="count"></span>
|
||||
def load_all_locales
|
||||
locales = {} of String => Hash(String, JSON::Any)
|
||||
|
||||
LOCALES_LIST.each_key do |name|
|
||||
locales[name] = JSON.parse(File.read("locales/#{name}.json")).as_h
|
||||
end
|
||||
|
||||
def load_all_locales
|
||||
locales = {} of String => Hash(String, JSON::Any)
|
||||
return locales
|
||||
end
|
||||
|
||||
LOCALES_LIST.each_key do |name|
|
||||
locales[name] = JSON.parse(File.read("locales/#{name}.json")).as_h
|
||||
end
|
||||
|
||||
return locales
|
||||
def translate(locale : String?, key : String, text : String | Hash(String, String) | Nil = nil) : String
|
||||
# Log a warning if "key" doesn't exist in en-US locale and return
|
||||
# that key as the text, so this is more or less transparent to the user.
|
||||
if !LOCALES["en-US"].has_key?(key)
|
||||
LOGGER.warn("i18n: Missing translation key \"#{key}\"")
|
||||
return key
|
||||
end
|
||||
|
||||
def translate(locale : String?, key : String, text : String | Hash(String, String) | Nil = nil) : String
|
||||
# Log a warning if "key" doesn't exist in en-US locale and return
|
||||
# that key as the text, so this is more or less transparent to the user.
|
||||
if !LOCALES["en-US"].has_key?(key)
|
||||
LOGGER.warn("i18n: Missing translation key \"#{key}\"")
|
||||
return key
|
||||
end
|
||||
# Default to english, whenever the locale doesn't exist,
|
||||
# or the key requested has not been translated
|
||||
if locale && LOCALES.has_key?(locale) && LOCALES[locale].has_key?(key)
|
||||
raw_data = LOCALES[locale][key]
|
||||
else
|
||||
raw_data = LOCALES["en-US"][key]
|
||||
end
|
||||
|
||||
# Default to english, whenever the locale doesn't exist,
|
||||
# or the key requested has not been translated
|
||||
if locale && LOCALES.has_key?(locale) && LOCALES[locale].has_key?(key)
|
||||
raw_data = LOCALES[locale][key]
|
||||
else
|
||||
raw_data = LOCALES["en-US"][key]
|
||||
end
|
||||
case raw_data
|
||||
when .as_h?
|
||||
# Init
|
||||
translation = ""
|
||||
match_length = 0
|
||||
|
||||
case raw_data
|
||||
when .as_h?
|
||||
# Init
|
||||
translation = ""
|
||||
match_length = 0
|
||||
|
||||
raw_data.as_h.each do |hash_key, value|
|
||||
if text.is_a?(String)
|
||||
if md = text.try &.match(/#{hash_key}/)
|
||||
if md[0].size >= match_length
|
||||
translation = value.as_s
|
||||
match_length = md[0].size
|
||||
end
|
||||
raw_data.as_h.each do |hash_key, value|
|
||||
if text.is_a?(String)
|
||||
if md = text.try &.match(/#{hash_key}/)
|
||||
if md[0].size >= match_length
|
||||
translation = value.as_s
|
||||
match_length = md[0].size
|
||||
end
|
||||
end
|
||||
end
|
||||
when .as_s?
|
||||
translation = raw_data.as_s
|
||||
end
|
||||
when .as_s?
|
||||
translation = raw_data.as_s
|
||||
else
|
||||
raise "Invalid translation \"#{raw_data}\""
|
||||
end
|
||||
|
||||
if text.is_a?(String)
|
||||
translation = translation.gsub("`x`", text)
|
||||
elsif text.is_a?(Hash(String, String))
|
||||
# adds support for multi string interpolation. Based on i18next https://www.i18next.com/translation-function/interpolation#basic
|
||||
text.each_key do |hash_key|
|
||||
translation = translation.gsub("{{#{hash_key}}}", text[hash_key])
|
||||
end
|
||||
end
|
||||
|
||||
return translation
|
||||
end
|
||||
|
||||
def translate_count(locale : String, key : String, count : Int, format = NumberFormatting::None) : String
|
||||
# Fallback on english if locale doesn't exist
|
||||
locale = "en-US" if !LOCALES.has_key?(locale)
|
||||
|
||||
# Retrieve suffix
|
||||
suffix = I18next::Plurals::RESOLVER.get_suffix(locale, count)
|
||||
plural_key = key + suffix
|
||||
|
||||
if LOCALES[locale].has_key?(plural_key)
|
||||
translation = LOCALES[locale][plural_key].as_s
|
||||
else
|
||||
# Try #1: Fallback to singular in the same locale
|
||||
singular_suffix = I18next::Plurals::RESOLVER.get_suffix(locale, 1)
|
||||
|
||||
if LOCALES[locale].has_key?(key + singular_suffix)
|
||||
translation = LOCALES[locale][key + singular_suffix].as_s
|
||||
elsif locale != "en-US"
|
||||
# Try #2: Fallback to english
|
||||
translation = translate_count("en-US", key, count)
|
||||
else
|
||||
raise "Invalid translation \"#{raw_data}\""
|
||||
end
|
||||
|
||||
if text.is_a?(String)
|
||||
translation = translation.gsub("`x`", text)
|
||||
elsif text.is_a?(Hash(String, String))
|
||||
# adds support for multi string interpolation. Based on i18next https://www.i18next.com/translation-function/interpolation#basic
|
||||
text.each_key do |hash_key|
|
||||
translation = translation.gsub("{{#{hash_key}}}", text[hash_key])
|
||||
end
|
||||
end
|
||||
|
||||
return translation
|
||||
end
|
||||
|
||||
def translate_count(locale : String, key : String, count : Int, format = NumberFormatting::None) : String
|
||||
# Fallback on english if locale doesn't exist
|
||||
locale = "en-US" if !LOCALES.has_key?(locale)
|
||||
|
||||
# Retrieve suffix
|
||||
suffix = I18next::Plurals::RESOLVER.get_suffix(locale, count)
|
||||
plural_key = key + suffix
|
||||
|
||||
if LOCALES[locale].has_key?(plural_key)
|
||||
translation = LOCALES[locale][plural_key].as_s
|
||||
else
|
||||
# Try #1: Fallback to singular in the same locale
|
||||
singular_suffix = I18next::Plurals::RESOLVER.get_suffix(locale, 1)
|
||||
|
||||
if LOCALES[locale].has_key?(key + singular_suffix)
|
||||
translation = LOCALES[locale][key + singular_suffix].as_s
|
||||
elsif locale != "en-US"
|
||||
# Try #2: Fallback to english
|
||||
translation = self.translate_count("en-US", key, count)
|
||||
else
|
||||
# Return key if we're already in english, as the translation is missing
|
||||
LOGGER.warn("i18n: Missing translation key \"#{key}\"")
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
case format
|
||||
when .separator? then count_txt = number_with_separator(count)
|
||||
when .short? then count_txt = number_to_short_text(count)
|
||||
when .html_span? then count_txt = "<span id=\"count\">" + count.to_s + "</span>"
|
||||
else count_txt = count.to_s
|
||||
end
|
||||
|
||||
return translation.gsub("{{count}}", count_txt)
|
||||
end
|
||||
|
||||
def translate_bool(locale : String?, translation : Bool)
|
||||
case translation
|
||||
when true
|
||||
return self.translate(locale, "Yes")
|
||||
when false
|
||||
return self.translate(locale, "No")
|
||||
# Return key if we're already in english, as the translation is missing
|
||||
LOGGER.warn("i18n: Missing translation key \"#{key}\"")
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
def locale_is_rtl?(locale : String?)
|
||||
# Fallback to en-US
|
||||
return false if locale.nil?
|
||||
case format
|
||||
when .separator? then count_txt = number_with_separator(count)
|
||||
when .short? then count_txt = number_to_short_text(count)
|
||||
when .html_span? then count_txt = "<span id=\"count\">" + count.to_s + "</span>"
|
||||
else count_txt = count.to_s
|
||||
end
|
||||
|
||||
# Arabic, Persian, Hebrew
|
||||
# See https://en.wikipedia.org/wiki/Right-to-left_script#List_of_RTL_scripts
|
||||
return {"ar", "fa", "he"}.includes? locale
|
||||
return translation.gsub("{{count}}", count_txt)
|
||||
end
|
||||
|
||||
def translate_bool(locale : String?, translation : Bool)
|
||||
case translation
|
||||
when true
|
||||
return translate(locale, "Yes")
|
||||
when false
|
||||
return translate(locale, "No")
|
||||
end
|
||||
end
|
||||
|
||||
def locale_is_rtl?(locale : String?)
|
||||
# Fallback to en-US
|
||||
return false if locale.nil?
|
||||
|
||||
# Arabic, Persian, Hebrew
|
||||
# See https://en.wikipedia.org/wiki/Right-to-left_script#List_of_RTL_scripts
|
||||
return {"ar", "fa", "he"}.includes? locale
|
||||
end
|
||||
|
||||
@@ -53,7 +53,7 @@ struct SearchVideo
|
||||
xml.element("img", src: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg")
|
||||
end
|
||||
|
||||
xml.element("p", style: "word-break:break-word;white-space:pre-wrap") { xml.text Helpers.html_to_content(self.description_html) }
|
||||
xml.element("p", style: "word-break:break-word;white-space:pre-wrap") { xml.text html_to_content(self.description_html) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,7 +63,7 @@ struct SearchVideo
|
||||
xml.element("media:title") { xml.text self.title }
|
||||
xml.element("media:thumbnail", url: "#{HOST_URL}/vi/#{self.id}/mqdefault.jpg",
|
||||
width: "320", height: "180")
|
||||
xml.element("media:description") { xml.text Helpers.html_to_content(self.description_html) }
|
||||
xml.element("media:description") { xml.text html_to_content(self.description_html) }
|
||||
end
|
||||
|
||||
xml.element("media:community") do
|
||||
@@ -111,13 +111,13 @@ struct SearchVideo
|
||||
Invidious::JSONify::APIv1.thumbnails(json, self.id)
|
||||
end
|
||||
|
||||
json.field "description", Helpers.html_to_content(self.description_html)
|
||||
json.field "description", html_to_content(self.description_html)
|
||||
json.field "descriptionHtml", self.description_html
|
||||
|
||||
json.field "viewCount", self.views
|
||||
json.field "viewCountText", I18n.translate_count(locale, "generic_views_count", self.views, I18n::NumberFormatting::Short)
|
||||
json.field "viewCountText", translate_count(locale, "generic_views_count", self.views, NumberFormatting::Short)
|
||||
json.field "published", self.published.to_unix
|
||||
json.field "publishedText", I18n.translate(locale, "`x` ago", recode_date(self.published, locale))
|
||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale))
|
||||
json.field "lengthSeconds", self.length_seconds
|
||||
json.field "liveNow", self.badges.live_now?
|
||||
json.field "premium", self.badges.premium?
|
||||
@@ -255,7 +255,7 @@ struct SearchChannel
|
||||
json.field "videoCount", self.video_count
|
||||
json.field "channelHandle", self.channel_handle
|
||||
|
||||
json.field "description", Helpers.html_to_content(self.description_html)
|
||||
json.field "description", html_to_content(self.description_html)
|
||||
json.field "descriptionHtml", self.description_html
|
||||
end
|
||||
end
|
||||
@@ -327,8 +327,8 @@ struct ProblematicTimelineItem
|
||||
xml.element("content", type: "xhtml") do
|
||||
xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do
|
||||
xml.element("div") do
|
||||
xml.element("h4") { I18n.translate(locale, "timeline_parse_error_placeholder_heading") }
|
||||
xml.element("p") { I18n.translate(locale, "timeline_parse_error_placeholder_message") }
|
||||
xml.element("h4") { translate(locale, "timeline_parse_error_placeholder_heading") }
|
||||
xml.element("p") { translate(locale, "timeline_parse_error_placeholder_message") }
|
||||
end
|
||||
|
||||
xml.element("pre") do
|
||||
|
||||
@@ -146,19 +146,19 @@ def recode_date(time : Time, locale)
|
||||
span = Time.utc - time
|
||||
|
||||
if span.total_days > 365.0
|
||||
return I18n.translate_count(locale, "generic_count_years", span.total_days.to_i // 365)
|
||||
return translate_count(locale, "generic_count_years", span.total_days.to_i // 365)
|
||||
elsif span.total_days > 30.0
|
||||
return I18n.translate_count(locale, "generic_count_months", span.total_days.to_i // 30)
|
||||
return translate_count(locale, "generic_count_months", span.total_days.to_i // 30)
|
||||
elsif span.total_days > 7.0
|
||||
return I18n.translate_count(locale, "generic_count_weeks", span.total_days.to_i // 7)
|
||||
return translate_count(locale, "generic_count_weeks", span.total_days.to_i // 7)
|
||||
elsif span.total_hours > 24.0
|
||||
return I18n.translate_count(locale, "generic_count_days", span.total_days.to_i)
|
||||
return translate_count(locale, "generic_count_days", span.total_days.to_i)
|
||||
elsif span.total_minutes > 60.0
|
||||
return I18n.translate_count(locale, "generic_count_hours", span.total_hours.to_i)
|
||||
return translate_count(locale, "generic_count_hours", span.total_hours.to_i)
|
||||
elsif span.total_seconds > 60.0
|
||||
return I18n.translate_count(locale, "generic_count_minutes", span.total_minutes.to_i)
|
||||
return translate_count(locale, "generic_count_minutes", span.total_minutes.to_i)
|
||||
else
|
||||
return I18n.translate_count(locale, "generic_count_seconds", span.total_seconds.to_i)
|
||||
return translate_count(locale, "generic_count_seconds", span.total_seconds.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ module Invidious::JSONify::APIv1
|
||||
json.field "description", video.description
|
||||
json.field "descriptionHtml", video.description_html
|
||||
json.field "published", video.published.to_unix
|
||||
json.field "publishedText", I18n.translate(locale, "`x` ago", recode_date(video.published, locale))
|
||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published, locale))
|
||||
json.field "keywords", video.keywords
|
||||
|
||||
json.field "viewCount", video.views
|
||||
@@ -268,7 +268,7 @@ module Invidious::JSONify::APIv1
|
||||
json.field "viewCountText", rv["short_view_count"]?
|
||||
json.field "published", rv["published"]?
|
||||
if rv["published"]?.try &.presence
|
||||
json.field "publishedText", I18n.translate(locale, "`x` ago", recode_date(Time.parse_rfc3339(rv["published"].to_s), locale))
|
||||
json.field "publishedText", translate(locale, "`x` ago", recode_date(Time.parse_rfc3339(rv["published"].to_s), locale))
|
||||
else
|
||||
json.field "publishedText", ""
|
||||
end
|
||||
|
||||
@@ -27,7 +27,7 @@ def fetch_mix(rdid, video_id, cookies = nil, locale = nil)
|
||||
|
||||
video_id = "CvFH_6DNRCY" if rdid.starts_with? "OLAK5uy_"
|
||||
response = YT_POOL.client &.get("/watch?v=#{video_id}&list=#{rdid}&gl=US&hl=en", headers)
|
||||
initial_data = Helpers.extract_initial_data(response.body)
|
||||
initial_data = extract_initial_data(response.body)
|
||||
|
||||
if !initial_data["contents"]["twoColumnWatchNextResults"]["playlist"]?
|
||||
raise InfoException.new("Could not create mix.")
|
||||
|
||||
@@ -199,7 +199,7 @@ struct InvidiousPlaylist
|
||||
json.field "authorUrl", nil
|
||||
json.field "authorThumbnails", [] of String
|
||||
|
||||
json.field "description", Helpers.html_to_content(self.description_html)
|
||||
json.field "description", html_to_content(self.description_html)
|
||||
json.field "descriptionHtml", self.description_html
|
||||
json.field "videoCount", self.video_count
|
||||
|
||||
@@ -384,7 +384,7 @@ def fetch_playlist(plid : String)
|
||||
video_count = text.gsub(/\D/, "").to_i? || 0
|
||||
elsif text.includes? "view"
|
||||
views = text.gsub(/\D/, "").to_i64? || 0_i64
|
||||
elsif !text.includes? "Pay to watch"
|
||||
else
|
||||
updated = decode_date(text.lchop("Last updated on ").lchop("Updated "))
|
||||
end
|
||||
end
|
||||
@@ -445,7 +445,7 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
|
||||
# 100 videos per request
|
||||
ctoken = produce_playlist_continuation(playlist.id, offset)
|
||||
initial_data = YoutubeAPI.browse(ctoken)
|
||||
videos += extract_playlist_videos(playlist.id, initial_data)
|
||||
videos += extract_playlist_videos(initial_data)
|
||||
|
||||
offset += 100
|
||||
end
|
||||
@@ -454,7 +454,7 @@ def get_playlist_videos(playlist : InvidiousPlaylist | Playlist, offset : Int32,
|
||||
end
|
||||
end
|
||||
|
||||
def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JSON::Any))
|
||||
def extract_playlist_videos(initial_data : Hash(String, JSON::Any))
|
||||
videos = [] of PlaylistVideo | ProblematicTimelineItem
|
||||
|
||||
if initial_data["contents"]?
|
||||
@@ -480,9 +480,9 @@ def extract_playlist_videos(playlist_id : String, initial_data : Hash(String, JS
|
||||
|
||||
contents.try &.each do |item|
|
||||
if i = item["playlistVideoRenderer"]?
|
||||
video_id = i.dig?("navigationEndpoint", "watchEndpoint", "videoId").try &.as_s || i.dig("videoId").as_s
|
||||
plid = i.dig?("navigationEndpoint", "watchEndpoint", "playlistId").try &.as_s || playlist_id
|
||||
index = i.dig?("navigationEndpoint", "watchEndpoint", "index").try &.as_i64 || i.dig("index", "simpleText").as_s.to_i64
|
||||
video_id = i["navigationEndpoint"]["watchEndpoint"]["videoId"].as_s
|
||||
plid = i["navigationEndpoint"]["watchEndpoint"]["playlistId"].as_s
|
||||
index = i["navigationEndpoint"]["watchEndpoint"]["index"].as_i64
|
||||
|
||||
title = i["title"].try { |t| t["simpleText"]? || t["runs"]?.try &.[0]["text"]? }.try &.as_s || ""
|
||||
author = i["shortBylineText"]?.try &.["runs"][0]["text"].as_s || ""
|
||||
|
||||
@@ -8,7 +8,7 @@ module Invidious::Routes::API::V1::Authenticated
|
||||
# topics = env.params.body["topics"]?.try &.split(",").uniq.first(1000)
|
||||
# topics ||= [] of String
|
||||
|
||||
# Helpers.create_notification_stream(env, topics, connection_channel)
|
||||
# create_notification_stream(env, topics, connection_channel)
|
||||
# end
|
||||
|
||||
def self.get_preferences(env)
|
||||
@@ -485,6 +485,6 @@ module Invidious::Routes::API::V1::Authenticated
|
||||
topics = raw_topics.try &.split(",").uniq.first(1000)
|
||||
topics ||= [] of String
|
||||
|
||||
Helpers.create_notification_stream(env, topics, CONNECTION_CHANNEL)
|
||||
create_notification_stream(env, topics, CONNECTION_CHANNEL)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -97,7 +97,7 @@ module Invidious::Routes::API::V1::Channels
|
||||
json.field "autoGenerated", channel.auto_generated
|
||||
json.field "ageGated", channel.is_age_gated
|
||||
json.field "isFamilyFriendly", channel.is_family_friendly
|
||||
json.field "description", Helpers.html_to_content(channel.description_html)
|
||||
json.field "description", html_to_content(channel.description_html)
|
||||
json.field "descriptionHtml", channel.description_html
|
||||
|
||||
json.field "allowedRegions", channel.allowed_regions
|
||||
@@ -128,6 +128,7 @@ module Invidious::Routes::API::V1::Channels
|
||||
end
|
||||
end
|
||||
end # relatedChannels
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -300,7 +300,7 @@ module Invidious::Routes::API::V1::Videos
|
||||
|
||||
annotations = response.body
|
||||
|
||||
Helpers.cache_annotation(id, annotations)
|
||||
cache_annotation(id, annotations)
|
||||
end
|
||||
else # "youtube"
|
||||
response = YT_POOL.client &.get("/annotations_invideo?video_id=#{id}")
|
||||
|
||||
@@ -19,7 +19,7 @@ module Invidious::Routes::BeforeAll
|
||||
preferences = Preferences.from_json(URI.decode_www_form(prefs_cookie.value))
|
||||
else
|
||||
if language_header = env.request.headers["Accept-Language"]?
|
||||
if language = ANG.language_negotiator.best(language_header, I18n::LOCALES.keys)
|
||||
if language = ANG.language_negotiator.best(language_header, LOCALES.keys)
|
||||
preferences.locale = language.header
|
||||
end
|
||||
end
|
||||
|
||||
@@ -354,7 +354,7 @@ module Invidious::Routes::Channels
|
||||
resolved_url = YoutubeAPI.resolve_url("https://youtube.com#{env.request.path}#{yt_url_params.size > 0 ? "?#{yt_url_params}" : ""}")
|
||||
ucid = resolved_url["endpoint"]["browseEndpoint"]["browseId"]
|
||||
rescue ex : InfoException | KeyError
|
||||
return error_template(404, I18n.translate(locale, "This channel does not exist."))
|
||||
return error_template(404, translate(locale, "This channel does not exist."))
|
||||
end
|
||||
|
||||
selected_tab = env.params.url["tab"]?
|
||||
|
||||
@@ -10,7 +10,7 @@ module Invidious::Routes::Embed
|
||||
videos = get_playlist_videos(playlist, offset: offset)
|
||||
if videos.empty?
|
||||
url = "/playlist?list=#{plid}"
|
||||
raise NotFoundException.new(I18n.translate(locale, "error_video_not_in_playlist", url))
|
||||
raise NotFoundException.new(translate(locale, "error_video_not_in_playlist", url))
|
||||
end
|
||||
|
||||
first_playlist_video = videos[0].as(PlaylistVideo)
|
||||
@@ -71,7 +71,7 @@ module Invidious::Routes::Embed
|
||||
videos = get_playlist_videos(playlist, offset: offset)
|
||||
if videos.empty?
|
||||
url = "/playlist?list=#{plid}"
|
||||
raise NotFoundException.new(I18n.translate(locale, "error_video_not_in_playlist", url))
|
||||
raise NotFoundException.new(translate(locale, "error_video_not_in_playlist", url))
|
||||
end
|
||||
|
||||
first_playlist_video = videos[0].as(PlaylistVideo)
|
||||
|
||||
@@ -37,7 +37,7 @@ module Invidious::Routes::Feeds
|
||||
if CONFIG.popular_enabled
|
||||
templated "feeds/popular"
|
||||
else
|
||||
message = I18n.translate(locale, "The Popular feed has been disabled by the administrator.")
|
||||
message = translate(locale, "The Popular feed has been disabled by the administrator.")
|
||||
templated "message"
|
||||
end
|
||||
end
|
||||
@@ -259,7 +259,7 @@ module Invidious::Routes::Feeds
|
||||
xml.element("link", "type": "text/html", rel: "alternate", href: "#{HOST_URL}/feed/subscriptions")
|
||||
xml.element("link", "type": "application/atom+xml", rel: "self",
|
||||
href: "#{HOST_URL}#{env.request.resource}")
|
||||
xml.element("title") { xml.text I18n.translate(locale, "Invidious Private Feed for `x`", user.email) }
|
||||
xml.element("title") { xml.text translate(locale, "Invidious Private Feed for `x`", user.email) }
|
||||
|
||||
(notifications + videos).each do |video|
|
||||
video.to_xml(locale, params, xml)
|
||||
|
||||
@@ -96,7 +96,7 @@ module Invidious::Routes::Images
|
||||
break
|
||||
end
|
||||
|
||||
Helpers.proxy_file(response, env)
|
||||
proxy_file(response, env)
|
||||
end
|
||||
rescue ex
|
||||
end
|
||||
@@ -148,6 +148,6 @@ module Invidious::Routes::Images
|
||||
return env.response.headers.delete("Transfer-Encoding")
|
||||
end
|
||||
|
||||
return Helpers.proxy_file(response, env)
|
||||
return proxy_file(response, env)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,7 +112,7 @@ module Invidious::Routes::Login
|
||||
user, sid = create_user(sid, email, password)
|
||||
|
||||
if language_header = env.request.headers["Accept-Language"]?
|
||||
if language = ANG.language_negotiator.best(language_header, I18n::LOCALES.keys)
|
||||
if language = ANG.language_negotiator.best(language_header, LOCALES.keys)
|
||||
user.preferences.locale = language.header
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,7 +83,7 @@ module Invidious::Routes::VideoPlayback
|
||||
# Remove the Range header added previously.
|
||||
headers.delete("Range") if range_header.nil?
|
||||
|
||||
playback_statistics = Helpers.get_playback_statistic
|
||||
playback_statistics = get_playback_statistic()
|
||||
playback_statistics["totalRequests"] += 1
|
||||
|
||||
if response.status_code >= 400
|
||||
@@ -195,7 +195,7 @@ module Invidious::Routes::VideoPlayback
|
||||
end
|
||||
end
|
||||
|
||||
Helpers.proxy_file(resp, env)
|
||||
proxy_file(resp, env)
|
||||
end
|
||||
rescue ex
|
||||
if ex.message != "Error reading socket: Connection reset by peer"
|
||||
|
||||
@@ -57,6 +57,8 @@ module Invidious::Search
|
||||
# Values correspond to { "1:varint": <X> }
|
||||
enum Sort
|
||||
Relevance = 0
|
||||
Rating = 1
|
||||
Date = 2
|
||||
Views = 3
|
||||
end
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ module Invidious::Search
|
||||
if response.status_code == 404
|
||||
response = YT_POOL.client &.get("/user/#{query.channel}")
|
||||
response = YT_POOL.client &.get("/c/#{query.channel}") if response.status_code == 404
|
||||
initial_data = Helpers.extract_initial_data(response.body)
|
||||
initial_data = extract_initial_data(response.body)
|
||||
ucid = initial_data.dig?("header", "c4TabbedHeaderRenderer", "channelId").try(&.as_s?)
|
||||
raise ChannelSearchException.new(query.channel) if !ucid
|
||||
else
|
||||
|
||||
@@ -15,7 +15,7 @@ struct Invidious::User
|
||||
playlists.each do |playlist|
|
||||
json.object do
|
||||
json.field "title", playlist.title
|
||||
json.field "description", Helpers.html_to_content(playlist.description_html)
|
||||
json.field "description", html_to_content(playlist.description_html)
|
||||
json.field "privacy", playlist.privacy.to_s
|
||||
json.field "videos" do
|
||||
json.array do
|
||||
|
||||
@@ -84,7 +84,7 @@ def extract_video_info(video_id : String)
|
||||
|
||||
# Although technically not a call to /videoplayback the fact that YouTube is returning the
|
||||
# wrong video means that we should count it as a failure.
|
||||
Helpers.get_playback_statistic["totalRequests"] += 1
|
||||
get_playback_statistic()["totalRequests"] += 1
|
||||
|
||||
return {
|
||||
"version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64),
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
<div class="pure-u-1 pure-u-lg-3-5">
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/add_playlist_items" method="get">
|
||||
<legend><a href="/playlist?list=<%= playlist.id %>"><%= I18n.translate(locale, "Editing playlist `x`", %|"#{HTML.escape(playlist.title)}"|) %></a></legend>
|
||||
<legend><a href="/playlist?list=<%= playlist.id %>"><%= translate(locale, "Editing playlist `x`", %|"#{HTML.escape(playlist.title)}"|) %></a></legend>
|
||||
|
||||
<fieldset>
|
||||
<input class="pure-input-1" type="search" name="q"
|
||||
<% if query %>value="<%= HTML.escape(query.text) %>"<% end %>
|
||||
placeholder="<%= I18n.translate(locale, "Search for videos") %>">
|
||||
placeholder="<%= translate(locale, "Search for videos") %>">
|
||||
<input type="hidden" name="list" value="<%= plid %>">
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
<%=
|
||||
{
|
||||
"ucid" => ucid,
|
||||
"youtube_comments_text" => HTML.escape(I18n.translate(locale, "View YouTube comments")),
|
||||
"comments_text" => HTML.escape(I18n.translate(locale, "View `x` comments", "{commentCount}")),
|
||||
"hide_replies_text" => HTML.escape(I18n.translate(locale, "Hide replies")),
|
||||
"show_replies_text" => HTML.escape(I18n.translate(locale, "Show replies")),
|
||||
"youtube_comments_text" => HTML.escape(translate(locale, "View YouTube comments")),
|
||||
"comments_text" => HTML.escape(translate(locale, "View `x` comments", "{commentCount}")),
|
||||
"hide_replies_text" => HTML.escape(translate(locale, "Hide replies")),
|
||||
"show_replies_text" => HTML.escape(translate(locale, "Show replies")),
|
||||
"preferences" => env.get("preferences").as(Preferences)
|
||||
}.to_pretty_json
|
||||
%>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary" dir="auto" href="/feed/channel/<%= ucid %>">
|
||||
<i class="icon ion-logo-rss"></i> <%= I18n.translate(locale, "generic_button_rss") %>
|
||||
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,10 +40,10 @@
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-2">
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<a href="<%= youtube_url %>"><%= I18n.translate(locale, "View channel on YouTube") %></a>
|
||||
<a href="<%= youtube_url %>"><%= translate(locale, "View channel on YouTube") %></a>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<a href="<%= redirect_url %>"><%= I18n.translate(locale, "Switch Invidious Instance") %></a>
|
||||
<a href="<%= redirect_url %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
||||
</div>
|
||||
|
||||
<%= Invidious::Frontend::ChannelPage.generate_tabs_links(locale, channel, selected_tab) %>
|
||||
@@ -53,9 +53,9 @@
|
||||
<% sort_options.each do |sort| %>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<% if sort_by == sort %>
|
||||
<b><%= I18n.translate(locale, sort) %></b>
|
||||
<b><%= translate(locale, sort) %></b>
|
||||
<% else %>
|
||||
<a href="<%= relative_url %>?sort_by=<%= sort %>"><%= I18n.translate(locale, sort) %></a>
|
||||
<a href="<%= relative_url %>?sort_by=<%= sort %>"><%= translate(locale, sort) %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<% end %>
|
||||
<% feed_menu.each do |feed| %>
|
||||
<a href="/feed/<%= feed.downcase %>" class="feed-menu-item pure-menu-heading">
|
||||
<%= I18n.translate(locale, feed) %>
|
||||
<%= translate(locale, feed) %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
</div>
|
||||
|
||||
<% if !item.channel_handle.nil? %><p class="channel-name" dir="auto"><%= item.channel_handle %></p><% end %>
|
||||
<p><%= I18n.translate_count(locale, "generic_subscribers_count", item.subscriber_count, I18n::NumberFormatting::Separator) %></p>
|
||||
<% if !item.auto_generated && item.channel_handle.nil? %><p><%= I18n.translate_count(locale, "generic_videos_count", item.video_count, I18n::NumberFormatting::Separator) %></p><% end %>
|
||||
<p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p>
|
||||
<% if !item.auto_generated && item.channel_handle.nil? %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %>
|
||||
<h5><%= item.description_html %></h5>
|
||||
<% when SearchHashtag %>
|
||||
<% if !thin_mode %>
|
||||
@@ -45,13 +45,13 @@
|
||||
|
||||
<div class="video-card-row">
|
||||
<%- if item.video_count != 0 -%>
|
||||
<p><%= I18n.translate_count(locale, "generic_videos_count", item.video_count, I18n::NumberFormatting::Separator) %></p>
|
||||
<p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
|
||||
<%- end -%>
|
||||
</div>
|
||||
|
||||
<div class="video-card-row">
|
||||
<%- if item.channel_count != 0 -%>
|
||||
<p><%= I18n.translate_count(locale, "generic_channels_count", item.channel_count, I18n::NumberFormatting::Separator) %></p>
|
||||
<p><%= translate_count(locale, "generic_channels_count", item.channel_count, NumberFormatting::Separator) %></p>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<% when SearchPlaylist, InvidiousPlaylist %>
|
||||
@@ -73,7 +73,7 @@
|
||||
<%- end -%>
|
||||
|
||||
<div class="bottom-right-overlay">
|
||||
<p class="length"><%= I18n.translate_count(locale, "generic_videos_count", item.video_count, I18n::NumberFormatting::Separator) %></p>
|
||||
<p class="length"><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,11 +101,11 @@
|
||||
<div class="error-card">
|
||||
<div class="explanation">
|
||||
<i class="icon ion-ios-alert"></i>
|
||||
<h4><%=I18n.translate(locale, "timeline_parse_error_placeholder_heading")%></h4>
|
||||
<p><%=I18n.translate(locale, "timeline_parse_error_placeholder_message")%></p>
|
||||
<h4><%=translate(locale, "timeline_parse_error_placeholder_heading")%></h4>
|
||||
<p><%=translate(locale, "timeline_parse_error_placeholder_message")%></p>
|
||||
</div>
|
||||
<details>
|
||||
<summary class="pure-button pure-button-secondary"><%=I18n.translate(locale, "timeline_parse_error_show_technical_details")%></summary>
|
||||
<summary class="pure-button pure-button-secondary"><%=translate(locale, "timeline_parse_error_show_technical_details")%></summary>
|
||||
<pre class="error-issue-template"><%=get_issue_template(env, item.parse_exception)[1]%></pre>
|
||||
</details>
|
||||
</div>
|
||||
@@ -168,7 +168,7 @@
|
||||
|
||||
<div class="bottom-right-overlay">
|
||||
<%- if item.responds_to?(:live_now) && item.live_now -%>
|
||||
<p class="length" dir="auto"><i class="icon ion-ios-play-circle"></i> <%= I18n.translate(locale, "LIVE") %></p>
|
||||
<p class="length" dir="auto"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
|
||||
<%- elsif item.length_seconds != 0 -%>
|
||||
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
||||
<%- end -%>
|
||||
@@ -200,15 +200,15 @@
|
||||
<div class="video-card-row flexible">
|
||||
<div class="flex-left">
|
||||
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>
|
||||
<p class="video-data" dir="auto"><%= I18n.translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %></p>
|
||||
<p class="video-data" dir="auto"><%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %></p>
|
||||
<% elsif item.responds_to?(:published) && (Time.utc - item.published) > 1.minute %>
|
||||
<p class="video-data" dir="auto"><%= I18n.translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></p>
|
||||
<p class="video-data" dir="auto"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if item.responds_to?(:views) && item.views %>
|
||||
<div class="flex-right">
|
||||
<p class="video-data" dir="auto"><%= I18n.translate_count(locale, "generic_views_count", item.views || 0, I18n::NumberFormatting::Short) %></p>
|
||||
<p class="video-data" dir="auto"><%= translate_count(locale, "generic_views_count", item.views || 0, NumberFormatting::Short) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
<script id="pagination-data" type="application/json">
|
||||
<%=
|
||||
{
|
||||
"next_page" => I18n.translate(locale, "Next page"),
|
||||
"prev_page" => I18n.translate(locale, "Previous page"),
|
||||
"is_rtl" => I18n.locale_is_rtl?(locale)
|
||||
"next_page" => translate(locale, "Next page"),
|
||||
"prev_page" => translate(locale, "Previous page"),
|
||||
"is_rtl" => locale_is_rtl?(locale)
|
||||
}.to_pretty_json
|
||||
%>
|
||||
</script>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
audio_streams.each_with_index do |fmt, i|
|
||||
src_url = "/latest_version?id=#{video.id}&itag=#{fmt["itag"]}"
|
||||
src_url += "&local=true" if params.local
|
||||
src_url = invidious_companion.public_url.to_s + src_url +
|
||||
src_url = invidious_companion.public_url.to_s + src_url +
|
||||
"&check=#{invidious_companion_check_id}" if (invidious_companion)
|
||||
|
||||
bitrate = fmt["bitrate"]
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<fieldset>
|
||||
<input type="search" id="searchbox" autocorrect="off"
|
||||
autocapitalize="none" spellcheck="false" <% if autofocus %>autofocus<% end %>
|
||||
name="q" placeholder="<%= I18n.translate(locale, "search") %>"
|
||||
title="<%= I18n.translate(locale, "search") %>"
|
||||
name="q" placeholder="<%= translate(locale, "search") %>"
|
||||
title="<%= translate(locale, "search") %>"
|
||||
value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } %>">
|
||||
</fieldset>
|
||||
<button type="submit" id="searchbutton" aria-label="<%= I18n.translate(locale, "search") %>">
|
||||
<button type="submit" id="searchbutton" aria-label="<%= translate(locale, "search") %>">
|
||||
<i class="icon ion-ios-search"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<form action="/subscription_ajax?action=remove_subscriptions&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button data-type="unsubscribe" id="subscribe" class="pure-button pure-button-primary">
|
||||
<b><input style="all:unset" type="submit" value="<%= I18n.translate(locale, "Unsubscribe") %> | <%= sub_count_text %>"></b>
|
||||
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %>"></b>
|
||||
</button>
|
||||
</form>
|
||||
<% else %>
|
||||
<form action="/subscription_ajax?action=create_subscription_to_channel&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button data-type="subscribe" id="subscribe" class="pure-button pure-button-primary">
|
||||
<b><input style="all:unset" type="submit" value="<%= I18n.translate(locale, "Subscribe") %> | <%= sub_count_text %>"></b>
|
||||
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Subscribe") %> | <%= sub_count_text %>"></b>
|
||||
</button>
|
||||
</form>
|
||||
<% end %>
|
||||
@@ -22,8 +22,8 @@
|
||||
"author" => HTML.escape(author),
|
||||
"sub_count_text" => HTML.escape(sub_count_text),
|
||||
"csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || ""),
|
||||
"subscribe_text" => HTML.escape(I18n.translate(locale, "Subscribe")),
|
||||
"unsubscribe_text" => HTML.escape(I18n.translate(locale, "Unsubscribe"))
|
||||
"subscribe_text" => HTML.escape(translate(locale, "Subscribe")),
|
||||
"unsubscribe_text" => HTML.escape(translate(locale, "Unsubscribe"))
|
||||
}.to_pretty_json
|
||||
%>
|
||||
</script>
|
||||
@@ -31,6 +31,6 @@
|
||||
<% else %>
|
||||
<a id="subscribe" class="pure-button pure-button-primary"
|
||||
href="/login?referer=<%= env.get("current_page") %>">
|
||||
<b><%= I18n.translate(locale, "Subscribe") %> | <%= sub_count_text %></b>
|
||||
<b><%= translate(locale, "Subscribe") %> | <%= sub_count_text %></b>
|
||||
</a>
|
||||
<% end %>
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<div class="flex-right flexible">
|
||||
<div class="icon-buttons">
|
||||
<a title="<%=I18n.translate(locale, "videoinfo_watch_on_youTube")%>" rel="noreferrer noopener" href="https://www.youtube.com/watch<%=endpoint_params%>">
|
||||
<a title="<%=translate(locale, "videoinfo_watch_on_youTube")%>" rel="noreferrer noopener" href="https://www.youtube.com/watch<%=endpoint_params%>">
|
||||
<i class="icon ion-logo-youtube"></i>
|
||||
</a>
|
||||
<a title="<%=I18n.translate(locale, "Audio mode")%>" href="/watch<%=endpoint_params%>&listen=1">
|
||||
<a title="<%=translate(locale, "Audio mode")%>" href="/watch<%=endpoint_params%>&listen=1">
|
||||
<i class="icon ion-md-headset"></i>
|
||||
</a>
|
||||
|
||||
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
||||
<a title="<%=I18n.translate(locale, "Switch Invidious Instance")%>" href="/redirect?referer=%2Fwatch<%=URI.encode_www_form(endpoint_params)%>">
|
||||
<a title="<%=translate(locale, "Switch Invidious Instance")%>" href="/redirect?referer=%2Fwatch<%=URI.encode_www_form(endpoint_params)%>">
|
||||
<i class="icon ion-md-jet"></i>
|
||||
</a>
|
||||
<% else %>
|
||||
<a title="<%=I18n.translate(locale, "Switch Invidious Instance")%>" href="https://redirect.invidious.io/watch<%=endpoint_params%>">
|
||||
<a title="<%=translate(locale, "Switch Invidious Instance")%>" href="https://redirect.invidious.io/watch<%=endpoint_params%>">
|
||||
<i class="icon ion-md-jet"></i>
|
||||
</a>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Create playlist") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Create playlist") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g">
|
||||
@@ -8,25 +8,25 @@
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/create_playlist?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||
<fieldset>
|
||||
<legend><%= I18n.translate(locale, "Create playlist") %></legend>
|
||||
<legend><%= translate(locale, "Create playlist") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="title"><%= I18n.translate(locale, "Title") %> :</label>
|
||||
<input required name="title" type="text" placeholder="<%= I18n.translate(locale, "Title") %>">
|
||||
<label for="title"><%= translate(locale, "Title") %> :</label>
|
||||
<input required name="title" type="text" placeholder="<%= translate(locale, "Title") %>">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="privacy"><%= I18n.translate(locale, "Playlist privacy") %> :</label>
|
||||
<label for="privacy"><%= translate(locale, "Playlist privacy") %> :</label>
|
||||
<select name="privacy" id="privacy">
|
||||
<% PlaylistPrivacy.names.each do |option| %>
|
||||
<option value="<%= option %>" <% if option == "Public" %> selected <% end %>><%= I18n.translate(locale, option) %></option>
|
||||
<option value="<%= option %>" <% if option == "Public" %> selected <% end %>><%= translate(locale, option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-controls">
|
||||
<button type="submit" name="action" value="create_playlist" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Create playlist") %>
|
||||
<%= translate(locale, "Create playlist") %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Delete playlist") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Delete playlist") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/delete_playlist?list=<%= plid %>&referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||
<legend><%= I18n.translate(locale, "Delete playlist `x`?", %|"#{HTML.escape(playlist.title)}"|) %></legend>
|
||||
<legend><%= translate(locale, "Delete playlist `x`?", %|"#{HTML.escape(playlist.title)}"|) %></legend>
|
||||
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<button type="submit" name="submit" value="delete_playlist" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Yes") %>
|
||||
<%= translate(locale, "Yes") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pure-u-1-2">
|
||||
<a class="pure-button" href="/playlist?list=<%= plid %>">
|
||||
<%= I18n.translate(locale, "No") %>
|
||||
<%= translate(locale, "No") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,17 +10,17 @@
|
||||
<div class="flex-right button-container">
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-close"></i> <%= I18n.translate(locale, "generic_button_cancel") %>
|
||||
<i class="icon ion-md-close"></i> <%= translate(locale, "generic_button_cancel") %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<button class="pure-button pure-button-secondary low-profile" dir="auto" type="submit">
|
||||
<i class="icon ion-md-save"></i> <%= I18n.translate(locale, "generic_button_save") %>
|
||||
<i class="icon ion-md-save"></i> <%= translate(locale, "generic_button_save") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-trash"></i> <%= I18n.translate(locale, "generic_button_delete") %>
|
||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,11 +36,11 @@
|
||||
<div class="pure-u-1-1">
|
||||
<b>
|
||||
<%= HTML.escape(playlist.author) %> |
|
||||
<%= I18n.translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
||||
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
||||
</b>
|
||||
<select name="privacy">
|
||||
<%- {"Public", "Unlisted", "Private"}.each do |option| -%>
|
||||
<option value="<%= option %>" <% if option == playlist.privacy.to_s %>selected<% end %>><%= I18n.translate(locale, option) %></option>
|
||||
<option value="<%= option %>" <% if option == playlist.privacy.to_s %>selected<% end %>><%= translate(locale, option) %></option>
|
||||
<%- end -%>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "History") %> - Invidious</title>
|
||||
<title><%= translate(locale, "History") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-3">
|
||||
<h3><%= I18n.translate_count(locale, "generic_videos_count", user.watched.size, I18n::NumberFormatting::HtmlSpan) %></h3>
|
||||
<h3><%= translate_count(locale, "generic_videos_count", user.watched.size, NumberFormatting::HtmlSpan) %></h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:center">
|
||||
<a href="/feed/subscriptions"><%= I18n.translate_count(locale, "generic_subscriptions_count", user.subscriptions.size, I18n::NumberFormatting::HtmlSpan) %></a>
|
||||
<a href="/feed/subscriptions"><%= translate_count(locale, "generic_subscriptions_count", user.subscriptions.size, NumberFormatting::HtmlSpan) %></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:right">
|
||||
<a href="/clear_watch_history"><%= I18n.translate(locale, "Clear watch history") %></a>
|
||||
<a href="/clear_watch_history"><%= translate(locale, "Clear watch history") %></a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Playlists") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Playlists") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<%= rendered "components/feed_menu" %>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-3">
|
||||
<h3><%= I18n.translate(locale, "user_created_playlists", %(<span id="count">#{items_created.size}</span>)) %></h3>
|
||||
<h3><%= translate(locale, "user_created_playlists", %(<span id="count">#{items_created.size}</span>)) %></h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:center">
|
||||
<a href="/create_playlist?referer=<%= URI.encode_www_form("/feed/playlists") %>"><%= I18n.translate(locale, "Create playlist") %></a>
|
||||
<a href="/create_playlist?referer=<%= URI.encode_www_form("/feed/playlists") %>"><%= translate(locale, "Create playlist") %></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:right">
|
||||
<a href="/data_control?referer=<%= URI.encode_www_form("/feed/playlists") %>">
|
||||
<%= I18n.translate(locale, "Import/export") %>
|
||||
<%= translate(locale, "Import/export") %>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1">
|
||||
<h3><%= I18n.translate(locale, "user_saved_playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3>
|
||||
<h3><%= translate(locale, "user_saved_playlists", %(<span id="count">#{items_saved.size}</span>)) %></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<% content_for "header" do %>
|
||||
<meta name="description" content="<%= I18n.translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<title>
|
||||
<% if env.get("preferences").as(Preferences).default_home != "Popular" %>
|
||||
<%= I18n.translate(locale, "Popular") %> - Invidious
|
||||
<%= translate(locale, "Popular") %> - Invidious
|
||||
<% else %>
|
||||
Invidious
|
||||
<% end %>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Subscriptions") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Subscriptions") %> - Invidious</title>
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/private?token=<%= token %>" />
|
||||
<% end %>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-3">
|
||||
<h3>
|
||||
<a href="/subscription_manager"><%= I18n.translate(locale, "Manage subscriptions") %></a>
|
||||
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:center">
|
||||
<a href="/feed/history"><%= I18n.translate(locale, "Watch history") %></a>
|
||||
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
@@ -26,7 +26,7 @@
|
||||
<% if CONFIG.enable_user_notifications %>
|
||||
|
||||
<center>
|
||||
<%= I18n.translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %>
|
||||
<%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %>
|
||||
</center>
|
||||
|
||||
<% if !notifications.empty? %>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<% content_for "header" do %>
|
||||
<meta name="description" content="<%= I18n.translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<title>
|
||||
<% if env.get("preferences").as(Preferences).default_home != "Trending" %>
|
||||
<%= I18n.translate(locale, "Trending") %> - Invidious
|
||||
<%= translate(locale, "Trending") %> - Invidious
|
||||
<% else %>
|
||||
Invidious
|
||||
<% end %>
|
||||
@@ -15,7 +15,7 @@
|
||||
<div style="align-self:flex-end" class="pure-u-2-3">
|
||||
<% if plid %>
|
||||
<a href="/playlist?list=<%= plid %>">
|
||||
<%= I18n.translate(locale, "View as playlist") %>
|
||||
<%= translate(locale, "View as playlist") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -24,10 +24,10 @@
|
||||
<% {"Livestreams", "Gaming"}.each do |option| %>
|
||||
<div class="pure-u-1 pure-md-1-3">
|
||||
<% if trending_type == option %>
|
||||
<b><%= I18n.translate(locale, option) %></b>
|
||||
<b><%= translate(locale, option) %></b>
|
||||
<% else %>
|
||||
<a href="/feed/trending?type=<%= option %>®ion=<%= region %>">
|
||||
<%= I18n.translate(locale, option) %>
|
||||
<%= translate(locale, option) %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1><%= I18n.translate(locale, "JavaScript license information") %></h1>
|
||||
<h1><%= translate(locale, "JavaScript license information") %></h1>
|
||||
<table id="jslicense-labels1">
|
||||
<tr>
|
||||
<td>
|
||||
@@ -19,7 +19,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/iv-org/videojs-quality-selector"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/iv-org/videojs-quality-selector"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/mpetazzoni/sse.js"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/mpetazzoni/sse.js"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/videojs/videojs-contrib-quality-levels"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/videojs/videojs-contrib-quality-levels"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/jfujita/videojs-http-source-selector"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/jfujita/videojs-http-source-selector"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/mister-ben/videojs-mobile-ui"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/mister-ben/videojs-mobile-ui"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/spchuang/videojs-markers"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/spchuang/videojs-markers"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/brightcove/videojs-overlay"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/brightcove/videojs-overlay"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/mkhazov/videojs-share"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/mkhazov/videojs-share"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/chrisboustead/videojs-vtt-thumbnails"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/chrisboustead/videojs-vtt-thumbnails"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/afrmtbl/videojs-youtube-annotations"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/afrmtbl/videojs-youtube-annotations"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/videojs/videojs-vr"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/videojs/videojs-vr"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<a href="https://github.com/videojs/video.js"><%= I18n.translate(locale, "source") %></a>
|
||||
<a href="https://github.com/videojs/video.js"><%= translate(locale, "source") %></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% content_for "header" do %>
|
||||
<meta name="description" content="<%= I18n.translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<title>
|
||||
Invidious
|
||||
</title>
|
||||
|
||||
@@ -13,28 +13,28 @@
|
||||
<%- if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email -%>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/add_playlist_items?list=<%= plid %>">
|
||||
<i class="icon ion-md-add"></i> <%= I18n.translate(locale, "playlist_button_add_items") %>
|
||||
<i class="icon ion-md-add"></i> <%= translate(locale, "playlist_button_add_items") %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/edit_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-create"></i> <%= I18n.translate(locale, "generic_button_edit") %>
|
||||
<i class="icon ion-md-create"></i> <%= translate(locale, "generic_button_edit") %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-trash"></i> <%= I18n.translate(locale, "generic_button_delete") %>
|
||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
||||
</a>
|
||||
</div>
|
||||
<%- else -%>
|
||||
<div class="pure-u">
|
||||
<%- if IV::Database::Playlists.exists?(playlist.id) -%>
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/subscribe_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-add"></i> <%= I18n.translate(locale, "Subscribe") %>
|
||||
<i class="icon ion-md-add"></i> <%= translate(locale, "Subscribe") %>
|
||||
</a>
|
||||
<%- else -%>
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-trash"></i> <%= I18n.translate(locale, "Unsubscribe") %>
|
||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "Unsubscribe") %>
|
||||
</a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/feed/playlist/<%= plid %>">
|
||||
<i class="icon ion-logo-rss"></i> <%= I18n.translate(locale, "generic_button_rss") %>
|
||||
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,15 +57,15 @@
|
||||
<% else %>
|
||||
<%= author %> |
|
||||
<% end %>
|
||||
<%= I18n.translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
||||
<%= I18n.translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> |
|
||||
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
||||
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> |
|
||||
<% case playlist.as(InvidiousPlaylist).privacy when %>
|
||||
<% when PlaylistPrivacy::Public %>
|
||||
<i class="icon ion-md-globe"></i> <%= I18n.translate(locale, "Public") %>
|
||||
<i class="icon ion-md-globe"></i> <%= translate(locale, "Public") %>
|
||||
<% when PlaylistPrivacy::Unlisted %>
|
||||
<i class="icon ion-ios-unlock"></i> <%= I18n.translate(locale, "Unlisted") %>
|
||||
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
|
||||
<% when PlaylistPrivacy::Private %>
|
||||
<i class="icon ion-ios-lock"></i> <%= I18n.translate(locale, "Private") %>
|
||||
<i class="icon ion-ios-lock"></i> <%= translate(locale, "Private") %>
|
||||
<% end %>
|
||||
</b>
|
||||
<% else %>
|
||||
@@ -76,25 +76,25 @@
|
||||
<% subtitle = playlist.subtitle || "" %>
|
||||
<span><%= HTML.escape(subtitle[0..subtitle.rindex(" • ") || subtitle.size]) %></span> |
|
||||
<% end %>
|
||||
<%= I18n.translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
||||
<%= I18n.translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %>
|
||||
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
||||
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %>
|
||||
</b>
|
||||
<% end %>
|
||||
|
||||
<% if !playlist.is_a? InvidiousPlaylist %>
|
||||
<div class="pure-u-2-3">
|
||||
<a rel="noreferrer noopener" href="https://www.youtube.com/playlist?list=<%= playlist.id %>">
|
||||
<%= I18n.translate(locale, "View playlist on YouTube") %>
|
||||
<%= translate(locale, "View playlist on YouTube") %>
|
||||
</a>
|
||||
<span> | </span>
|
||||
|
||||
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
|
||||
<a href="/redirect?referer=<%= env.get?("current_page") %>">
|
||||
<%= I18n.translate(locale, "Switch Invidious Instance") %>
|
||||
<%= translate(locale, "Switch Invidious Instance") %>
|
||||
</a>
|
||||
<% else %>
|
||||
<a href="https://redirect.invidious.io/playlist?list=<%= playlist.id %>">
|
||||
<%= I18n.translate(locale, "Switch Invidious Instance") %>
|
||||
<%= translate(locale, "Switch Invidious Instance") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<% else %>
|
||||
<noscript>
|
||||
<a href="/post/<%= id %>?ucid=<%= ucid %>&nojs=1">
|
||||
<%= I18n.translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
|
||||
<%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
|
||||
</a>
|
||||
</noscript>
|
||||
<% end %>
|
||||
@@ -29,12 +29,12 @@
|
||||
<%=
|
||||
{
|
||||
"id" => id,
|
||||
"youtube_comments_text" => HTML.escape(I18n.translate(locale, "View YouTube comments")),
|
||||
"youtube_comments_text" => HTML.escape(translate(locale, "View YouTube comments")),
|
||||
"reddit_comments_text" => "",
|
||||
"reddit_permalink_text" => "",
|
||||
"comments_text" => HTML.escape(I18n.translate(locale, "View `x` comments", "{commentCount}")),
|
||||
"hide_replies_text" => HTML.escape(I18n.translate(locale, "Hide replies")),
|
||||
"show_replies_text" => HTML.escape(I18n.translate(locale, "Show replies")),
|
||||
"comments_text" => HTML.escape(translate(locale, "View `x` comments", "{commentCount}")),
|
||||
"hide_replies_text" => HTML.escape(translate(locale, "Hide replies")),
|
||||
"show_replies_text" => HTML.escape(translate(locale, "Show replies")),
|
||||
"params" => {
|
||||
"comments": ["youtube"]
|
||||
},
|
||||
@@ -45,4 +45,4 @@
|
||||
%>
|
||||
</script>
|
||||
<script src="/js/comments.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<script src="/js/post.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<script src="/js/post.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
@@ -11,9 +11,9 @@
|
||||
<%- if items.empty? -%>
|
||||
<div class="h-box no-results-error">
|
||||
<div>
|
||||
<%= I18n.translate(locale, "search_message_no_results") %><br/><br/>
|
||||
<%= I18n.translate(locale, "search_message_change_filters_or_query") %><br/><br/>
|
||||
<%= I18n.translate(locale, "search_message_use_another_instance", redirect_url) %>
|
||||
<%= translate(locale, "search_message_no_results") %><br/><br/>
|
||||
<%= translate(locale, "search_message_change_filters_or_query") %><br/><br/>
|
||||
<%= translate(locale, "search_message_use_another_instance", redirect_url) %>
|
||||
</div>
|
||||
</div>
|
||||
<%- else -%>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<% content_for "header" do %>
|
||||
<meta name="description" content="<%= I18n.translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
|
||||
<title>
|
||||
Invidious - <%= I18n.translate(locale, "search") %>
|
||||
Invidious - <%= translate(locale, "search") %>
|
||||
</title>
|
||||
<link rel="stylesheet" href="/css/empty.css?v=<%= ASSET_COMMIT %>">
|
||||
<% end %>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="pure-u-1 pure-u-md-8-24 user-field">
|
||||
<% if env.get? "user" %>
|
||||
<div class="pure-u-1-4">
|
||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= I18n.translate(locale, "toggle_theme") %>">
|
||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
|
||||
<% if dark_mode == "dark" %>
|
||||
<i class="icon ion-ios-sunny"></i>
|
||||
<% else %>
|
||||
@@ -52,7 +52,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u-1-4">
|
||||
<a id="notification_ticker" title="<%= I18n.translate(locale, "Subscriptions") %>" href="/feed/subscriptions" class="pure-menu-heading">
|
||||
<a id="notification_ticker" title="<%= translate(locale, "Subscriptions") %>" href="/feed/subscriptions" class="pure-menu-heading">
|
||||
<% notification_count = env.get("user").as(Invidious::User).notifications.size %>
|
||||
<% if CONFIG.enable_user_notifications && notification_count > 0 %>
|
||||
<span id="notification_count"><%= notification_count %></span> <i class="icon ion-ios-notifications"></i>
|
||||
@@ -62,7 +62,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u-1-4">
|
||||
<a title="<%= I18n.translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||
<i class="icon ion-ios-cog"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -75,13 +75,13 @@
|
||||
<form action="/signout?referer=<%= env.get?("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<a class="pure-menu-heading" href="#">
|
||||
<input style="all:unset" type="submit" value="<%= I18n.translate(locale, "Log out") %>">
|
||||
<input style="all:unset" type="submit" value="<%= translate(locale, "Log out") %>">
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="pure-u-1-3">
|
||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= I18n.translate(locale, "toggle_theme") %>">
|
||||
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading" title="<%= translate(locale, "toggle_theme") %>">
|
||||
<% if dark_mode == "dark" %>
|
||||
<i class="icon ion-ios-sunny"></i>
|
||||
<% else %>
|
||||
@@ -90,14 +90,14 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<a title="<%= I18n.translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||
<i class="icon ion-ios-cog"></i>
|
||||
</a>
|
||||
</div>
|
||||
<% if CONFIG.login_enabled %>
|
||||
<div class="pure-u-1-3">
|
||||
<a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
|
||||
<%= I18n.translate(locale, "Log in") %>
|
||||
<%= translate(locale, "Log in") %>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -119,39 +119,39 @@
|
||||
<span>
|
||||
<i class="icon ion-logo-github"></i>
|
||||
<% if CONFIG.modified_source_code_url %>
|
||||
<a href="https://github.com/iv-org/invidious"><%= I18n.translate(locale, "footer_original_source_code") %></a> /
|
||||
<a href="<%= CONFIG.modified_source_code_url %>"><%= I18n.translate(locale, "footer_modfied_source_code") %></a>
|
||||
<a href="https://github.com/iv-org/invidious"><%= translate(locale, "footer_original_source_code") %></a> /
|
||||
<a href="<%= CONFIG.modified_source_code_url %>"><%= translate(locale, "footer_modfied_source_code") %></a>
|
||||
<% else %>
|
||||
<a href="https://github.com/iv-org/invidious"><%= I18n.translate(locale, "footer_source_code") %></a>
|
||||
<a href="https://github.com/iv-org/invidious"><%= translate(locale, "footer_source_code") %></a>
|
||||
<% end %>
|
||||
</span>
|
||||
<span>
|
||||
<i class="icon ion-ios-paper"></i>
|
||||
<a href="https://github.com/iv-org/documentation"><%= I18n.translate(locale, "footer_documentation") %></a>
|
||||
<a href="https://github.com/iv-org/documentation"><%= translate(locale, "footer_documentation") %></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<span>
|
||||
<a href="https://github.com/iv-org/invidious/blob/master/LICENSE"><%= I18n.translate(locale, "Released under the AGPLv3 on Github.") %></a>
|
||||
<a href="https://github.com/iv-org/invidious/blob/master/LICENSE"><%= translate(locale, "Released under the AGPLv3 on Github.") %></a>
|
||||
</span>
|
||||
<span>
|
||||
<i class="icon ion-logo-javascript"></i>
|
||||
<a rel="jslicense" href="/licenses"><%= I18n.translate(locale, "View JavaScript license information.") %></a>
|
||||
<a rel="jslicense" href="/licenses"><%= translate(locale, "View JavaScript license information.") %></a>
|
||||
</span>
|
||||
<span>
|
||||
<i class="icon ion-ios-paper"></i>
|
||||
<a href="/privacy"><%= I18n.translate(locale, "View privacy policy.") %></a>
|
||||
<a href="/privacy"><%= translate(locale, "View privacy policy.") %></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1 pure-u-md-1-3">
|
||||
<span>
|
||||
<i class="icon ion-ios-wallet"></i>
|
||||
<a href="https://invidious.io/donate/"><%= I18n.translate(locale, "footer_donate_page") %></a>
|
||||
<a href="https://invidious.io/donate/"><%= translate(locale, "footer_donate_page") %></a>
|
||||
</span>
|
||||
<span>
|
||||
<%= I18n.translate(locale, "Current version: ") %>
|
||||
<%= translate(locale, "Current version: ") %>
|
||||
<% if CONFIG.modified_source_code_url %>
|
||||
<a href="<%= CONFIG.modified_source_code_url %>/commit/<%= CURRENT_COMMIT %>"><%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %></a>
|
||||
<% else %>
|
||||
@@ -159,7 +159,7 @@
|
||||
<% end %>
|
||||
@ <%= CURRENT_BRANCH %>
|
||||
<% if CURRENT_TAG != "" %>
|
||||
(
|
||||
(
|
||||
<% if CONFIG.modified_source_code_url %>
|
||||
<a href="<%= CONFIG.modified_source_code_url %>/releases/tag/<%= CURRENT_TAG %>"><%= CURRENT_TAG %></a>
|
||||
<% else %>
|
||||
@@ -181,8 +181,8 @@
|
||||
<script id="notification_data" type="application/json">
|
||||
<%=
|
||||
{
|
||||
"upload_text" => HTML.escape(I18n.translate(locale, "`x` uploaded a video")),
|
||||
"live_upload_text" => HTML.escape(I18n.translate(locale, "`x` is live"))
|
||||
"upload_text" => HTML.escape(translate(locale, "`x` uploaded a video")),
|
||||
"live_upload_text" => HTML.escape(translate(locale, "`x` is live"))
|
||||
}.to_pretty_json
|
||||
%>
|
||||
</script>
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Token") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Token") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<% if env.get? "access_token" %>
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-3">
|
||||
<h3>
|
||||
<%= I18n.translate(locale, "Token") %>
|
||||
<%= translate(locale, "Token") %>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:center">
|
||||
<a href="/token_manager"><%= I18n.translate(locale, "Token manager") %></a>
|
||||
<a href="/token_manager"><%= translate(locale, "Token manager") %></a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:right">
|
||||
<a href="/preferences"><%= I18n.translate(locale, "Preferences") %></a>
|
||||
<a href="/preferences"><%= translate(locale, "Preferences") %></a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,9 +30,9 @@
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/authorize_token" method="post">
|
||||
<% if callback_url %>
|
||||
<legend><%= I18n.translate(locale, "Authorize token for `x`?", "#{callback_url.scheme}://#{callback_url.host}") %></legend>
|
||||
<legend><%= translate(locale, "Authorize token for `x`?", "#{callback_url.scheme}://#{callback_url.host}") %></legend>
|
||||
<% else %>
|
||||
<legend><%= I18n.translate(locale, "Authorize token?") %></legend>
|
||||
<legend><%= translate(locale, "Authorize token?") %></legend>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g">
|
||||
@@ -48,7 +48,7 @@
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Yes") %>
|
||||
<%= translate(locale, "Yes") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pure-u-1-2">
|
||||
@@ -57,7 +57,7 @@
|
||||
<% else %>
|
||||
<a class="pure-button" href="/">
|
||||
<% end %>
|
||||
<%= I18n.translate(locale, "No") %>
|
||||
<%= translate(locale, "No") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Change password") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Change password") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g">
|
||||
@@ -7,20 +7,20 @@
|
||||
<div class="pure-u-1 pure-u-lg-3-5">
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/change_password?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||
<legend><%= I18n.translate(locale, "Change password") %></legend>
|
||||
<legend><%= translate(locale, "Change password") %></legend>
|
||||
|
||||
<fieldset>
|
||||
<label for="password"><%= I18n.translate(locale, "Password") %> :</label>
|
||||
<input required class="pure-input-1" name="password" type="password" placeholder="<%= I18n.translate(locale, "Password") %>">
|
||||
<label for="password"><%= translate(locale, "Password") %> :</label>
|
||||
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
|
||||
|
||||
<label for="new_password[0]"><%= I18n.translate(locale, "New password") %> :</label>
|
||||
<input required class="pure-input-1" name="new_password[0]" type="password" placeholder="<%= I18n.translate(locale, "New password") %>">
|
||||
<label for="new_password[0]"><%= translate(locale, "New password") %> :</label>
|
||||
<input required class="pure-input-1" name="new_password[0]" type="password" placeholder="<%= translate(locale, "New password") %>">
|
||||
|
||||
<label for="new_password[1]"><%= I18n.translate(locale, "New password") %> :</label>
|
||||
<input required class="pure-input-1" name="new_password[1]" type="password" placeholder="<%= I18n.translate(locale, "New password") %>">
|
||||
<label for="new_password[1]"><%= translate(locale, "New password") %> :</label>
|
||||
<input required class="pure-input-1" name="new_password[1]" type="password" placeholder="<%= translate(locale, "New password") %>">
|
||||
|
||||
<button type="submit" name="action" value="change_password" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Change password") %>
|
||||
<%= translate(locale, "Change password") %>
|
||||
</button>
|
||||
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Clear watch history") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Clear watch history") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/clear_watch_history?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||
<legend><%= I18n.translate(locale, "Clear watch history?") %></legend>
|
||||
<legend><%= translate(locale, "Clear watch history?") %></legend>
|
||||
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Yes") %>
|
||||
<%= translate(locale, "Yes") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pure-u-1-2">
|
||||
<a class="pure-button" href="<%= URI.encode_www_form(referer) %>">
|
||||
<%= I18n.translate(locale, "No") %>
|
||||
<%= translate(locale, "No") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Import and Export Data") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Import and Export Data") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" enctype="multipart/form-data" action="/data_control?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||
<fieldset>
|
||||
<legend><%= I18n.translate(locale, "Import") %></legend>
|
||||
<legend><%= translate(locale, "Import") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="import_invidious"><%= I18n.translate(locale, "Import Invidious data") %></label>
|
||||
<label for="import_invidious"><%= translate(locale, "Import Invidious data") %></label>
|
||||
<input type="file" id="import_invidious" name="import_invidious">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="import_youtube">
|
||||
<a rel="noopener noreferrer" target="_blank" href="https://github.com/iv-org/documentation/blob/master/docs/export-youtube-subscriptions.md">
|
||||
<%= I18n.translate(locale, "Import YouTube subscriptions") %>
|
||||
<%= translate(locale, "Import YouTube subscriptions") %>
|
||||
</a>
|
||||
</label>
|
||||
<input type="file" id="import_youtube" name="import_youtube">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="import_youtube_pl"><%= I18n.translate(locale, "Import YouTube playlist (.csv)") %></label>
|
||||
<label for="import_youtube_pl"><%= translate(locale, "Import YouTube playlist (.csv)") %></label>
|
||||
<input type="file" id="import_youtube_pl" name="import_youtube_pl">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="import_youtube_wh"><%= I18n.translate(locale, "Import YouTube watch history (.json)") %></label>
|
||||
<label for="import_youtube_wh"><%= translate(locale, "Import YouTube watch history (.json)") %></label>
|
||||
<input type="file" id="import_youtube_wh" name="import_youtube_wh">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="import_freetube"><%= I18n.translate(locale, "Import FreeTube subscriptions (.db)") %></label>
|
||||
<label for="import_freetube"><%= translate(locale, "Import FreeTube subscriptions (.db)") %></label>
|
||||
<input type="file" id="import_freetube" name="import_freetube">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="import_newpipe_subscriptions"><%= I18n.translate(locale, "Import NewPipe subscriptions (.json)") %></label>
|
||||
<label for="import_newpipe_subscriptions"><%= translate(locale, "Import NewPipe subscriptions (.json)") %></label>
|
||||
<input type="file" id="import_newpipe_subscriptions" name="import_newpipe_subscriptions">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="import_newpipe"><%= I18n.translate(locale, "Import NewPipe data (.zip)") %></label>
|
||||
<label for="import_newpipe"><%= translate(locale, "Import NewPipe data (.zip)") %></label>
|
||||
<input type="file" id="import_newpipe" name="import_newpipe">
|
||||
</div>
|
||||
|
||||
<div class="pure-controls">
|
||||
<button type="submit" class="pure-button pure-button-primary"><%= I18n.translate(locale, "Import") %></button>
|
||||
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Import") %></button>
|
||||
</div>
|
||||
|
||||
<legend><%= I18n.translate(locale, "Export") %></legend>
|
||||
<legend><%= translate(locale, "Export") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/subscription_manager?action_takeout=1"><%= I18n.translate(locale, "Export subscriptions as OPML") %></a>
|
||||
<a href="/subscription_manager?action_takeout=1"><%= translate(locale, "Export subscriptions as OPML") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/subscription_manager?action_takeout=1&format=newpipe"><%= I18n.translate(locale, "Export subscriptions as OPML (for NewPipe & FreeTube)") %></a>
|
||||
<a href="/subscription_manager?action_takeout=1&format=newpipe"><%= translate(locale, "Export subscriptions as OPML (for NewPipe & FreeTube)") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/subscription_manager?action_takeout=1&format=json"><%= I18n.translate(locale, "Export data as JSON") %></a>
|
||||
<a href="/subscription_manager?action_takeout=1&format=json"><%= translate(locale, "Export data as JSON") %></a>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Delete account") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Delete account") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/delete_account?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||
<legend><%= I18n.translate(locale, "Delete account?") %></legend>
|
||||
<legend><%= translate(locale, "Delete account?") %></legend>
|
||||
|
||||
<div class="pure-g">
|
||||
<div class="pure-u-1-2">
|
||||
<button type="submit" name="submit" value="delete_account" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Yes") %>
|
||||
<%= translate(locale, "Yes") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pure-u-1-2">
|
||||
<a class="pure-button" href="<%= URI.encode_www_form(referer) %>">
|
||||
<%= I18n.translate(locale, "No") %>
|
||||
<%= translate(locale, "No") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Log in") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Log in") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g">
|
||||
@@ -13,15 +13,15 @@
|
||||
<% if email %>
|
||||
<input name="email" type="hidden" value="<%= HTML.escape(email) %>">
|
||||
<% else %>
|
||||
<label for="email"><%= I18n.translate(locale, "User ID") %> :</label>
|
||||
<input required class="pure-input-1" name="email" type="text" placeholder="<%= I18n.translate(locale, "User ID") %>">
|
||||
<label for="email"><%= translate(locale, "User ID") %> :</label>
|
||||
<input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>">
|
||||
<% end %>
|
||||
|
||||
<% if password %>
|
||||
<input name="password" type="hidden" value="<%= HTML.escape(password) %>">
|
||||
<% else %>
|
||||
<label for="password"><%= I18n.translate(locale, "Password") %> :</label>
|
||||
<input required class="pure-input-1" name="password" type="password" placeholder="<%= I18n.translate(locale, "Password") %>">
|
||||
<label for="password"><%= translate(locale, "Password") %> :</label>
|
||||
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
|
||||
<% end %>
|
||||
|
||||
<% if captcha %>
|
||||
@@ -30,15 +30,15 @@
|
||||
<% captcha[:tokens].each_with_index do |token, i| %>
|
||||
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
|
||||
<% end %>
|
||||
<label for="answer"><%= I18n.translate(locale, "Time (h:mm:ss):") %></label>
|
||||
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
|
||||
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
|
||||
|
||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Register") %>
|
||||
<%= translate(locale, "Register") %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
|
||||
<%= I18n.translate(locale, "Sign In") %>/<%= I18n.translate(locale, "Register") %>
|
||||
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
|
||||
</button>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Preferences") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Preferences") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<form class="pure-form pure-form-aligned" action="/preferences?referer=<%= URI.encode_www_form(referer) %>" method="post">
|
||||
<fieldset>
|
||||
<legend><%= I18n.translate(locale, "preferences_category_player") %></legend>
|
||||
<legend><%= translate(locale, "preferences_category_player") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="video_loop"><%= I18n.translate(locale, "preferences_video_loop_label") %></label>
|
||||
<label for="video_loop"><%= translate(locale, "preferences_video_loop_label") %></label>
|
||||
<input name="video_loop" id="video_loop" type="checkbox" <% if preferences.video_loop %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="preload"><%= I18n.translate(locale, "preferences_preload_label") %></label>
|
||||
<label for="preload"><%= translate(locale, "preferences_preload_label") %></label>
|
||||
<input name="preload" id="preload" type="checkbox" <% if preferences.preload %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="autoplay"><%= I18n.translate(locale, "preferences_autoplay_label") %></label>
|
||||
<label for="autoplay"><%= translate(locale, "preferences_autoplay_label") %></label>
|
||||
<input name="autoplay" id="autoplay" type="checkbox" <% if preferences.autoplay %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="continue"><%= I18n.translate(locale, "preferences_continue_label") %></label>
|
||||
<label for="continue"><%= translate(locale, "preferences_continue_label") %></label>
|
||||
<input name="continue" id="continue" type="checkbox" <% if preferences.continue %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="continue_autoplay"><%= I18n.translate(locale, "preferences_continue_autoplay_label") %></label>
|
||||
<label for="continue_autoplay"><%= translate(locale, "preferences_continue_autoplay_label") %></label>
|
||||
<input name="continue_autoplay" id="continue_autoplay" type="checkbox" <% if preferences.continue_autoplay %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="local"><%= I18n.translate(locale, "preferences_local_label") %></label>
|
||||
<label for="local"><%= translate(locale, "preferences_local_label") %></label>
|
||||
<input name="local" id="local" type="checkbox" <% if preferences.local && !CONFIG.disabled?("local") %>checked<% end %> <% if CONFIG.disabled?("local") %>disabled<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="listen"><%= I18n.translate(locale, "preferences_listen_label") %></label>
|
||||
<label for="listen"><%= translate(locale, "preferences_listen_label") %></label>
|
||||
<input name="listen" id="listen" type="checkbox" <% if preferences.listen %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="speed"><%= I18n.translate(locale, "preferences_speed_label") %></label>
|
||||
<label for="speed"><%= translate(locale, "preferences_speed_label") %></label>
|
||||
<select name="speed" id="speed">
|
||||
<% {2.0, 1.75, 1.5, 1.25, 1.0, 0.75, 0.5, 0.25}.each do |option| %>
|
||||
<option <% if preferences.speed == option %> selected <% end %>><%= option %></option>
|
||||
@@ -52,11 +52,11 @@
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="quality"><%= I18n.translate(locale, "preferences_quality_label") %></label>
|
||||
<label for="quality"><%= translate(locale, "preferences_quality_label") %></label>
|
||||
<select name="quality" id="quality">
|
||||
<% {"dash", "hd720", "medium", "small"}.each do |option| %>
|
||||
<% if !(option == "dash" && CONFIG.disabled?("dash")) %>
|
||||
<option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= I18n.translate(locale, "preferences_quality_option_" + option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, "preferences_quality_option_" + option) %></option>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</select>
|
||||
@@ -64,74 +64,74 @@
|
||||
|
||||
<% if !CONFIG.disabled?("dash") %>
|
||||
<div class="pure-control-group">
|
||||
<label for="quality_dash"><%= I18n.translate(locale, "preferences_quality_dash_label") %></label>
|
||||
<label for="quality_dash"><%= translate(locale, "preferences_quality_dash_label") %></label>
|
||||
<select name="quality_dash" id="quality_dash">
|
||||
<% {"auto", "best", "4320p", "2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p", "worst"}.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= I18n.translate(locale, "preferences_quality_dash_option_" + option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.quality_dash == option %> selected <% end %>><%= translate(locale, "preferences_quality_dash_option_" + option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="volume"><%= I18n.translate(locale, "preferences_volume_label") %></label>
|
||||
<label for="volume"><%= translate(locale, "preferences_volume_label") %></label>
|
||||
<input name="volume" id="volume" data-onrange="update_volume_value" type="range" min="0" max="100" step="5" value="<%= preferences.volume %>">
|
||||
<span class="pure-form-message-inline" id="volume-value"><%= preferences.volume %></span>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="comments[0]"><%= I18n.translate(locale, "preferences_comments_label") %></label>
|
||||
<label for="comments[0]"><%= translate(locale, "preferences_comments_label") %></label>
|
||||
<% preferences.comments.each_with_index do |comments, index| %>
|
||||
<select name="comments[<%= index %>]" id="comments[<%= index %>]">
|
||||
<% {"", "youtube", "reddit"}.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.comments[index] == option %> selected <% end %>><%= I18n.translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.comments[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="captions[0]"><%= I18n.translate(locale, "preferences_captions_label") %></label>
|
||||
<label for="captions[0]"><%= translate(locale, "preferences_captions_label") %></label>
|
||||
<% preferences.captions.each_with_index do |caption, index| %>
|
||||
<select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]">
|
||||
<% Invidious::Videos::Captions::LANGUAGES.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= I18n.translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="related_videos"><%= I18n.translate(locale, "preferences_related_videos_label") %></label>
|
||||
<label for="related_videos"><%= translate(locale, "preferences_related_videos_label") %></label>
|
||||
<input name="related_videos" id="related_videos" type="checkbox" <% if preferences.related_videos %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="annotations"><%= I18n.translate(locale, "preferences_annotations_label") %></label>
|
||||
<label for="annotations"><%= translate(locale, "preferences_annotations_label") %></label>
|
||||
<input name="annotations" id="annotations" type="checkbox" <% if preferences.annotations %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="extend_desc"><%= I18n.translate(locale, "preferences_extend_desc_label") %></label>
|
||||
<label for="extend_desc"><%= translate(locale, "preferences_extend_desc_label") %></label>
|
||||
<input name="extend_desc" id="extend_desc" type="checkbox" <% if preferences.extend_desc %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="vr_mode"><%= I18n.translate(locale, "preferences_vr_mode_label") %></label>
|
||||
<label for="vr_mode"><%= translate(locale, "preferences_vr_mode_label") %></label>
|
||||
<input name="vr_mode" id="vr_mode" type="checkbox" <% if preferences.vr_mode %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="save_player_pos"><%= I18n.translate(locale, "preferences_save_player_pos_label") %></label>
|
||||
<label for="save_player_pos"><%= translate(locale, "preferences_save_player_pos_label") %></label>
|
||||
<input name="save_player_pos" id="save_player_pos" type="checkbox" <% if preferences.save_player_pos %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<% if user = env.get?("user").try &.as(User) %>
|
||||
<% playlists = Invidious::Database::Playlists.select_user_created_playlists(user.email) %>
|
||||
<div class="pure-control-group">
|
||||
<label for="default_playlist"><%= I18n.translate(locale, "preferences_default_playlist") %></label>
|
||||
<label for="default_playlist"><%= translate(locale, "preferences_default_playlist") %></label>
|
||||
<select name="default_playlist" id="default_playlist">
|
||||
<option value=""><%= I18n.translate(locale, "preferences_default_playlist_none") %></option>
|
||||
<option value=""><%= translate(locale, "preferences_default_playlist_none") %></option>
|
||||
<% playlists.each do |plid, playlist_title| %>
|
||||
<option value="<%= plid %>" <%= "selected" if user.preferences.default_playlist == plid %>><%= HTML.escape(playlist_title) %></option>
|
||||
<% end %>
|
||||
@@ -139,46 +139,46 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<legend><%= I18n.translate(locale, "preferences_category_visual") %></legend>
|
||||
<legend><%= translate(locale, "preferences_category_visual") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="locale"><%= I18n.translate(locale, "preferences_locale_label") %></label>
|
||||
<label for="locale"><%= translate(locale, "preferences_locale_label") %></label>
|
||||
<select name="locale" id="locale">
|
||||
<% I18n::LOCALES_LIST.each do |iso_name, full_name| %>
|
||||
<% LOCALES_LIST.each do |iso_name, full_name| %>
|
||||
<option value="<%= iso_name %>" <% if preferences.locale == iso_name %> selected <% end %>><%= HTML.escape(full_name) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="region"><%= I18n.translate(locale, "preferences_region_label") %></label>
|
||||
<label for="region"><%= translate(locale, "preferences_region_label") %></label>
|
||||
<select name="region" id="region">
|
||||
<% I18n::CONTENT_REGIONS.each do |option| %>
|
||||
<% CONTENT_REGIONS.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.region == option %> selected <% end %>><%= option %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="player_style"><%= I18n.translate(locale, "preferences_player_style_label") %></label>
|
||||
<label for="player_style"><%= translate(locale, "preferences_player_style_label") %></label>
|
||||
<select name="player_style" id="player_style">
|
||||
<% {"invidious", "youtube"}.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.player_style == option %> selected <% end %>><%= I18n.translate(locale, option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.player_style == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="dark_mode"><%= I18n.translate(locale, "preferences_dark_mode_label") %></label>
|
||||
<label for="dark_mode"><%= translate(locale, "preferences_dark_mode_label") %></label>
|
||||
<select name="dark_mode" id="dark_mode">
|
||||
<% {"", "light", "dark"}.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.dark_mode == option %> selected <% end %>><%= I18n.translate(locale, option.blank? ? "auto" : option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.dark_mode == option %> selected <% end %>><%= translate(locale, option.blank? ? "auto" : option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="thin_mode"><%= I18n.translate(locale, "preferences_thin_mode_label") %></label>
|
||||
<label for="thin_mode"><%= translate(locale, "preferences_thin_mode_label") %></label>
|
||||
<input name="thin_mode" id="thin_mode" type="checkbox" <% if preferences.thin_mode %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
@@ -189,187 +189,187 @@
|
||||
<% end %>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="default_home"><%= I18n.translate(locale, "preferences_default_home_label") %></label>
|
||||
<label for="default_home"><%= translate(locale, "preferences_default_home_label") %></label>
|
||||
<select name="default_home" id="default_home">
|
||||
<% feed_options.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.default_home == option %> selected <% end %>><%= I18n.translate(locale, option.blank? ? "Search" : option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.default_home == option %> selected <% end %>><%= translate(locale, option.blank? ? "Search" : option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="feed_menu"><%= I18n.translate(locale, "preferences_feed_menu_label") %></label>
|
||||
<label for="feed_menu"><%= translate(locale, "preferences_feed_menu_label") %></label>
|
||||
<% (feed_options.size - 1).times do |index| %>
|
||||
<select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]">
|
||||
<% feed_options.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.feed_menu[index]? == option %> selected <% end %>><%= I18n.translate(locale, option.blank? ? "Search" : option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option.blank? ? "Search" : option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if env.get? "user" %>
|
||||
<div class="pure-control-group">
|
||||
<label for="show_nick"><%= I18n.translate(locale, "preferences_show_nick_label") %></label>
|
||||
<label for="show_nick"><%= translate(locale, "preferences_show_nick_label") %></label>
|
||||
<input name="show_nick" id="show_nick" type="checkbox" <% if preferences.show_nick %>checked<% end %>>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<legend><%= I18n.translate(locale, "preferences_category_misc") %></legend>
|
||||
<legend><%= translate(locale, "preferences_category_misc") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="automatic_instance_redirect"><%= I18n.translate(locale, "preferences_automatic_instance_redirect_label") %></label>
|
||||
<label for="automatic_instance_redirect"><%= translate(locale, "preferences_automatic_instance_redirect_label") %></label>
|
||||
<input name="automatic_instance_redirect" id="automatic_instance_redirect" type="checkbox" <% if preferences.automatic_instance_redirect %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<% if env.get? "user" %>
|
||||
<legend><%= I18n.translate(locale, "preferences_category_subscription") %></legend>
|
||||
<legend><%= translate(locale, "preferences_category_subscription") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="watch_history"><%= I18n.translate(locale, "preferences_watch_history_label") %></label>
|
||||
<label for="watch_history"><%= translate(locale, "preferences_watch_history_label") %></label>
|
||||
<input name="watch_history" id="watch_history" type="checkbox" <% if preferences.watch_history %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="annotations_subscribed"><%= I18n.translate(locale, "preferences_annotations_subscribed_label") %></label>
|
||||
<label for="annotations_subscribed"><%= translate(locale, "preferences_annotations_subscribed_label") %></label>
|
||||
<input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="max_results"><%= I18n.translate(locale, "preferences_max_results_label") %></label>
|
||||
<label for="max_results"><%= translate(locale, "preferences_max_results_label") %></label>
|
||||
<input name="max_results" id="max_results" type="number" value="<%= preferences.max_results %>">
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="sort"><%= I18n.translate(locale, "preferences_sort_label") %></label>
|
||||
<label for="sort"><%= translate(locale, "preferences_sort_label") %></label>
|
||||
<select name="sort" id="sort">
|
||||
<% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %>
|
||||
<option value="<%= option %>" <% if preferences.sort == option %> selected <% end %>><%= I18n.translate(locale, option) %></option>
|
||||
<option value="<%= option %>" <% if preferences.sort == option %> selected <% end %>><%= translate(locale, option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<% if preferences.unseen_only %>
|
||||
<label for="latest_only"><%= I18n.translate(locale, "Only show latest unwatched video from channel: ") %></label>
|
||||
<label for="latest_only"><%= translate(locale, "Only show latest unwatched video from channel: ") %></label>
|
||||
<% else %>
|
||||
<label for="latest_only"><%= I18n.translate(locale, "Only show latest video from channel: ") %></label>
|
||||
<label for="latest_only"><%= translate(locale, "Only show latest video from channel: ") %></label>
|
||||
<% end %>
|
||||
<input name="latest_only" id="latest_only" type="checkbox" <% if preferences.latest_only %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="unseen_only"><%= I18n.translate(locale, "preferences_unseen_only_label") %></label>
|
||||
<label for="unseen_only"><%= translate(locale, "preferences_unseen_only_label") %></label>
|
||||
<input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<% if CONFIG.enable_user_notifications %>
|
||||
<div class="pure-control-group">
|
||||
<label for="notifications_only"><%= I18n.translate(locale, "preferences_notifications_only_label") %></label>
|
||||
<label for="notifications_only"><%= translate(locale, "preferences_notifications_only_label") %></label>
|
||||
<input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<% # Web notifications are only supported over HTTPS %>
|
||||
<% if Kemal.config.ssl || CONFIG.https_only %>
|
||||
<div class="pure-control-group">
|
||||
<a href="#" data-onclick="notification_requestPermission"><%= I18n.translate(locale, "Enable web notifications") %></a>
|
||||
<a href="#" data-onclick="notification_requestPermission"><%= translate(locale, "Enable web notifications") %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %>
|
||||
<legend><%= I18n.translate(locale, "preferences_category_admin") %></legend>
|
||||
<legend><%= translate(locale, "preferences_category_admin") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="admin_default_home"><%= I18n.translate(locale, "preferences_default_home_label") %></label>
|
||||
<label for="admin_default_home"><%= translate(locale, "preferences_default_home_label") %></label>
|
||||
<select name="admin_default_home" id="admin_default_home">
|
||||
<% feed_options.each do |option| %>
|
||||
<option value="<%= option %>" <% if CONFIG.default_user_preferences.default_home == option %> selected <% end %>><%= I18n.translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<option value="<%= option %>" <% if CONFIG.default_user_preferences.default_home == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="admin_feed_menu"><%= I18n.translate(locale, "preferences_feed_menu_label") %></label>
|
||||
<label for="admin_feed_menu"><%= translate(locale, "preferences_feed_menu_label") %></label>
|
||||
<% (feed_options.size - 1).times do |index| %>
|
||||
<select name="admin_feed_menu[<%= index %>]" id="admin_feed_menu[<%= index %>]">
|
||||
<% feed_options.each do |option| %>
|
||||
<option value="<%= option %>" <% if CONFIG.default_user_preferences.feed_menu[index]? == option %> selected <% end %>><%= I18n.translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<option value="<%= option %>" <% if CONFIG.default_user_preferences.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option.blank? ? "none" : option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="popular_enabled"><%= I18n.translate(locale, "Popular enabled: ") %></label>
|
||||
<label for="popular_enabled"><%= translate(locale, "Popular enabled: ") %></label>
|
||||
<input name="popular_enabled" id="popular_enabled" type="checkbox" <% if CONFIG.popular_enabled %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="captcha_enabled"><%= I18n.translate(locale, "CAPTCHA enabled: ") %></label>
|
||||
<label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled: ") %></label>
|
||||
<input name="captcha_enabled" id="captcha_enabled" type="checkbox" <% if CONFIG.captcha_enabled %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="login_enabled"><%= I18n.translate(locale, "Login enabled: ") %></label>
|
||||
<label for="login_enabled"><%= translate(locale, "Login enabled: ") %></label>
|
||||
<input name="login_enabled" id="login_enabled" type="checkbox" <% if CONFIG.login_enabled %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="registration_enabled"><%= I18n.translate(locale, "Registration enabled: ") %></label>
|
||||
<label for="registration_enabled"><%= translate(locale, "Registration enabled: ") %></label>
|
||||
<input name="registration_enabled" id="registration_enabled" type="checkbox" <% if CONFIG.registration_enabled %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="statistics_enabled"><%= I18n.translate(locale, "Report statistics: ") %></label>
|
||||
<label for="statistics_enabled"><%= translate(locale, "Report statistics: ") %></label>
|
||||
<input name="statistics_enabled" id="statistics_enabled" type="checkbox" <% if CONFIG.statistics_enabled %>checked<% end %>>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="modified_source_code_url"><%= I18n.translate(locale, "adminprefs_modified_source_code_url_label") %></label>
|
||||
<label for="modified_source_code_url"><%= translate(locale, "adminprefs_modified_source_code_url_label") %></label>
|
||||
<input name="modified_source_code_url" id="modified_source_code_url" type="url" value="<%= CONFIG.modified_source_code_url %>">
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if env.get? "user" %>
|
||||
<legend><%= I18n.translate(locale, "preferences_category_data") %></legend>
|
||||
<legend><%= translate(locale, "preferences_category_data") %></legend>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/clear_watch_history?referer=<%= URI.encode_www_form(referer) %>"><%= I18n.translate(locale, "Clear watch history") %></a>
|
||||
<a href="/clear_watch_history?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Clear watch history") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/change_password?referer=<%= URI.encode_www_form(referer) %>"><%= I18n.translate(locale, "Change password") %></a>
|
||||
<a href="/change_password?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Change password") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>"><%= I18n.translate(locale, "Import/export data") %></a>
|
||||
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Import/export data") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/subscription_manager"><%= I18n.translate(locale, "Manage subscriptions") %></a>
|
||||
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/token_manager"><%= I18n.translate(locale, "Manage tokens") %></a>
|
||||
<a href="/token_manager"><%= translate(locale, "Manage tokens") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/feed/playlists"><%= I18n.translate(locale, "View all playlists") %></a>
|
||||
<a href="/feed/playlists"><%= translate(locale, "View all playlists") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/feed/history"><%= I18n.translate(locale, "Watch history") %></a>
|
||||
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<a href="/delete_account?referer=<%= URI.encode_www_form(referer) %>"><%= I18n.translate(locale, "Delete account") %></a>
|
||||
<a href="/delete_account?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Delete account") %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-controls">
|
||||
<button type="submit" class="pure-button pure-button-primary"><%= I18n.translate(locale, "Save preferences") %></button>
|
||||
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Save preferences") %></button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Subscription manager") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Subscription manager") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-3">
|
||||
<h3>
|
||||
<a href="/feed/subscriptions">
|
||||
<%= I18n.translate_count(locale, "generic_subscriptions_count", subscriptions.size, I18n::NumberFormatting::HtmlSpan) %>
|
||||
<%= translate_count(locale, "generic_subscriptions_count", subscriptions.size, NumberFormatting::HtmlSpan) %>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:center">
|
||||
<a href="/feed/history">
|
||||
<%= I18n.translate(locale, "Watch history") %>
|
||||
<%= translate(locale, "Watch history") %>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:right">
|
||||
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>">
|
||||
<%= I18n.translate(locale, "Import/export") %>
|
||||
<%= translate(locale, "Import/export") %>
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
<h3 style="padding-right:0.5em">
|
||||
<form data-onsubmit="return_false" action="/subscription_ajax?action=remove_subscriptions&c=<%= channel.id %>&referer=<%= env.get("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<input style="all:unset" type="submit" data-onclick="remove_subscription" data-ucid="<%= channel.id %>" value="<%= I18n.translate(locale, "unsubscribe") %>">
|
||||
<input style="all:unset" type="submit" data-onclick="remove_subscription" data-ucid="<%= channel.id %>" value="<%= translate(locale, "unsubscribe") %>">
|
||||
</form>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<% content_for "header" do %>
|
||||
<title><%= I18n.translate(locale, "Token manager") %> - Invidious</title>
|
||||
<title><%= translate(locale, "Token manager") %> - Invidious</title>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1-3">
|
||||
<h3>
|
||||
<%= I18n.translate_count(locale, "tokens_count", tokens.size, I18n::NumberFormatting::HtmlSpan) %>
|
||||
<%= translate_count(locale, "tokens_count", tokens.size, NumberFormatting::HtmlSpan) %>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="pure-u-1-3"></div>
|
||||
<div class="pure-u-1-3" style="text-align:right">
|
||||
<h3>
|
||||
<a href="/preferences?referer=<%= URI.encode_www_form(referer) %>"><%= I18n.translate(locale, "Preferences") %></a>
|
||||
<a href="/preferences?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Preferences") %></a>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
@@ -25,13 +25,13 @@
|
||||
</h4>
|
||||
</div>
|
||||
<div class="pure-u-1-5" style="text-align:center">
|
||||
<h4><%= I18n.translate(locale, "`x` ago", recode_date(token[:issued], locale)) %></h4>
|
||||
<h4><%= translate(locale, "`x` ago", recode_date(token[:issued], locale)) %></h4>
|
||||
</div>
|
||||
<div class="pure-u-1-5" style="text-align:right">
|
||||
<h3 style="padding-right:0.5em">
|
||||
<form data-onsubmit="return_false" action="/token_ajax?action=revoke_token&session=<%= token[:session] %>&referer=<%= env.get("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<input style="all:unset" type="submit" data-onclick="revoke_token" data-session="<%= token[:session] %>" value="<%= I18n.translate(locale, "revoke") %>">
|
||||
<input style="all:unset" type="submit" data-onclick="revoke_token" data-session="<%= token[:session] %>" value="<%= translate(locale, "revoke") %>">
|
||||
</form>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -35,11 +35,11 @@ we're going to need to do it here in order to allow for translations.
|
||||
-->
|
||||
<style>
|
||||
#descexpansionbutton ~ label > a::after {
|
||||
content: "<%= I18n.translate(locale, "Show more") %>"
|
||||
content: "<%= translate(locale, "Show more") %>"
|
||||
}
|
||||
|
||||
#descexpansionbutton:checked ~ label > a::after {
|
||||
content: "<%= I18n.translate(locale, "Show less") %>"
|
||||
content: "<%= translate(locale, "Show less") %>"
|
||||
}
|
||||
</style>
|
||||
<% end %>
|
||||
@@ -53,12 +53,12 @@ we're going to need to do it here in order to allow for translations.
|
||||
"length_seconds" => video.length_seconds.to_f,
|
||||
"play_next" => !video.related_videos.empty? && !plid && params.continue,
|
||||
"next_video" => video.related_videos.select { |rv| rv["id"]? }[0]?.try &.["id"],
|
||||
"youtube_comments_text" => HTML.escape(I18n.translate(locale, "View YouTube comments")),
|
||||
"reddit_comments_text" => HTML.escape(I18n.translate(locale, "View Reddit comments")),
|
||||
"reddit_permalink_text" => HTML.escape(I18n.translate(locale, "View more comments on Reddit")),
|
||||
"comments_text" => HTML.escape(I18n.translate(locale, "View `x` comments", "{commentCount}")),
|
||||
"hide_replies_text" => HTML.escape(I18n.translate(locale, "Hide replies")),
|
||||
"show_replies_text" => HTML.escape(I18n.translate(locale, "Show replies")),
|
||||
"youtube_comments_text" => HTML.escape(translate(locale, "View YouTube comments")),
|
||||
"reddit_comments_text" => HTML.escape(translate(locale, "View Reddit comments")),
|
||||
"reddit_permalink_text" => HTML.escape(translate(locale, "View more comments on Reddit")),
|
||||
"comments_text" => HTML.escape(translate(locale, "View `x` comments", "{commentCount}")),
|
||||
"hide_replies_text" => HTML.escape(translate(locale, "Hide replies")),
|
||||
"show_replies_text" => HTML.escape(translate(locale, "Show replies")),
|
||||
"params" => params,
|
||||
"preferences" => preferences,
|
||||
"premiere_timestamp" => video.premiere_timestamp.try &.to_unix,
|
||||
@@ -79,11 +79,11 @@ we're going to need to do it here in order to allow for translations.
|
||||
<h1>
|
||||
<%= title %>
|
||||
<% if params.listen %>
|
||||
<a title="<%=I18n.translate(locale, "Video mode")%>" id="link-iv-listen" data-base-url="/watch?<%= env.params.query %>&listen=0" href="/watch?<%= env.params.query %>&listen=0">
|
||||
<a title="<%=translate(locale, "Video mode")%>" href="/watch?<%= env.params.query %>&listen=0">
|
||||
<i class="icon ion-ios-videocam"></i>
|
||||
</a>
|
||||
<% else %>
|
||||
<a title="<%=I18n.translate(locale, "Audio mode")%>" id="link-iv-listen" data-base-url="/watch?<%= env.params.query %>&listen=1" href="/watch?<%= env.params.query %>&listen=1">
|
||||
<a title="<%=translate(locale, "Audio mode")%>" href="/watch?<%= env.params.query %>&listen=1">
|
||||
<i class="icon ion-md-headset"></i>
|
||||
</a>
|
||||
<% end %>
|
||||
@@ -91,7 +91,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
|
||||
<% if !video.is_listed %>
|
||||
<h3>
|
||||
<i class="icon ion-ios-unlock"></i> <%= I18n.translate(locale, "Unlisted") %>
|
||||
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
|
||||
</h3>
|
||||
<% end %>
|
||||
|
||||
@@ -101,11 +101,11 @@ we're going to need to do it here in order to allow for translations.
|
||||
</h3>
|
||||
<% elsif video.premiere_timestamp.try &.> Time.utc %>
|
||||
<h3>
|
||||
<%= video.premiere_timestamp.try { |t| I18n.translate(locale, "Premieres in `x`", recode_date((t - Time.utc).ago, locale)) } %>
|
||||
<%= video.premiere_timestamp.try { |t| translate(locale, "Premieres in `x`", recode_date((t - Time.utc).ago, locale)) } %>
|
||||
</h3>
|
||||
<% elsif video.live_now %>
|
||||
<h3>
|
||||
<%= video.premiere_timestamp.try { |t| I18n.translate(locale, "videoinfo_started_streaming_x_ago", recode_date((Time.utc - t).ago, locale)) } %>
|
||||
<%= video.premiere_timestamp.try { |t| translate(locale, "videoinfo_started_streaming_x_ago", recode_date((Time.utc - t).ago, locale)) } %>
|
||||
</h3>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -124,13 +124,13 @@ we're going to need to do it here in order to allow for translations.
|
||||
link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param)
|
||||
end
|
||||
-%>
|
||||
<a id="link-yt-watch" rel="noreferrer noopener" data-base-url="<%= link_yt_watch %>" href="<%= link_yt_watch %>"><%= I18n.translate(locale, "videoinfo_watch_on_youTube") %></a>
|
||||
(<a id="link-yt-embed" referrerpolicy="origin-when-cross-origin" rel="noopener" data-base-url="<%= link_yt_embed %>" href="<%= link_yt_embed %>"><%= I18n.translate(locale, "videoinfo_youTube_embed_link") %></a>)
|
||||
<a id="link-yt-watch" rel="noreferrer noopener" data-base-url="<%= link_yt_watch %>" href="<%= link_yt_watch %>"><%= translate(locale, "videoinfo_watch_on_youTube") %></a>
|
||||
(<a id="link-yt-embed" rel="noreferrer noopener" data-base-url="<%= link_yt_embed %>" href="<%= link_yt_embed %>"><%= translate(locale, "videoinfo_youTube_embed_link") %></a>)
|
||||
</span>
|
||||
|
||||
<p id="watch-on-another-invidious-instance">
|
||||
<%- link_iv_other = IV::Frontend::Misc.redirect_url(env) -%>
|
||||
<a id="link-iv-other" data-base-url="<%= link_iv_other %>" href="<%= link_iv_other %>"><%= I18n.translate(locale, "Switch Invidious Instance") %></a>
|
||||
<a id="link-iv-other" data-base-url="<%= link_iv_other %>" href="<%= link_iv_other %>"><%= translate(locale, "Switch Invidious Instance") %></a>
|
||||
</p>
|
||||
|
||||
<p id="embed-link">
|
||||
@@ -141,17 +141,17 @@ we're going to need to do it here in order to allow for translations.
|
||||
link_iv_embed = URI.new(path: "/embed/#{id}")
|
||||
link_iv_embed = IV::HttpServer::Utils.add_params_to_url(link_iv_embed, params_iv_embed)
|
||||
-%>
|
||||
<a id="link-iv-embed" data-base-url="<%= link_iv_embed %>" href="<%= link_iv_embed %>"><%= I18n.translate(locale, "videoinfo_invidious_embed_link") %></a>
|
||||
<a id="link-iv-embed" data-base-url="<%= link_iv_embed %>" href="<%= link_iv_embed %>"><%= translate(locale, "videoinfo_invidious_embed_link") %></a>
|
||||
</p>
|
||||
|
||||
<p id="annotations">
|
||||
<% if params.annotations %>
|
||||
<a href="/watch?<%= env.params.query %>&iv_load_policy=3">
|
||||
<%= I18n.translate(locale, "Hide annotations") %>
|
||||
<%= translate(locale, "Hide annotations") %>
|
||||
</a>
|
||||
<% else %>
|
||||
<a href="/watch?<%= env.params.query %>&iv_load_policy=1">
|
||||
<%=I18n.translate(locale, "Show annotations")%>
|
||||
<%=translate(locale, "Show annotations")%>
|
||||
</a>
|
||||
<% end %>
|
||||
</p>
|
||||
@@ -161,7 +161,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
<% if !playlists.empty? %>
|
||||
<form data-onsubmit="return_false" class="pure-form pure-form-stacked" action="/playlist_ajax?action=add_video" method="post" target="_blank">
|
||||
<div class="pure-control-group">
|
||||
<label for="playlist_id"><%= I18n.translate(locale, "Add to playlist: ") %></label>
|
||||
<label for="playlist_id"><%= translate(locale, "Add to playlist: ") %></label>
|
||||
<select style="width:100%" name="playlist_id" id="playlist_id">
|
||||
<% playlists.each do |plid, playlist_title| %>
|
||||
<option data-plid="<%= plid %>" value="<%= plid %>" <%= "selected" if user.preferences.default_playlist == plid %>><%= HTML.escape(playlist_title) %></option>
|
||||
@@ -172,7 +172,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<input type="hidden" name="video_id" value="<%= video.id %>">
|
||||
<button data-onclick="add_playlist_video" data-id="<%= video.id %>" type="submit" class="pure-button pure-button-primary">
|
||||
<b><%= I18n.translate(locale, "Add to playlist") %></b>
|
||||
<b><%= translate(locale, "Add to playlist") %></b>
|
||||
</button>
|
||||
</form>
|
||||
<script id="playlist_data" type="application/json">
|
||||
@@ -191,7 +191,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
<p id="views"><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></p>
|
||||
<p id="likes"><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></p>
|
||||
<p id="dislikes" style="display: none; visibility: hidden;"></p>
|
||||
<p id="genre"><%= I18n.translate(locale, "Genre: ") %>
|
||||
<p id="genre"><%= translate(locale, "Genre: ") %>
|
||||
<% if !video.genre_url %>
|
||||
<%= video.genre %>
|
||||
<% else %>
|
||||
@@ -200,21 +200,21 @@ we're going to need to do it here in order to allow for translations.
|
||||
</p>
|
||||
<% if video.license %>
|
||||
<% if video.license.empty? %>
|
||||
<p id="license"><%= I18n.translate(locale, "License: ") %><%= I18n.translate(locale, "Standard YouTube license") %></p>
|
||||
<p id="license"><%= translate(locale, "License: ") %><%= translate(locale, "Standard YouTube license") %></p>
|
||||
<% else %>
|
||||
<p id="license"><%= I18n.translate(locale, "License: ") %><%= video.license %></p>
|
||||
<p id="license"><%= translate(locale, "License: ") %><%= video.license %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<p id="family_friendly"><%= I18n.translate(locale, "Family friendly? ") %><%= I18n.translate_bool(locale, video.is_family_friendly) %></p>
|
||||
<p id="family_friendly"><%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %></p>
|
||||
<p id="wilson" style="display: none; visibility: hidden;"></p>
|
||||
<p id="rating" style="display: none; visibility: hidden;"></p>
|
||||
<p id="engagement" style="display: none; visibility: hidden;"></p>
|
||||
<% if video.allowed_regions.size != REGIONS.size %>
|
||||
<p id="allowed_regions">
|
||||
<% if video.allowed_regions.size < REGIONS.size // 2 %>
|
||||
<%= I18n.translate(locale, "Whitelisted regions: ") %><%= video.allowed_regions.join(", ") %>
|
||||
<%= translate(locale, "Whitelisted regions: ") %><%= video.allowed_regions.join(", ") %>
|
||||
<% else %>
|
||||
<%= I18n.translate(locale, "Blacklisted regions: ") %><%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
|
||||
<%= translate(locale, "Blacklisted regions: ") %><%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
@@ -246,9 +246,9 @@ we're going to need to do it here in order to allow for translations.
|
||||
<div class="h-box">
|
||||
<p id="published-date">
|
||||
<% if video.premiere_timestamp.try &.> Time.utc %>
|
||||
<b><%= video.premiere_timestamp.try { |t| I18n.translate(locale, "Premieres `x`", t.to_s("%B %-d, %R UTC")) } %></b>
|
||||
<b><%= video.premiere_timestamp.try { |t| translate(locale, "Premieres `x`", t.to_s("%B %-d, %R UTC")) } %></b>
|
||||
<% else %>
|
||||
<b><%= I18n.translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %></b>
|
||||
<b><%= translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %></b>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
@@ -270,7 +270,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
<input id="music-desc-expansion" type="checkbox"/>
|
||||
<label for="music-desc-expansion">
|
||||
<h3 id="music-description-title">
|
||||
<%= I18n.translate(locale, "Music in this video") %>
|
||||
<%= translate(locale, "Music in this video") %>
|
||||
<span class="icon ion-ios-arrow-up"></span>
|
||||
<span class="icon ion-ios-arrow-down"></span>
|
||||
</h3>
|
||||
@@ -279,9 +279,9 @@ we're going to need to do it here in order to allow for translations.
|
||||
<div id="music-description-box">
|
||||
<% video.music.each do |music| %>
|
||||
<div class="music-item">
|
||||
<p class="music-song"><%= I18n.translate(locale, "Song: ") %><%= music.song %></p>
|
||||
<p class="music-artist"><%= I18n.translate(locale, "Artist: ") %><%= music.artist %></p>
|
||||
<p class="music-album"><%= I18n.translate(locale, "Album: ") %><%= music.album %></p>
|
||||
<p class="music-song"><%= translate(locale, "Song: ") %><%= music.song %></p>
|
||||
<p class="music-artist"><%= translate(locale, "Artist: ") %><%= music.artist %></p>
|
||||
<p class="music-album"><%= translate(locale, "Album: ") %><%= music.album %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -294,7 +294,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
<% else %>
|
||||
<noscript>
|
||||
<a href="/watch?<%= env.params.query %>&nojs=1">
|
||||
<%= I18n.translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
|
||||
<%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
|
||||
</a>
|
||||
</noscript>
|
||||
<% end %>
|
||||
@@ -313,7 +313,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
<% if !video.related_videos.empty? %>
|
||||
<div <% if plid %>style="display:none"<% end %>>
|
||||
<div class="pure-control-group">
|
||||
<label for="continue"><%= I18n.translate(locale, "preferences_continue_label") %></label>
|
||||
<label for="continue"><%= translate(locale, "preferences_continue_label") %></label>
|
||||
<input name="continue" id="continue" type="checkbox" <% if params.continue %>checked<% end %>>
|
||||
</div>
|
||||
<hr>
|
||||
@@ -356,7 +356,7 @@ we're going to need to do it here in order to allow for translations.
|
||||
<div class="pure-u-10-24" style="text-align:right">
|
||||
<b class="width:100%"><%=
|
||||
views = short_text_to_number(rv["short_view_count"]? || "0")
|
||||
I18n.translate_count(locale, "generic_views_count", views, I18n::NumberFormatting::Short)
|
||||
translate_count(locale, "generic_views_count", views, NumberFormatting::Short)
|
||||
%></b>
|
||||
</div>
|
||||
</h5>
|
||||
|
||||
Reference in New Issue
Block a user