revert module encapsulation, remove Log.forf macro

This commit is contained in:
Fijxu 2025-08-31 16:30:29 -04:00
parent d0cd940893
commit b6f164bef3
No known key found for this signature in database
GPG Key ID: 32C1DDF333EDA6A4
70 changed files with 1116 additions and 1133 deletions

View File

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

View File

@ -4,7 +4,7 @@ Spectator.describe Invidious::Hashtag do
it "parses richItemRenderer containers (test 1)" do
# Enable mock
test_content = load_mock("hashtag/martingarrix_page1")
videos, _ = YoutubeJSONParser.extract_items(test_content)
videos, _ = extract_items(test_content)
expect(typeof(videos)).to eq(Array(SearchItem))
expect(videos.size).to eq(60)
@ -57,7 +57,7 @@ Spectator.describe Invidious::Hashtag do
it "parses richItemRenderer containers (test 2)" do
# Enable mock
test_content = load_mock("hashtag/martingarrix_page2")
videos, _ = YoutubeJSONParser.extract_items(test_content)
videos, _ = extract_items(test_content)
expect(typeof(videos)).to eq(Array(SearchItem))
expect(videos.size).to eq(60)

View File

@ -7,7 +7,7 @@ Spectator.describe "parse_video_info" do
_next = load_mock("video/regular_mrbeast.next")
raw_data = _player.merge!(_next)
info = Parser.parse_video_info("2isYuQZMbdU", raw_data)
info = parse_video_info("2isYuQZMbdU", raw_data)
# Some basic verifications
expect(typeof(info)).to eq(Hash(String, JSON::Any))
@ -89,7 +89,7 @@ Spectator.describe "parse_video_info" do
_next = load_mock("video/regular_no-description.next")
raw_data = _player.merge!(_next)
info = Parser.parse_video_info("iuevw6218F0", raw_data)
info = parse_video_info("iuevw6218F0", raw_data)
# Some basic verifications
expect(typeof(info)).to eq(Hash(String, JSON::Any))

View File

@ -7,7 +7,7 @@ Spectator.describe "parse_video_info" do
_next = load_mock("video/scheduled_live_PBD-Podcast.next")
raw_data = _player.merge!(_next)
info = Parser.parse_video_info("N-yVic7BbY0", raw_data)
info = parse_video_info("N-yVic7BbY0", raw_data)
# Some basic verifications
expect(typeof(info)).to eq(Hash(String, JSON::Any))

View File

@ -200,7 +200,7 @@ def fetch_related_channels(about_channel : AboutChannel, continuation : String?
initial_data = YoutubeAPI.browse(continuation)
end
items, continuation = YoutubeJSONParser.extract_items(initial_data)
items, continuation = extract_items(initial_data)
return items.select(SearchChannel), continuation
end

View File

@ -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
@ -156,8 +156,8 @@ def get_channel(id) : InvidiousChannel
end
def fetch_channel(ucid, pull_all_videos : Bool)
::Log.forf.debug { "#{ucid}" }
::Log.forf.trace { "#{ucid} : pull_all_videos = #{pull_all_videos}" }
Log.debug { "fetch_channel: #{ucid}" }
Log.trace { "fetch_channel: #{ucid} : pull_all_videos = #{pull_all_videos}" }
namespaces = {
"yt" => "http://www.youtube.com/xml/schemas/2015",
@ -165,9 +165,9 @@ def fetch_channel(ucid, pull_all_videos : Bool)
"default" => "http://www.w3.org/2005/Atom",
}
::Log.forf.trace { "#{ucid} : Downloading RSS feed" }
Log.trace { "fetch_channel: #{ucid} : Downloading RSS feed" }
rss = YT_POOL.client &.get("/feeds/videos.xml?channel_id=#{ucid}").body
::Log.forf.trace { "#{ucid} : Parsing RSS feed" }
Log.trace { "fetch_channel: #{ucid} : Parsing RSS feed" }
rss = XML.parse(rss)
author = rss.xpath_node("//default:feed/default:title", namespaces)
@ -184,7 +184,7 @@ def fetch_channel(ucid, pull_all_videos : Bool)
auto_generated = true
end
::Log.forf.trace { "#{ucid} : author = #{author}, auto_generated = #{auto_generated}" }
Log.trace { "fetch_channel: #{ucid} : author = #{author}, auto_generated = #{auto_generated}" }
channel = InvidiousChannel.new({
id: ucid,
@ -194,10 +194,10 @@ def fetch_channel(ucid, pull_all_videos : Bool)
subscribed: nil,
})
::Log.forf.trace { "#{ucid} : Downloading channel videos page" }
Log.trace { "fetch_channel: #{ucid} : Downloading channel videos page" }
videos, continuation = IV::Channel::Tabs.get_videos(channel)
::Log.forf.trace { "#{ucid} : Extracting videos from channel RSS feed" }
Log.trace { "fetch_channel: #{ucid} : Extracting videos from channel RSS feed" }
rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry|
video_id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content
title = entry.xpath_node("default:title", namespaces).not_nil!.content
@ -241,17 +241,17 @@ def fetch_channel(ucid, pull_all_videos : Bool)
views: views,
})
::Log.forf.trace { "#{ucid} : video #{video_id} : Updating or inserting video" }
Log.trace { "fetch_channel: #{ucid} : video #{video_id} : Updating or inserting video" }
# We don't include the 'premiere_timestamp' here because channel pages don't include them,
# meaning the above timestamp is always null
was_insert = Invidious::Database::ChannelVideos.insert(video)
if was_insert
::Log.forf.trace { "#{ucid} : video #{video_id} : Inserted, updating subscriptions" }
Log.trace { "fetch_channel: #{ucid} : video #{video_id} : Inserted, updating subscriptions" }
NOTIFICATION_CHANNEL.send(VideoNotification.from_video(video))
else
::Log.forf.trace { "#{ucid} : video #{video_id} : Updated" }
Log.trace { "fetch_channel: #{ucid} : video #{video_id} : Updated" }
end
end

View File

@ -7,7 +7,7 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
initial_data = YoutubeAPI.browse(ucid, params: "EgVwb3N0c_IGBAoCSgA%3D")
items = [] of JSON::Any
YoutubeJSONParser.extract_items(initial_data) do |item|
extract_items(initial_data) do |item|
items << item
end
else
@ -49,7 +49,7 @@ def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
initial_data = YoutubeAPI.browse("FEpost_detail", params: params)
items = [] of JSON::Any
YoutubeJSONParser.extract_items(initial_data) do |item|
extract_items(initial_data) do |item|
items << item
end
@ -131,7 +131,7 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_sing
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
@ -142,7 +142,7 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_sing
json.field "attachment" do
case attachment.as_h
when .has_key?("videoRenderer")
YoutubeJSONParser.parse_item(attachment)
parse_item(attachment)
.as(SearchVideo)
.to_json(locale, json)
when .has_key?("backstageImageRenderer")
@ -230,7 +230,7 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_sing
end
end
when .has_key?("playlistRenderer")
YoutubeJSONParser.parse_item(attachment)
parse_item(attachment)
.as(SearchPlaylist)
.to_json(locale, json)
when .has_key?("quizRenderer")

View File

@ -24,7 +24,7 @@ def fetch_channel_playlists(ucid, author, continuation, sort_by)
initial_data = YoutubeAPI.browse(ucid, params: params || "")
end
return YoutubeJSONParser.extract_items(initial_data, author, ucid)
return extract_items(initial_data, author, ucid)
end
def fetch_channel_podcasts(ucid, author, continuation)
@ -33,7 +33,7 @@ def fetch_channel_podcasts(ucid, author, continuation)
else
initial_data = YoutubeAPI.browse(ucid, params: "Eghwb2RjYXN0c_IGBQoDugEA")
end
return YoutubeJSONParser.extract_items(initial_data, author, ucid)
return extract_items(initial_data, author, ucid)
end
def fetch_channel_releases(ucid, author, continuation)
@ -42,7 +42,7 @@ def fetch_channel_releases(ucid, author, continuation)
else
initial_data = YoutubeAPI.browse(ucid, params: "EghyZWxlYXNlc_IGBQoDsgEA")
end
return YoutubeJSONParser.extract_items(initial_data, author, ucid)
return extract_items(initial_data, author, ucid)
end
def fetch_channel_courses(ucid, author, continuation)
@ -51,5 +51,5 @@ def fetch_channel_courses(ucid, author, continuation)
else
initial_data = YoutubeAPI.browse(ucid, params: "Egdjb3Vyc2Vz8gYFCgPCAQA%3D")
end
return YoutubeJSONParser.extract_items(initial_data, author, ucid)
return extract_items(initial_data, author, ucid)
end

View File

@ -29,7 +29,7 @@ module Invidious::Channel::Tabs
continuation ||= make_initial_videos_ctoken(ucid, sort_by)
initial_data = YoutubeAPI.browse(continuation: continuation)
return YoutubeJSONParser.extract_items(initial_data, author, ucid)
return extract_items(initial_data, author, ucid)
end
def get_60_videos(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")
@ -59,7 +59,7 @@ module Invidious::Channel::Tabs
continuation ||= make_initial_shorts_ctoken(channel.ucid, sort_by)
initial_data = YoutubeAPI.browse(continuation: continuation)
return YoutubeJSONParser.extract_items(initial_data, channel.author, channel.ucid)
return extract_items(initial_data, channel.author, channel.ucid)
end
# -------------------
@ -70,7 +70,7 @@ module Invidious::Channel::Tabs
continuation ||= make_initial_livestreams_ctoken(channel.ucid, sort_by)
initial_data = YoutubeAPI.browse(continuation: continuation)
return YoutubeJSONParser.extract_items(initial_data, channel.author, channel.ucid)
return extract_items(initial_data, channel.author, channel.ucid)
end
def get_60_livestreams(channel : AboutChannel, *, continuation : String? = nil, sort_by = "newest")

View File

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

View File

@ -34,7 +34,7 @@ module Invidious::Database
return # TODO
if !PG_DB.query_one?("SELECT true FROM pg_type WHERE typname = $1", enum_name, as: Bool)
::Log.forf.info { "CREATE TYPE #{enum_name}" }
Log.info { "check_enum: CREATE TYPE #{enum_name}" }
PG_DB.using_connection do |conn|
conn.as(PG::Connection).exec_all(File.read("config/sql/#{enum_name}.sql"))
@ -47,7 +47,7 @@ module Invidious::Database
begin
PG_DB.exec("SELECT * FROM #{table_name} LIMIT 0")
rescue ex
::Log.forf.info { "CREATE TABLE #{table_name}" }
Log.info { "check_table: CREATE TABLE #{table_name}" }
PG_DB.using_connection do |conn|
conn.as(PG::Connection).exec_all(File.read("config/sql/#{table_name}.sql"))
@ -67,7 +67,7 @@ module Invidious::Database
if name != column_array[i]?
if !column_array[i]?
new_column = column_types.select(&.starts_with?(name))[0]
::Log.forf.info { "ALTER TABLE #{table_name} ADD COLUMN #{new_column}" }
Log.info { "check_table: ALTER TABLE #{table_name} ADD COLUMN #{new_column}" }
PG_DB.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}")
next
end
@ -85,29 +85,29 @@ module Invidious::Database
# There's a column we didn't expect
if !new_column
::Log.forf.info { "ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]}" }
Log.info { "check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]}" }
PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
column_array = get_column_array(PG_DB, table_name)
next
end
::Log.forf.info { "ALTER TABLE #{table_name} ADD COLUMN #{new_column}" }
Log.info { "check_table: ALTER TABLE #{table_name} ADD COLUMN #{new_column}" }
PG_DB.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}")
::Log.forf.info { "UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}" }
Log.info { "check_table: UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}" }
PG_DB.exec("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}")
::Log.forf.info { "ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE" }
Log.info { "check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE" }
PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
::Log.forf.info { "ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}" }
Log.info { "check_table: ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}" }
PG_DB.exec("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}")
column_array = get_column_array(PG_DB, table_name)
end
else
::Log.forf.info { "ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE" }
Log.info { "check_table: ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE" }
PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE")
end
end
@ -117,7 +117,7 @@ module Invidious::Database
column_array.each do |column|
if !struct_array.includes? column
::Log.forf.info { "ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE" }
Log.info { "check_table: ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE" }
PG_DB.exec("ALTER TABLE #{table_name} DROP COLUMN #{column} CASCADE")
end
end

View File

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

View File

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

View File

@ -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
&nbsp;
<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>

View File

@ -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 << "&nbsp;&nbsp;"
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 << "&nbsp;&nbsp;"
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 << "&nbsp;&nbsp;"
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 << "&nbsp;&nbsp;"
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 << "&nbsp;&nbsp;"
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 << "&nbsp;&nbsp;"
str << %(<i class="icon ion-ios-arrow-forward"></i>)
end

View File

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

View File

@ -20,7 +20,7 @@ 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
url = "/download"
@ -45,7 +45,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"
@ -94,7 +94,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
@ -104,7 +104,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"

View File

@ -8,7 +8,7 @@ module Invidious::Hashtag
client_config = YoutubeAPI::ClientConfig.new(region: region)
response = YoutubeAPI.browse(continuation: ctoken, client_config: client_config)
items, _ = YoutubeJSONParser.extract_items(response)
items, _ = extract_items(response)
return items
end

View File

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

View File

@ -1,18 +1,16 @@
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 = {
# 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
@ -62,11 +60,11 @@ module I18n
"vi" => "Tiếng Việt", # Vietnamese
"zh-CN" => "汉语", # Chinese (Simplified)
"zh-TW" => "漢語", # Chinese (Traditional)
}
}
LOCALES = load_all_locales()
LOCALES = load_all_locales()
CONTENT_REGIONS = {
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",
@ -76,17 +74,17 @@ module I18n
"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
# 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
end
def load_all_locales
def load_all_locales
locales = {} of String => Hash(String, JSON::Any)
LOCALES_LIST.each_key do |name|
@ -94,9 +92,9 @@ module I18n
end
return locales
end
end
def translate(locale : String?, key : String, text : String | Hash(String, String) | Nil = nil) : String
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)
@ -144,9 +142,9 @@ module I18n
end
return translation
end
end
def translate_count(locale : String, key : String, count : Int, format = NumberFormatting::None) : String
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)
@ -180,23 +178,22 @@ module I18n
end
return translation.gsub("{{count}}", count_txt)
end
end
def translate_bool(locale : String?, translation : Bool)
def translate_bool(locale : String?, translation : Bool)
case translation
when true
return translate(locale, "Yes")
when false
return translate(locale, "No")
end
end
end
def locale_is_rtl?(locale : String?)
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
end

View File

@ -70,9 +70,3 @@ macro haltf(env, status_code = 200, response = "")
{{env}}.response.close
return
end
class Log
macro forf
Log.for({{@def.name.stringify}})
end
end

View File

@ -115,9 +115,9 @@ struct SearchVideo
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?
@ -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

View File

@ -144,19 +144,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

View File

@ -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
@ -269,7 +269,7 @@ module Invidious::JSONify::APIv1
json.field "viewCount", rv["view_count"]?.try &.empty? ? nil : rv["view_count"].to_i64
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

View File

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

View File

@ -352,7 +352,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"]?

View File

@ -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)
@ -72,7 +72,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)

View File

@ -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
@ -258,7 +258,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)

View File

@ -110,7 +110,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

View File

@ -9,7 +9,7 @@ module Invidious::Search
client_config = YoutubeAPI::ClientConfig.new(region: query.region)
initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config)
items, _ = YoutubeJSONParser.extract_items(initial_data)
items, _ = extract_items(initial_data)
return items.reject!(Category)
end
@ -31,7 +31,7 @@ module Invidious::Search
continuation = produce_channel_search_continuation(ucid, query.text, query.page)
response_json = YoutubeAPI.browse(continuation)
items, _ = YoutubeJSONParser.extract_items(response_json, "", ucid)
items, _ = extract_items(response_json, "", ucid)
return items.reject!(Category)
end

View File

@ -18,7 +18,7 @@ def fetch_trending(trending_type, region, locale)
client_config = YoutubeAPI::ClientConfig.new(region: region)
initial_data = YoutubeAPI.browse("FEtrending", params: params, client_config: client_config)
items, _ = YoutubeJSONParser.extract_items(initial_data)
items, _ = extract_items(initial_data)
extracted = [] of SearchItem

View File

@ -324,7 +324,7 @@ rescue DB::Error
end
def fetch_video(id, region)
info = Parser.extract_video_info(video_id: id)
info = extract_video_info(video_id: id)
if reason = info["reason"]?
if reason == "Video unavailable"

View File

@ -6,11 +6,7 @@ require "json"
#
# TODO: "compactRadioRenderer" (Mix) and
# TODO: Use a proper struct/class instead of a hacky JSON object
module Parser
extend self
Log = ::Log.for(self)
private def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
private def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
return nil if !related["videoId"]?
# The compact renderer has video length in seconds, where the end
@ -38,7 +34,7 @@ module Parser
HelperExtractors.get_short_view_count(r).to_s
end
::Log.forf.trace { "Found \"watchNextEndScreenRenderer\" container" }
Log.trace { "parse_related_video: Found \"watchNextEndScreenRenderer\" container" }
if published_time_text = related["publishedTimeText"]?
decoded_time = decode_date(published_time_text["simpleText"].to_s)
@ -60,9 +56,9 @@ module Parser
"author_verified" => JSON::Any.new(author_verified),
"published" => JSON::Any.new(published || ""),
}
end
end
def extract_video_info(video_id : String)
def extract_video_info(video_id : String)
# Init client config for the API
client_config = YoutubeAPI::ClientConfig.new
@ -129,6 +125,8 @@ module Parser
player_response["streamingData"] = JSON::Any.new(streaming_data)
break
end
rescue InfoException
next Log.warn { "Failed to fetch streams with #{player_fallback}" }
end
end
@ -155,23 +153,23 @@ module Parser
end
format["url"] = JSON::Any.new(convert_url(format))
end
end
params["streamingData"] = streaming_data
end
end
# Data structure version, for cache control
params["version"] = JSON::Any.new(Video::SCHEMA_VERSION.to_i64)
return params
end
end
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
::Log.forf.debug { "[#{id}] Using #{client_config.client_type} client." }
def try_fetch_streaming_data(id : String, client_config : YoutubeAPI::ClientConfig) : Hash(String, JSON::Any)?
Log.debug { "try_fetch_streaming_data: [#{id}] Using #{client_config.client_type} client." }
response = YoutubeAPI.player(video_id: id, params: "2AMB", client_config: client_config)
playability_status = response["playabilityStatus"]["status"]
::Log.forf.debug { "[#{id}] Got playabilityStatus == #{playability_status}." }
Log.debug { "try_fetch_streaming_data: [#{id}] Got playabilityStatus == #{playability_status}." }
if id != response.dig?("videoDetails", "videoId")
# YouTube may return a different video player response than expected.
@ -184,9 +182,9 @@ module Parser
else
return nil
end
end
end
def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any)) : Hash(String, JSON::Any)
def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any)) : Hash(String, JSON::Any)
# Top level elements
main_results = player_response.dig?("contents", "twoColumnWatchNextResults")
@ -268,7 +266,7 @@ module Parser
# Related videos
::Log.forf.debug { "parsing related videos..." }
Log.debug { "parse_video_info: parsing related videos..." }
related = [] of JSON::Any
@ -335,8 +333,8 @@ module Parser
.try &.dig?("accessibility", "accessibilityData", "label")
likes = likes_txt.as_s.gsub(/\D/, "").to_i64? if likes_txt
::Log.forf.trace { "Found \"likes\" button. Button text is \"#{likes_txt}\"" }
::Log.forf.debug { "Likes count is #{likes}" } if likes
Log.trace { "parse_video_info: Found \"likes\" button. Button text is \"#{likes_txt}\"" }
Log.debug { "parse_video_info: Likes count is #{likes}" } if likes
end
end
@ -476,15 +474,15 @@ module Parser
}
return params
end
end
private def convert_url(fmt)
private def convert_url(fmt)
if cfr = fmt["signatureCipher"]?.try { |json| HTTP::Params.parse(json.as_s) }
sp = cfr["sp"]
url = URI.parse(cfr["url"])
params = url.query_params
::Log.forf.debug { "Decoding '#{cfr}'" }
Log.debug { "convert_url: Decoding '#{cfr}'" }
unsig = DECRYPT_FUNCTION.try &.decrypt_signature(cfr["s"])
params[sp] = unsig if unsig
@ -501,12 +499,11 @@ module Parser
end
url.query_params = params
::Log.forf.trace { "new url is '#{url}'" }
Log.trace { "convert_url: new url is '#{url}'" }
return url.to_s
rescue ex
::Log.forf.debug { "Error when parsing video URL" }
::Log.forf.trace { ex.inspect_with_backtrace }
rescue ex
Log.debug { "convert_url: Error when parsing video URL" }
Log.trace { ex.inspect_with_backtrace }
return ""
end
end

View File

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

View File

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

View File

@ -24,7 +24,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>&nbsp;<%= I18n.translate(locale, "generic_button_rss") %>
<i class="icon ion-logo-rss"></i>&nbsp;<%= translate(locale, "generic_button_rss") %>
</a>
</div>
</div>
@ -37,10 +37,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) %>
@ -50,9 +50,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 %>

View File

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

View File

@ -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>&nbsp;<%= I18n.translate(locale, "LIVE") %></p>
<p class="length" dir="auto"><i class="icon ion-ios-play-circle"></i>&nbsp;<%= 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>

View File

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

View File

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

View File

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

View File

@ -1,18 +1,18 @@
<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 %>

View File

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

View File

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

View File

@ -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>&nbsp;<%= I18n.translate(locale, "generic_button_cancel") %>
<i class="icon ion-md-close"></i>&nbsp;<%= 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>&nbsp;<%= I18n.translate(locale, "generic_button_save") %>
<i class="icon ion-md-save"></i>&nbsp;<%= 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>&nbsp;<%= I18n.translate(locale, "generic_button_delete") %>
<i class="icon ion-md-trash"></i>&nbsp;<%= 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>

View File

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

View File

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

View File

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

View File

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

View File

@ -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 @@
<% {"Default", "Music", "Gaming", "Movies"}.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 %>&region=<%= region %>">
<%= I18n.translate(locale, option) %>
<%= translate(locale, option) %>
</a>
<% end %>
</div>

View File

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

View File

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

View File

@ -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>&nbsp;<%= I18n.translate(locale, "playlist_button_add_items") %>
<i class="icon ion-md-add"></i>&nbsp;<%= 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>&nbsp;<%= I18n.translate(locale, "generic_button_edit") %>
<i class="icon ion-md-create"></i>&nbsp;<%= 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>&nbsp;<%= I18n.translate(locale, "generic_button_delete") %>
<i class="icon ion-md-trash"></i>&nbsp;<%= 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>&nbsp;<%= I18n.translate(locale, "Subscribe") %>
<i class="icon ion-md-add"></i>&nbsp;<%= 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>&nbsp;<%= I18n.translate(locale, "Unsubscribe") %>
<i class="icon ion-md-trash"></i>&nbsp;<%= 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>&nbsp;<%= I18n.translate(locale, "generic_button_rss") %>
<i class="icon ion-logo-rss"></i>&nbsp;<%= 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>

View File

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

View File

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

View File

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

View File

@ -42,7 +42,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 %>
@ -51,7 +51,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>
@ -61,7 +61,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>
@ -74,13 +74,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 %>
@ -89,14 +89,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 %>
@ -118,38 +118,38 @@
<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>&nbsp;/
<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>&nbsp;/
<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: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %></span>
<span><%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> @ <%= CURRENT_BRANCH %></span>
</div>
</div>
</footer>
@ -163,8 +163,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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,108 +64,108 @@
<% 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>
<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>
@ -176,187 +176,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>

View File

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

View File

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

View File

@ -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,
@ -78,11 +78,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")%>" 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")%>" 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 %>
@ -90,7 +90,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 %>
@ -100,11 +100,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>
@ -123,13 +123,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" rel="noreferrer 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">
@ -140,17 +140,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>
@ -160,7 +160,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 %>"><%= HTML.escape(playlist_title) %></option>
@ -171,7 +171,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">
@ -190,7 +190,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 %>
@ -199,21 +199,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 %>
@ -245,9 +245,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>
@ -269,7 +269,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>
@ -278,9 +278,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>
@ -293,7 +293,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 %>
@ -312,7 +312,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.
<b class="width:100%"><%=
views = rv["view_count"]?.try &.to_i?
views ||= rv["view_count_short"]?.try { |x| short_text_to_number(x) }
I18n.translate_count(locale, "generic_views_count", views || 0, I18n::NumberFormatting::Short)
translate_count(locale, "generic_views_count", views || 0, NumberFormatting::Short)
%></b>
</div>
</h5>

View File

@ -144,7 +144,7 @@ def get_ytimg_pool(subdomain)
if pool = YTIMG_POOLS[subdomain]?
return pool
else
Log.forf.info { "Creating a new HTTP pool for \"https://#{subdomain}.ytimg.com\"" }
Log.info { "get_ytimg_pool: Creating a new HTTP pool for \"https://#{subdomain}.ytimg.com\"" }
pool = YoutubeConnectionPool.new(URI.parse("https://#{subdomain}.ytimg.com"), capacity: CONFIG.pool_size)
YTIMG_POOLS[subdomain] = pool

View File

@ -452,7 +452,7 @@ private module Parsers
end
content_container["items"]?.try &.as_a.each do |item|
result = YoutubeJSONParser.parse_item(item, author_fallback.name, author_fallback.id)
result = parse_item(item, author_fallback.name, author_fallback.id)
contents << result if result.is_a?(SearchItem)
end
@ -1017,13 +1017,9 @@ module HelperExtractors
end
end
module YoutubeJSONParser
extend self
Log = ::Log.for(self)
# Parses an item from Youtube's JSON response into a more usable structure.
# The end result can either be a SearchVideo, SearchPlaylist or SearchChannel.
def parse_item(item : JSON::Any, author_fallback : String? = "", author_id_fallback : String? = "")
# Parses an item from Youtube's JSON response into a more usable structure.
# The end result can either be a SearchVideo, SearchPlaylist or SearchChannel.
def parse_item(item : JSON::Any, author_fallback : String? = "", author_id_fallback : String? = "")
# We "allow" nil values but secretly use empty strings instead. This is to save us the
# hassle of modifying every author_fallback and author_id_fallback arg usage
# which is more often than not nil.
@ -1033,23 +1029,23 @@ module YoutubeJSONParser
# Each parser automatically validates the data given to see if the data is
# applicable to itself. If not nil is returned and the next parser is attempted.
ITEM_PARSERS.each do |parser|
Log.trace { "Attempting to parse item using \"#{parser.parser_name}\" (cycling...)" }
Log.trace { "parse_item: Attempting to parse item using \"#{parser.parser_name}\" (cycling...)" }
if result = parser.process(item, author_fallback)
Log.debug { "Successfully parsed via #{parser.parser_name}" }
Log.debug { "parse_item: Successfully parsed via #{parser.parser_name}" }
return result
else
Log.trace { "Parser \"#{parser.parser_name}\" does not apply. Cycling to the next one..." }
end
Log.trace { "parse_item: Parser \"#{parser.parser_name}\" does not apply. Cycling to the next one..." }
end
end
end
# Parses multiple items from YouTube's initial JSON response into a more usable structure.
# The end result is an array of SearchItem.
#
# This function yields the container so that items can be parsed separately.
#
def extract_items(initial_data : InitialData, &)
# Parses multiple items from YouTube's initial JSON response into a more usable structure.
# The end result is an array of SearchItem.
#
# This function yields the container so that items can be parsed separately.
#
def extract_items(initial_data : InitialData, &)
if unpackaged_data = initial_data["contents"]?.try &.as_h
elsif unpackaged_data = initial_data["response"]?.try &.as_h
elsif unpackaged_data = initial_data.dig?("onResponseReceivedActions", 1).try &.as_h
@ -1060,24 +1056,24 @@ module YoutubeJSONParser
# This is identical to the parser cycling of parse_item().
ITEM_CONTAINER_EXTRACTOR.each do |extractor|
Log.trace { "Attempting to extract item container using \"#{extractor.extractor_name}\" (cycling...)" }
Log.trace { "extract_items: Attempting to extract item container using \"#{extractor.extractor_name}\" (cycling...)" }
if container = extractor.process(unpackaged_data)
Log.debug { "Successfully unpacked container with \"#{extractor.extractor_name}\"" }
Log.debug { "extract_items: Successfully unpacked container with \"#{extractor.extractor_name}\"" }
# Extract items in container
container.each { |item| yield item }
else
Log.trace { "Extractor \"#{extractor.extractor_name}\" does not apply. Cycling to the next one..." }
end
Log.trace { "extract_items: Extractor \"#{extractor.extractor_name}\" does not apply. Cycling to the next one..." }
end
end
end
# Wrapper using the block function above
def extract_items(
# Wrapper using the block function above
def extract_items(
initial_data : InitialData,
author_fallback : String? = nil,
author_id_fallback : String? = nil,
) : {Array(SearchItem), String?}
) : {Array(SearchItem), String?}
items = [] of SearchItem
continuation = nil
@ -1091,5 +1087,4 @@ module YoutubeJSONParser
end
return items, continuation
end
end

View File

@ -696,8 +696,8 @@ module YoutubeAPI
}
# Logging
::Log.forf.debug { "Using endpoint: \"#{endpoint}\"" }
::Log.forf.trace { "POST data: #{data}" }
Log.debug { "Invidious companion: Using endpoint: \"#{endpoint}\"" }
Log.trace { "Invidious companion: POST data: #{data}" }
# Send the POST request