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> "<tr>
<td><a href=\\"/#{path}\\">#{file_name}</a></td> <td><a href=\\"/#{path}\\">#{file_name}</a></td>
<td><a href=\\"#{licence_link}\\">#{licence_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>" </tr>"
HTML HTML

View File

@ -4,7 +4,7 @@ Spectator.describe Invidious::Hashtag do
it "parses richItemRenderer containers (test 1)" do it "parses richItemRenderer containers (test 1)" do
# Enable mock # Enable mock
test_content = load_mock("hashtag/martingarrix_page1") 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(typeof(videos)).to eq(Array(SearchItem))
expect(videos.size).to eq(60) expect(videos.size).to eq(60)
@ -57,7 +57,7 @@ Spectator.describe Invidious::Hashtag do
it "parses richItemRenderer containers (test 2)" do it "parses richItemRenderer containers (test 2)" do
# Enable mock # Enable mock
test_content = load_mock("hashtag/martingarrix_page2") 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(typeof(videos)).to eq(Array(SearchItem))
expect(videos.size).to eq(60) 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") _next = load_mock("video/regular_mrbeast.next")
raw_data = _player.merge!(_next) raw_data = _player.merge!(_next)
info = Parser.parse_video_info("2isYuQZMbdU", raw_data) info = parse_video_info("2isYuQZMbdU", raw_data)
# Some basic verifications # Some basic verifications
expect(typeof(info)).to eq(Hash(String, JSON::Any)) 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") _next = load_mock("video/regular_no-description.next")
raw_data = _player.merge!(_next) raw_data = _player.merge!(_next)
info = Parser.parse_video_info("iuevw6218F0", raw_data) info = parse_video_info("iuevw6218F0", raw_data)
# Some basic verifications # Some basic verifications
expect(typeof(info)).to eq(Hash(String, JSON::Any)) 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") _next = load_mock("video/scheduled_live_PBD-Podcast.next")
raw_data = _player.merge!(_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 # Some basic verifications
expect(typeof(info)).to eq(Hash(String, JSON::Any)) 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) initial_data = YoutubeAPI.browse(continuation)
end end
items, continuation = YoutubeJSONParser.extract_items(initial_data) items, continuation = extract_items(initial_data)
return items.select(SearchChannel), continuation return items.select(SearchChannel), continuation
end end

View File

@ -38,7 +38,7 @@ struct ChannelVideo
json.field "authorId", self.ucid json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}" json.field "authorUrl", "/channel/#{self.ucid}"
json.field "published", self.published.to_unix 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 json.field "viewCount", self.views
end end
@ -156,8 +156,8 @@ def get_channel(id) : InvidiousChannel
end end
def fetch_channel(ucid, pull_all_videos : Bool) def fetch_channel(ucid, pull_all_videos : Bool)
::Log.forf.debug { "#{ucid}" } Log.debug { "fetch_channel: #{ucid}" }
::Log.forf.trace { "#{ucid} : pull_all_videos = #{pull_all_videos}" } Log.trace { "fetch_channel: #{ucid} : pull_all_videos = #{pull_all_videos}" }
namespaces = { namespaces = {
"yt" => "http://www.youtube.com/xml/schemas/2015", "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", "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 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) rss = XML.parse(rss)
author = rss.xpath_node("//default:feed/default:title", namespaces) author = rss.xpath_node("//default:feed/default:title", namespaces)
@ -184,7 +184,7 @@ def fetch_channel(ucid, pull_all_videos : Bool)
auto_generated = true auto_generated = true
end 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({ channel = InvidiousChannel.new({
id: ucid, id: ucid,
@ -194,10 +194,10 @@ def fetch_channel(ucid, pull_all_videos : Bool)
subscribed: nil, 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) 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| rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry|
video_id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content video_id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content
title = entry.xpath_node("default:title", 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, 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, # We don't include the 'premiere_timestamp' here because channel pages don't include them,
# meaning the above timestamp is always null # meaning the above timestamp is always null
was_insert = Invidious::Database::ChannelVideos.insert(video) was_insert = Invidious::Database::ChannelVideos.insert(video)
if was_insert 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)) NOTIFICATION_CHANNEL.send(VideoNotification.from_video(video))
else else
::Log.forf.trace { "#{ucid} : video #{video_id} : Updated" } Log.trace { "fetch_channel: #{ucid} : video #{video_id} : Updated" }
end end
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") initial_data = YoutubeAPI.browse(ucid, params: "EgVwb3N0c_IGBAoCSgA%3D")
items = [] of JSON::Any items = [] of JSON::Any
YoutubeJSONParser.extract_items(initial_data) do |item| extract_items(initial_data) do |item|
items << item items << item
end end
else 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) initial_data = YoutubeAPI.browse("FEpost_detail", params: params)
items = [] of JSON::Any items = [] of JSON::Any
YoutubeJSONParser.extract_items(initial_data) do |item| extract_items(initial_data) do |item|
items << item items << item
end end
@ -131,7 +131,7 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_sing
json.field "contentHtml", content_html json.field "contentHtml", content_html
json.field "published", published.to_unix 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 "likeCount", like_count
json.field "replyCount", reply_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 json.field "attachment" do
case attachment.as_h case attachment.as_h
when .has_key?("videoRenderer") when .has_key?("videoRenderer")
YoutubeJSONParser.parse_item(attachment) parse_item(attachment)
.as(SearchVideo) .as(SearchVideo)
.to_json(locale, json) .to_json(locale, json)
when .has_key?("backstageImageRenderer") when .has_key?("backstageImageRenderer")
@ -230,7 +230,7 @@ def extract_channel_community(items, *, ucid, locale, format, thin_mode, is_sing
end end
end end
when .has_key?("playlistRenderer") when .has_key?("playlistRenderer")
YoutubeJSONParser.parse_item(attachment) parse_item(attachment)
.as(SearchPlaylist) .as(SearchPlaylist)
.to_json(locale, json) .to_json(locale, json)
when .has_key?("quizRenderer") 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 || "") initial_data = YoutubeAPI.browse(ucid, params: params || "")
end end
return YoutubeJSONParser.extract_items(initial_data, author, ucid) return extract_items(initial_data, author, ucid)
end end
def fetch_channel_podcasts(ucid, author, continuation) def fetch_channel_podcasts(ucid, author, continuation)
@ -33,7 +33,7 @@ def fetch_channel_podcasts(ucid, author, continuation)
else else
initial_data = YoutubeAPI.browse(ucid, params: "Eghwb2RjYXN0c_IGBQoDugEA") initial_data = YoutubeAPI.browse(ucid, params: "Eghwb2RjYXN0c_IGBQoDugEA")
end end
return YoutubeJSONParser.extract_items(initial_data, author, ucid) return extract_items(initial_data, author, ucid)
end end
def fetch_channel_releases(ucid, author, continuation) def fetch_channel_releases(ucid, author, continuation)
@ -42,7 +42,7 @@ def fetch_channel_releases(ucid, author, continuation)
else else
initial_data = YoutubeAPI.browse(ucid, params: "EghyZWxlYXNlc_IGBQoDsgEA") initial_data = YoutubeAPI.browse(ucid, params: "EghyZWxlYXNlc_IGBQoDsgEA")
end end
return YoutubeJSONParser.extract_items(initial_data, author, ucid) return extract_items(initial_data, author, ucid)
end end
def fetch_channel_courses(ucid, author, continuation) def fetch_channel_courses(ucid, author, continuation)
@ -51,5 +51,5 @@ def fetch_channel_courses(ucid, author, continuation)
else else
initial_data = YoutubeAPI.browse(ucid, params: "Egdjb3Vyc2Vz8gYFCgPCAQA%3D") initial_data = YoutubeAPI.browse(ucid, params: "Egdjb3Vyc2Vz8gYFCgPCAQA%3D")
end end
return YoutubeJSONParser.extract_items(initial_data, author, ucid) return extract_items(initial_data, author, ucid)
end end

View File

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

View File

@ -268,7 +268,7 @@ module Invidious::Comments
end end
json.field "published", published.to_unix 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 end
if node_replies && !response["commentRepliesContinuation"]? if node_replies && !response["commentRepliesContinuation"]?

View File

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

View File

@ -28,14 +28,14 @@ module Invidious::Frontend::ChannelPage
if tab == selected_tab if tab == selected_tab
str << "\t<b>" str << "\t<b>"
str << I18n.translate(locale, "channel_tab_#{tab_name}_label") str << translate(locale, "channel_tab_#{tab_name}_label")
str << "</b>\n" str << "</b>\n"
else else
# Video tab doesn't have the last path component # Video tab doesn't have the last path component
url = tab.videos? ? base_url : "#{base_url}/#{tab_name}" url = tab.videos? ? base_url : "#{base_url}/#{tab_name}"
str << %(\t<a href=") << url << %(">) 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" str << "</a>\n"
end end

View File

@ -32,9 +32,9 @@ module Invidious::Frontend::Comments
<p> <p>
<a href="javascript:void(0)" data-onclick="toggle_parent">[ ]</a> <a href="javascript:void(0)" data-onclick="toggle_parent">[ ]</a>
<b><a href="https://www.reddit.com/user/#{child.author}">#{child.author}</a></b> <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)} #{translate_count(locale, "comments_points_count", child.score, 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> <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="#{I18n.translate(locale, "permalink")}">#{I18n.translate(locale, "permalink")}</a> <a href="https://www.reddit.com#{child.permalink}" title="#{translate(locale, "permalink")}">#{translate(locale, "permalink")}</a>
</p> </p>
<div> <div>
#{body_html} #{body_html}

View File

@ -6,10 +6,10 @@ module Invidious::Frontend::Comments
root = comments["comments"].as_a root = comments["comments"].as_a
root.each do |child| root.each do |child|
if child["replies"]? if child["replies"]?
replies_count_text = I18n.translate_count(locale, replies_count_text = translate_count(locale,
"comments_view_x_replies", "comments_view_x_replies",
child["replies"]["replyCount"].as_i64 || 0, child["replies"]["replyCount"].as_i64 || 0,
I18n::NumberFormatting::Separator NumberFormatting::Separator
) )
replies_html = <<-END_HTML replies_html = <<-END_HTML
@ -25,10 +25,10 @@ module Invidious::Frontend::Comments
END_HTML END_HTML
elsif comments["authorId"]? && !comments["singlePost"]? elsif comments["authorId"]? && !comments["singlePost"]?
# for posts we should display a link to the post # 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", "comments_view_x_replies",
child["replyCount"].as_i64 || 0, child["replyCount"].as_i64 || 0,
I18n::NumberFormatting::Separator NumberFormatting::Separator
) )
replies_html = <<-END_HTML replies_html = <<-END_HTML
@ -61,7 +61,7 @@ module Invidious::Frontend::Comments
sponsor_icon = String.build do |str| sponsor_icon = String.build do |str|
str << %(<img alt="" ) str << %(<img alt="" )
str << %(src="/ggpht) << URI.parse(child["sponsorIconUrl"].as_s).request_target << "\" " 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" />) str << %(width="16" height="16" />)
end end
end end
@ -110,14 +110,14 @@ module Invidious::Frontend::Comments
when "multiImage" when "multiImage"
html << <<-END_HTML html << <<-END_HTML
<section class="carousel"> <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"> <div class="slides">
END_HTML END_HTML
image_array = attachment["images"].as_a image_array = attachment["images"].as_a
image_array.each_index do |i| image_array.each_index do |i|
html << <<-END_HTML 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="" /> <img loading="lazy" src="/ggpht#{URI.parse(image_array[i][1]["url"].as_s).request_target}" alt="" />
</div> </div>
END_HTML END_HTML
@ -129,7 +129,7 @@ module Invidious::Frontend::Comments
END_HTML END_HTML
attachment["images"].as_a.each_index do |i| attachment["images"].as_a.each_index do |i|
html << <<-END_HTML 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 end
html << <<-END_HTML html << <<-END_HTML
@ -143,18 +143,18 @@ module Invidious::Frontend::Comments
html << <<-END_HTML html << <<-END_HTML
<p> <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 END_HTML
if comments["videoId"]? if comments["videoId"]?
html << <<-END_HTML 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 END_HTML
elsif comments["authorId"]? elsif comments["authorId"]?
html << <<-END_HTML 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_HTML
end end
@ -172,7 +172,7 @@ module Invidious::Frontend::Comments
html << <<-END_HTML html << <<-END_HTML
&nbsp; &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"> <span class="creator-heart">
<img loading="lazy" class="creator-heart-background-hearted" src="#{creator_thumbnail}" alt="" /> <img loading="lazy" class="creator-heart-background-hearted" src="#{creator_thumbnail}" alt="" />
<span class="creator-heart-small-hearted"> <span class="creator-heart-small-hearted">
@ -197,7 +197,7 @@ module Invidious::Frontend::Comments
<div class="pure-u-1"> <div class="pure-u-1">
<p> <p>
<a href="javascript:void(0)" data-continuation="#{comments["continuation"]}" <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> </p>
</div> </div>
</div> </div>

View File

@ -6,16 +6,16 @@ module Invidious::Frontend::Pagination
private def first_page(str : String::Builder, locale : String?, url : String) private def first_page(str : String::Builder, locale : String?, url : String)
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">) 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) # Inverted arrow ("first" points to the right)
str << I18n.translate(locale, "First page") str << translate(locale, "First page")
str << "&nbsp;&nbsp;" str << "&nbsp;&nbsp;"
str << %(<i class="icon ion-ios-arrow-forward"></i>) str << %(<i class="icon ion-ios-arrow-forward"></i>)
else else
# Regular arrow ("first" points to the left) # Regular arrow ("first" points to the left)
str << %(<i class="icon ion-ios-arrow-back"></i>) str << %(<i class="icon ion-ios-arrow-back"></i>)
str << "&nbsp;&nbsp;" str << "&nbsp;&nbsp;"
str << I18n.translate(locale, "First page") str << translate(locale, "First page")
end end
str << "</a>" str << "</a>"
@ -25,16 +25,16 @@ module Invidious::Frontend::Pagination
# Link # Link
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">) 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) # Inverted arrow ("previous" points to the right)
str << I18n.translate(locale, "Previous page") str << translate(locale, "Previous page")
str << "&nbsp;&nbsp;" str << "&nbsp;&nbsp;"
str << %(<i class="icon ion-ios-arrow-forward"></i>) str << %(<i class="icon ion-ios-arrow-forward"></i>)
else else
# Regular arrow ("previous" points to the left) # Regular arrow ("previous" points to the left)
str << %(<i class="icon ion-ios-arrow-back"></i>) str << %(<i class="icon ion-ios-arrow-back"></i>)
str << "&nbsp;&nbsp;" str << "&nbsp;&nbsp;"
str << I18n.translate(locale, "Previous page") str << translate(locale, "Previous page")
end end
str << "</a>" str << "</a>"
@ -44,14 +44,14 @@ module Invidious::Frontend::Pagination
# Link # Link
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">) 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) # Inverted arrow ("next" points to the left)
str << %(<i class="icon ion-ios-arrow-back"></i>) str << %(<i class="icon ion-ios-arrow-back"></i>)
str << "&nbsp;&nbsp;" str << "&nbsp;&nbsp;"
str << I18n.translate(locale, "Next page") str << translate(locale, "Next page")
else else
# Regular arrow ("next" points to the right) # Regular arrow ("next" points to the right)
str << I18n.translate(locale, "Next page") str << translate(locale, "Next page")
str << "&nbsp;&nbsp;" str << "&nbsp;&nbsp;"
str << %(<i class="icon ion-ios-arrow-forward"></i>) str << %(<i class="icon ion-ios-arrow-forward"></i>)
end end

View File

@ -6,7 +6,7 @@ module Invidious::Frontend::SearchFilters
return String.build(8000) do |str| return String.build(8000) do |str|
str << "<div id='filters'>\n" str << "<div id='filters'>\n"
str << "\t<details id='filters-collapse'>" 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" 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 << "\t\t\t<div id='filters-apply'>"
str << "<button type='submit' class=\"pure-button pure-button-primary\">" 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 << "</button></div>\n"
str << "\t\t</form></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<div class=\"filter-column\"><fieldset>\n"
str << "\t\t\t\t\t<legend><div class=\"filter-name underlined\">" 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 << "</div></legend>\n"
str << "\t\t\t\t\t<div class=\"filter-options\">\n" str << "\t\t\t\t\t<div class=\"filter-options\">\n"
@ -62,7 +62,7 @@ module Invidious::Frontend::SearchFilters
str << '>' str << '>'
str << "<label for='filter-date-{{date}}'>" 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" str << "</label></div>\n"
{% end %} {% end %}
end end
@ -78,7 +78,7 @@ module Invidious::Frontend::SearchFilters
str << '>' str << '>'
str << "<label for='filter-type-{{type}}'>" 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" str << "</label></div>\n"
{% end %} {% end %}
end end
@ -94,7 +94,7 @@ module Invidious::Frontend::SearchFilters
str << '>' str << '>'
str << "<label for='filter-duration-{{duration}}'>" 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" str << "</label></div>\n"
{% end %} {% end %}
end end
@ -111,7 +111,7 @@ module Invidious::Frontend::SearchFilters
str << '>' str << '>'
str << "<label for='filter-feature-{{feature}}'>" 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" str << "</label></div>\n"
{% end %} {% end %}
{% end %} {% end %}
@ -128,7 +128,7 @@ module Invidious::Frontend::SearchFilters
str << '>' str << '>'
str << "<label for='filter-sort-{{sort}}'>" 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" str << "</label></div>\n"
{% end %} {% end %}
end end

View File

@ -20,7 +20,7 @@ module Invidious::Frontend::WatchPage
def download_widget(locale : String, video : Video, video_assets : VideoAssets) : String def download_widget(locale : String, video : Video, video_assets : VideoAssets) : String
if CONFIG.disabled?("downloads") 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 end
url = "/download" url = "/download"
@ -45,7 +45,7 @@ module Invidious::Frontend::WatchPage
str << "\t<div class=\"pure-control-group\">\n" str << "\t<div class=\"pure-control-group\">\n"
str << "\t\t<label for='download_widget'>" str << "\t\t<label for='download_widget'>"
str << I18n.translate(locale, "Download as: ") str << translate(locale, "Download as: ")
str << "</label>\n" str << "</label>\n"
str << "\t\t<select name='download_widget' id='download_widget'>\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 value = {"label": caption.name, "ext": "#{caption.language_code}.vtt"}.to_json
str << "\t\t\t<option value='" << value << "'>" 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" str << "</option>\n"
end end
@ -104,7 +104,7 @@ module Invidious::Frontend::WatchPage
str << "\t</div>\n" str << "\t</div>\n"
str << "\t<button type=\"submit\" class=\"pure-button pure-button-primary\">\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 << "\t</button>\n"
str << "</form>\n" str << "</form>\n"

View File

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

View File

@ -63,19 +63,19 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
error_message = <<-END_HTML error_message = <<-END_HTML
<div class="error_message"> <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/> <br/><br/>
<p><b>#{I18n.translate(locale, "crash_page_before_reporting")}</b></p> <p><b>#{translate(locale, "crash_page_before_reporting")}</b></p>
<ul> <ul>
<li>#{I18n.translate(locale, "crash_page_refresh", env.request.resource)}</li> <li>#{translate(locale, "crash_page_refresh", env.request.resource)}</li>
<li>#{I18n.translate(locale, "crash_page_switch_instance", url_switch)}</li> <li>#{translate(locale, "crash_page_switch_instance", url_switch)}</li>
<li>#{I18n.translate(locale, "crash_page_read_the_faq", url_faq)}</li> <li>#{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_search_issue", url_search_issues)}</li>
</ul> </ul>
<br/> <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 --> <!-- TODO: Add a "copy to clipboard" button -->
<pre class="error-issue-template">#{issue_template}</pre> <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 locale = env.get("preferences").as(Preferences).locale
error_message = I18n.translate(locale, message) error_message = translate(locale, message)
next_steps = error_redirect_helper(env) next_steps = error_redirect_helper(env)
return templated "error" 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") || if request_path.starts_with?("/search") || request_path.starts_with?("/watch") ||
request_path.starts_with?("/channel") || request_path.starts_with?("/playlist?list=PL") request_path.starts_with?("/channel") || request_path.starts_with?("/playlist?list=PL")
next_steps_text = I18n.translate(locale, "next_steps_error_message") next_steps_text = translate(locale, "next_steps_error_message")
refresh = I18n.translate(locale, "next_steps_error_message_refresh") refresh = translate(locale, "next_steps_error_message_refresh")
go_to_youtube = I18n.translate(locale, "next_steps_error_message_go_to_youtube") go_to_youtube = translate(locale, "next_steps_error_message_go_to_youtube")
switch_instance = I18n.translate(locale, "Switch Invidious Instance") switch_instance = translate(locale, "Switch Invidious Instance")
return <<-END_HTML return <<-END_HTML
<p style="margin-bottom: 4px;">#{next_steps_text}</p> <p style="margin-bottom: 4px;">#{next_steps_text}</p>

View File

@ -1,18 +1,16 @@
module I18n # Languages requiring a better level of translation (at least 20%)
extend self # to be added to the list below:
# Languages requiring a better level of translation (at least 20%) #
# to be added to the list below: # "af" => "", # Afrikaans
# # "az" => "", # Azerbaijani
# "af" => "", # Afrikaans # "be" => "", # Belarusian
# "az" => "", # Azerbaijani # "bn_BD" => "", # Bengali (Bangladesh)
# "be" => "", # Belarusian # "ia" => "", # Interlingua
# "bn_BD" => "", # Bengali (Bangladesh) # "or" => "", # Odia
# "ia" => "", # Interlingua # "tk" => "", # Turkmen
# "or" => "", # Odia # "tok => "", # Toki Pona
# "tk" => "", # Turkmen #
# "tok => "", # Toki Pona LOCALES_LIST = {
#
LOCALES_LIST = {
"ar" => "العربية", # Arabic "ar" => "العربية", # Arabic
"bg" => "български", # Bulgarian "bg" => "български", # Bulgarian
"bn" => "বাংলা", # Bengali "bn" => "বাংলা", # Bengali
@ -62,11 +60,11 @@ module I18n
"vi" => "Tiếng Việt", # Vietnamese "vi" => "Tiếng Việt", # Vietnamese
"zh-CN" => "汉语", # Chinese (Simplified) "zh-CN" => "汉语", # Chinese (Simplified)
"zh-TW" => "漢語", # Chinese (Traditional) "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", "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", "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", "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", "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", "SN", "SV", "TH", "TN", "TR", "TW", "TZ", "UA", "UG", "US", "UY", "VE", "VN",
"YE", "ZA", "ZW", "YE", "ZA", "ZW",
} }
# Enum for the different types of number formats # Enum for the different types of number formats
enum NumberFormatting enum NumberFormatting
None # Print the number as-is None # Print the number as-is
Separator # Use a separator for thousands Separator # Use a separator for thousands
Short # Use short notation (k/M/B) Short # Use short notation (k/M/B)
HtmlSpan # Surround with <span id="count"></span> 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 = {} of String => Hash(String, JSON::Any)
LOCALES_LIST.each_key do |name| LOCALES_LIST.each_key do |name|
@ -94,9 +92,9 @@ module I18n
end end
return locales 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 # 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. # that key as the text, so this is more or less transparent to the user.
if !LOCALES["en-US"].has_key?(key) if !LOCALES["en-US"].has_key?(key)
@ -144,9 +142,9 @@ module I18n
end end
return translation 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 # Fallback on english if locale doesn't exist
locale = "en-US" if !LOCALES.has_key?(locale) locale = "en-US" if !LOCALES.has_key?(locale)
@ -180,23 +178,22 @@ module I18n
end end
return translation.gsub("{{count}}", count_txt) return translation.gsub("{{count}}", count_txt)
end end
def translate_bool(locale : String?, translation : Bool) def translate_bool(locale : String?, translation : Bool)
case translation case translation
when true when true
return translate(locale, "Yes") return translate(locale, "Yes")
when false when false
return translate(locale, "No") return translate(locale, "No")
end end
end end
def locale_is_rtl?(locale : String?) def locale_is_rtl?(locale : String?)
# Fallback to en-US # Fallback to en-US
return false if locale.nil? return false if locale.nil?
# Arabic, Persian, Hebrew # Arabic, Persian, Hebrew
# See https://en.wikipedia.org/wiki/Right-to-left_script#List_of_RTL_scripts # See https://en.wikipedia.org/wiki/Right-to-left_script#List_of_RTL_scripts
return {"ar", "fa", "he"}.includes? locale return {"ar", "fa", "he"}.includes? locale
end
end end

View File

@ -70,9 +70,3 @@ macro haltf(env, status_code = 200, response = "")
{{env}}.response.close {{env}}.response.close
return return
end 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 "descriptionHtml", self.description_html
json.field "viewCount", self.views 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 "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 "lengthSeconds", self.length_seconds
json.field "liveNow", self.badges.live_now? json.field "liveNow", self.badges.live_now?
json.field "premium", self.badges.premium? json.field "premium", self.badges.premium?
@ -327,8 +327,8 @@ struct ProblematicTimelineItem
xml.element("content", type: "xhtml") do xml.element("content", type: "xhtml") do
xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do xml.element("div", xmlns: "http://www.w3.org/1999/xhtml") do
xml.element("div") do xml.element("div") do
xml.element("h4") { I18n.translate(locale, "timeline_parse_error_placeholder_heading") } xml.element("h4") { translate(locale, "timeline_parse_error_placeholder_heading") }
xml.element("p") { I18n.translate(locale, "timeline_parse_error_placeholder_message") } xml.element("p") { translate(locale, "timeline_parse_error_placeholder_message") }
end end
xml.element("pre") do xml.element("pre") do

View File

@ -144,19 +144,19 @@ def recode_date(time : Time, locale)
span = Time.utc - time span = Time.utc - time
if span.total_days > 365.0 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 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 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 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 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 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 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
end end

View File

@ -22,7 +22,7 @@ module Invidious::JSONify::APIv1
json.field "description", video.description json.field "description", video.description
json.field "descriptionHtml", video.description_html json.field "descriptionHtml", video.description_html
json.field "published", video.published.to_unix 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 "keywords", video.keywords
json.field "viewCount", video.views 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 "viewCount", rv["view_count"]?.try &.empty? ? nil : rv["view_count"].to_i64
json.field "published", rv["published"]? json.field "published", rv["published"]?
if rv["published"]?.try &.presence 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 else
json.field "publishedText", "" json.field "publishedText", ""
end end

View File

@ -7,7 +7,7 @@ module Invidious::Routes::BeforeAll
preferences = Preferences.from_json(URI.decode_www_form(prefs_cookie.value)) preferences = Preferences.from_json(URI.decode_www_form(prefs_cookie.value))
else else
if language_header = env.request.headers["Accept-Language"]? 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 preferences.locale = language.header
end end
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}" : ""}") 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"] ucid = resolved_url["endpoint"]["browseEndpoint"]["browseId"]
rescue ex : InfoException | KeyError 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 end
selected_tab = env.params.url["tab"]? selected_tab = env.params.url["tab"]?

View File

@ -10,7 +10,7 @@ module Invidious::Routes::Embed
videos = get_playlist_videos(playlist, offset: offset) videos = get_playlist_videos(playlist, offset: offset)
if videos.empty? if videos.empty?
url = "/playlist?list=#{plid}" 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 end
first_playlist_video = videos[0].as(PlaylistVideo) first_playlist_video = videos[0].as(PlaylistVideo)
@ -72,7 +72,7 @@ module Invidious::Routes::Embed
videos = get_playlist_videos(playlist, offset: offset) videos = get_playlist_videos(playlist, offset: offset)
if videos.empty? if videos.empty?
url = "/playlist?list=#{plid}" 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 end
first_playlist_video = videos[0].as(PlaylistVideo) first_playlist_video = videos[0].as(PlaylistVideo)

View File

@ -37,7 +37,7 @@ module Invidious::Routes::Feeds
if CONFIG.popular_enabled if CONFIG.popular_enabled
templated "feeds/popular" templated "feeds/popular"
else 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" templated "message"
end end
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": "text/html", rel: "alternate", href: "#{HOST_URL}/feed/subscriptions")
xml.element("link", "type": "application/atom+xml", rel: "self", xml.element("link", "type": "application/atom+xml", rel: "self",
href: "#{HOST_URL}#{env.request.resource}") 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| (notifications + videos).each do |video|
video.to_xml(locale, params, xml) video.to_xml(locale, params, xml)

View File

@ -110,7 +110,7 @@ module Invidious::Routes::Login
user, sid = create_user(sid, email, password) user, sid = create_user(sid, email, password)
if language_header = env.request.headers["Accept-Language"]? 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 user.preferences.locale = language.header
end end
end end

View File

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

View File

@ -18,7 +18,7 @@ def fetch_trending(trending_type, region, locale)
client_config = YoutubeAPI::ClientConfig.new(region: region) client_config = YoutubeAPI::ClientConfig.new(region: region)
initial_data = YoutubeAPI.browse("FEtrending", params: params, client_config: client_config) 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 extracted = [] of SearchItem

View File

@ -324,7 +324,7 @@ rescue DB::Error
end end
def fetch_video(id, region) 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 = info["reason"]?
if reason == "Video unavailable" if reason == "Video unavailable"

View File

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

View File

@ -8,12 +8,12 @@
<div class="pure-u-1 pure-u-lg-3-5"> <div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/add_playlist_items" method="get"> <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> <fieldset>
<input class="pure-input-1" type="search" name="q" <input class="pure-input-1" type="search" name="q"
<% if query %>value="<%= HTML.escape(query.text) %>"<% end %> <% 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 %>"> <input type="hidden" name="list" value="<%= plid %>">
</fieldset> </fieldset>
</form> </form>

View File

@ -35,10 +35,10 @@
<%= <%=
{ {
"ucid" => ucid, "ucid" => ucid,
"youtube_comments_text" => HTML.escape(I18n.translate(locale, "View YouTube comments")), "youtube_comments_text" => HTML.escape(translate(locale, "View YouTube comments")),
"comments_text" => HTML.escape(I18n.translate(locale, "View `x` comments", "{commentCount}")), "comments_text" => HTML.escape(translate(locale, "View `x` comments", "{commentCount}")),
"hide_replies_text" => HTML.escape(I18n.translate(locale, "Hide replies")), "hide_replies_text" => HTML.escape(translate(locale, "Hide replies")),
"show_replies_text" => HTML.escape(I18n.translate(locale, "Show replies")), "show_replies_text" => HTML.escape(translate(locale, "Show replies")),
"preferences" => env.get("preferences").as(Preferences) "preferences" => env.get("preferences").as(Preferences)
}.to_pretty_json }.to_pretty_json
%> %>

View File

@ -24,7 +24,7 @@
<div class="pure-u"> <div class="pure-u">
<a class="pure-button pure-button-secondary" dir="auto" href="/feed/channel/<%= ucid %>"> <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> </a>
</div> </div>
</div> </div>
@ -37,10 +37,10 @@
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<div class="pure-u-1 pure-md-1-3"> <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>
<div class="pure-u-1 pure-md-1-3"> <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> </div>
<%= Invidious::Frontend::ChannelPage.generate_tabs_links(locale, channel, selected_tab) %> <%= Invidious::Frontend::ChannelPage.generate_tabs_links(locale, channel, selected_tab) %>
@ -50,9 +50,9 @@
<% sort_options.each do |sort| %> <% sort_options.each do |sort| %>
<div class="pure-u-1 pure-md-1-3"> <div class="pure-u-1 pure-md-1-3">
<% if sort_by == sort %> <% if sort_by == sort %>
<b><%= I18n.translate(locale, sort) %></b> <b><%= translate(locale, sort) %></b>
<% else %> <% 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 %> <% end %>
</div> </div>
<% end %> <% end %>

View File

@ -5,7 +5,7 @@
<% end %> <% end %>
<% feed_menu.each do |feed| %> <% feed_menu.each do |feed| %>
<a href="/feed/<%= feed.downcase %>" class="feed-menu-item pure-menu-heading"> <a href="/feed/<%= feed.downcase %>" class="feed-menu-item pure-menu-heading">
<%= I18n.translate(locale, feed) %> <%= translate(locale, feed) %>
</a> </a>
<% end %> <% end %>
</div> </div>

View File

@ -27,8 +27,8 @@
</div> </div>
<% if !item.channel_handle.nil? %><p class="channel-name" dir="auto"><%= item.channel_handle %></p><% end %> <% 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> <p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, 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 %> <% 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> <h5><%= item.description_html %></h5>
<% when SearchHashtag %> <% when SearchHashtag %>
<% if !thin_mode %> <% if !thin_mode %>
@ -45,13 +45,13 @@
<div class="video-card-row"> <div class="video-card-row">
<%- if item.video_count != 0 -%> <%- 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 -%> <%- end -%>
</div> </div>
<div class="video-card-row"> <div class="video-card-row">
<%- if item.channel_count != 0 -%> <%- 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 -%> <%- end -%>
</div> </div>
<% when SearchPlaylist, InvidiousPlaylist %> <% when SearchPlaylist, InvidiousPlaylist %>
@ -73,7 +73,7 @@
<%- end -%> <%- end -%>
<div class="bottom-right-overlay"> <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>
</div> </div>
@ -101,11 +101,11 @@
<div class="error-card"> <div class="error-card">
<div class="explanation"> <div class="explanation">
<i class="icon ion-ios-alert"></i> <i class="icon ion-ios-alert"></i>
<h4><%=I18n.translate(locale, "timeline_parse_error_placeholder_heading")%></h4> <h4><%=translate(locale, "timeline_parse_error_placeholder_heading")%></h4>
<p><%=I18n.translate(locale, "timeline_parse_error_placeholder_message")%></p> <p><%=translate(locale, "timeline_parse_error_placeholder_message")%></p>
</div> </div>
<details> <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> <pre class="error-issue-template"><%=get_issue_template(env, item.parse_exception)[1]%></pre>
</details> </details>
</div> </div>
@ -168,7 +168,7 @@
<div class="bottom-right-overlay"> <div class="bottom-right-overlay">
<%- if item.responds_to?(:live_now) && item.live_now -%> <%- 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 -%> <%- elsif item.length_seconds != 0 -%>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p> <p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<%- end -%> <%- end -%>
@ -200,15 +200,15 @@
<div class="video-card-row flexible"> <div class="video-card-row flexible">
<div class="flex-left"> <div class="flex-left">
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %> <% 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 %> <% 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 %> <% end %>
</div> </div>
<% if item.responds_to?(:views) && item.views %> <% if item.responds_to?(:views) && item.views %>
<div class="flex-right"> <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> </div>
<% end %> <% end %>
</div> </div>

View File

@ -11,9 +11,9 @@
<script id="pagination-data" type="application/json"> <script id="pagination-data" type="application/json">
<%= <%=
{ {
"next_page" => I18n.translate(locale, "Next page"), "next_page" => translate(locale, "Next page"),
"prev_page" => I18n.translate(locale, "Previous page"), "prev_page" => translate(locale, "Previous page"),
"is_rtl" => I18n.locale_is_rtl?(locale) "is_rtl" => locale_is_rtl?(locale)
}.to_pretty_json }.to_pretty_json
%> %>
</script> </script>

View File

@ -2,11 +2,11 @@
<fieldset> <fieldset>
<input type="search" id="searchbox" autocorrect="off" <input type="search" id="searchbox" autocorrect="off"
autocapitalize="none" spellcheck="false" <% if autofocus %>autofocus<% end %> autocapitalize="none" spellcheck="false" <% if autofocus %>autofocus<% end %>
name="q" placeholder="<%= I18n.translate(locale, "search") %>" name="q" placeholder="<%= translate(locale, "search") %>"
title="<%= I18n.translate(locale, "search") %>" title="<%= translate(locale, "search") %>"
value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } %>"> value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } %>">
</fieldset> </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> <i class="icon ion-ios-search"></i>
</button> </button>
</form> </form>

View File

@ -3,14 +3,14 @@
<form action="/subscription_ajax?action=remove_subscriptions&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post"> <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) || "") %>"> <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"> <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> </button>
</form> </form>
<% else %> <% else %>
<form action="/subscription_ajax?action=create_subscription_to_channel&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post"> <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) || "") %>"> <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"> <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> </button>
</form> </form>
<% end %> <% end %>
@ -22,8 +22,8 @@
"author" => HTML.escape(author), "author" => HTML.escape(author),
"sub_count_text" => HTML.escape(sub_count_text), "sub_count_text" => HTML.escape(sub_count_text),
"csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || ""), "csrf_token" => URI.encode_www_form(env.get?("csrf_token").try &.as(String) || ""),
"subscribe_text" => HTML.escape(I18n.translate(locale, "Subscribe")), "subscribe_text" => HTML.escape(translate(locale, "Subscribe")),
"unsubscribe_text" => HTML.escape(I18n.translate(locale, "Unsubscribe")) "unsubscribe_text" => HTML.escape(translate(locale, "Unsubscribe"))
}.to_pretty_json }.to_pretty_json
%> %>
</script> </script>
@ -31,6 +31,6 @@
<% else %> <% else %>
<a id="subscribe" class="pure-button pure-button-primary" <a id="subscribe" class="pure-button pure-button-primary"
href="/login?referer=<%= env.get("current_page") %>"> 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> </a>
<% end %> <% end %>

View File

@ -1,18 +1,18 @@
<div class="flex-right flexible"> <div class="flex-right flexible">
<div class="icon-buttons"> <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> <i class="icon ion-logo-youtube"></i>
</a> </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> <i class="icon ion-md-headset"></i>
</a> </a>
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%> <% 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> <i class="icon ion-md-jet"></i>
</a> </a>
<% else %> <% 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> <i class="icon ion-md-jet"></i>
</a> </a>
<% end %> <% end %>

View File

@ -1,5 +1,5 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Create playlist") %> - Invidious</title> <title><%= translate(locale, "Create playlist") %> - Invidious</title>
<% end %> <% end %>
<div class="pure-g"> <div class="pure-g">
@ -8,25 +8,25 @@
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/create_playlist?referer=<%= URI.encode_www_form(referer) %>" method="post"> <form class="pure-form pure-form-aligned" action="/create_playlist?referer=<%= URI.encode_www_form(referer) %>" method="post">
<fieldset> <fieldset>
<legend><%= I18n.translate(locale, "Create playlist") %></legend> <legend><%= translate(locale, "Create playlist") %></legend>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="title"><%= I18n.translate(locale, "Title") %> :</label> <label for="title"><%= translate(locale, "Title") %> :</label>
<input required name="title" type="text" placeholder="<%= I18n.translate(locale, "Title") %>"> <input required name="title" type="text" placeholder="<%= translate(locale, "Title") %>">
</div> </div>
<div class="pure-control-group"> <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"> <select name="privacy" id="privacy">
<% PlaylistPrivacy.names.each do |option| %> <% 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 %> <% end %>
</select> </select>
</div> </div>
<div class="pure-controls"> <div class="pure-controls">
<button type="submit" name="action" value="create_playlist" class="pure-button pure-button-primary"> <button type="submit" name="action" value="create_playlist" class="pure-button pure-button-primary">
<%= I18n.translate(locale, "Create playlist") %> <%= translate(locale, "Create playlist") %>
</button> </button>
</div> </div>

View File

@ -1,20 +1,20 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Delete playlist") %> - Invidious</title> <title><%= translate(locale, "Delete playlist") %> - Invidious</title>
<% end %> <% end %>
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/delete_playlist?list=<%= plid %>&referer=<%= URI.encode_www_form(referer) %>" method="post"> <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-g">
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<button type="submit" name="submit" value="delete_playlist" class="pure-button pure-button-primary"> <button type="submit" name="submit" value="delete_playlist" class="pure-button pure-button-primary">
<%= I18n.translate(locale, "Yes") %> <%= translate(locale, "Yes") %>
</button> </button>
</div> </div>
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<a class="pure-button" href="/playlist?list=<%= plid %>"> <a class="pure-button" href="/playlist?list=<%= plid %>">
<%= I18n.translate(locale, "No") %> <%= translate(locale, "No") %>
</a> </a>
</div> </div>
</div> </div>

View File

@ -10,17 +10,17 @@
<div class="flex-right button-container"> <div class="flex-right button-container">
<div class="pure-u"> <div class="pure-u">
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/playlist?list=<%= plid %>"> <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> </a>
</div> </div>
<div class="pure-u"> <div class="pure-u">
<button class="pure-button pure-button-secondary low-profile" dir="auto" type="submit"> <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> </button>
</div> </div>
<div class="pure-u"> <div class="pure-u">
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>"> <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> </a>
</div> </div>
</div> </div>
@ -36,11 +36,11 @@
<div class="pure-u-1-1"> <div class="pure-u-1-1">
<b> <b>
<%= HTML.escape(playlist.author) %> | <%= HTML.escape(playlist.author) %> |
<%= I18n.translate_count(locale, "generic_videos_count", playlist.video_count) %> | <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
</b> </b>
<select name="privacy"> <select name="privacy">
<%- {"Public", "Unlisted", "Private"}.each do |option| -%> <%- {"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 -%> <%- end -%>
</select> </select>
</div> </div>

View File

@ -1,19 +1,19 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "History") %> - Invidious</title> <title><%= translate(locale, "History") %> - Invidious</title>
<% end %> <% end %>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <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>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:center"> <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> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:right"> <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> </h3>
</div> </div>
</div> </div>

View File

@ -1,22 +1,22 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Playlists") %> - Invidious</title> <title><%= translate(locale, "Playlists") %> - Invidious</title>
<% end %> <% end %>
<%= rendered "components/feed_menu" %> <%= rendered "components/feed_menu" %>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <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>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:center"> <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> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:right"> <h3 style="text-align:right">
<a href="/data_control?referer=<%= URI.encode_www_form("/feed/playlists") %>"> <a href="/data_control?referer=<%= URI.encode_www_form("/feed/playlists") %>">
<%= I18n.translate(locale, "Import/export") %> <%= translate(locale, "Import/export") %>
</a> </a>
</h3> </h3>
</div> </div>
@ -30,7 +30,7 @@
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1"> <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>
</div> </div>

View File

@ -1,8 +1,8 @@
<% content_for "header" do %> <% 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> <title>
<% if env.get("preferences").as(Preferences).default_home != "Popular" %> <% if env.get("preferences").as(Preferences).default_home != "Popular" %>
<%= I18n.translate(locale, "Popular") %> - Invidious <%= translate(locale, "Popular") %> - Invidious
<% else %> <% else %>
Invidious Invidious
<% end %> <% end %>

View File

@ -1,5 +1,5 @@
<% content_for "header" do %> <% 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 %>" /> <link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/private?token=<%= token %>" />
<% end %> <% end %>
@ -8,12 +8,12 @@
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3> <h3>
<a href="/subscription_manager"><%= I18n.translate(locale, "Manage subscriptions") %></a> <a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:center"> <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> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
@ -26,7 +26,7 @@
<% if CONFIG.enable_user_notifications %> <% if CONFIG.enable_user_notifications %>
<center> <center>
<%= I18n.translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %> <%= translate_count(locale, "subscriptions_unseen_notifs_count", notifications.size) %>
</center> </center>
<% if !notifications.empty? %> <% if !notifications.empty? %>

View File

@ -1,8 +1,8 @@
<% content_for "header" do %> <% 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> <title>
<% if env.get("preferences").as(Preferences).default_home != "Trending" %> <% if env.get("preferences").as(Preferences).default_home != "Trending" %>
<%= I18n.translate(locale, "Trending") %> - Invidious <%= translate(locale, "Trending") %> - Invidious
<% else %> <% else %>
Invidious Invidious
<% end %> <% end %>
@ -15,7 +15,7 @@
<div style="align-self:flex-end" class="pure-u-2-3"> <div style="align-self:flex-end" class="pure-u-2-3">
<% if plid %> <% if plid %>
<a href="/playlist?list=<%= plid %>"> <a href="/playlist?list=<%= plid %>">
<%= I18n.translate(locale, "View as playlist") %> <%= translate(locale, "View as playlist") %>
</a> </a>
<% end %> <% end %>
</div> </div>
@ -24,10 +24,10 @@
<% {"Default", "Music", "Gaming", "Movies"}.each do |option| %> <% {"Default", "Music", "Gaming", "Movies"}.each do |option| %>
<div class="pure-u-1 pure-md-1-3"> <div class="pure-u-1 pure-md-1-3">
<% if trending_type == option %> <% if trending_type == option %>
<b><%= I18n.translate(locale, option) %></b> <b><%= translate(locale, option) %></b>
<% else %> <% else %>
<a href="/feed/trending?type=<%= option %>&region=<%= region %>"> <a href="/feed/trending?type=<%= option %>&region=<%= region %>">
<%= I18n.translate(locale, option) %> <%= translate(locale, option) %>
</a> </a>
<% end %> <% end %>
</div> </div>

View File

@ -7,7 +7,7 @@
</head> </head>
<body> <body>
<h1><%= I18n.translate(locale, "JavaScript license information") %></h1> <h1><%= translate(locale, "JavaScript license information") %></h1>
<table id="jslicense-labels1"> <table id="jslicense-labels1">
<tr> <tr>
<td> <td>
@ -19,7 +19,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -33,7 +33,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -47,7 +47,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -61,7 +61,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -75,7 +75,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -89,7 +89,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -103,7 +103,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -117,7 +117,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -131,7 +131,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -145,7 +145,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -159,7 +159,7 @@
</td> </td>
<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> </td>
</tr> </tr>
@ -173,7 +173,7 @@
</td> </td>
<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> </td>
</tr> </tr>

View File

@ -1,5 +1,5 @@
<% content_for "header" do %> <% 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> <title>
Invidious Invidious
</title> </title>

View File

@ -13,28 +13,28 @@
<%- if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email -%> <%- if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email -%>
<div class="pure-u"> <div class="pure-u">
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/add_playlist_items?list=<%= plid %>"> <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> </a>
</div> </div>
<div class="pure-u"> <div class="pure-u">
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/edit_playlist?list=<%= plid %>"> <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> </a>
</div> </div>
<div class="pure-u"> <div class="pure-u">
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>"> <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> </a>
</div> </div>
<%- else -%> <%- else -%>
<div class="pure-u"> <div class="pure-u">
<%- if IV::Database::Playlists.exists?(playlist.id) -%> <%- if IV::Database::Playlists.exists?(playlist.id) -%>
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/subscribe_playlist?list=<%= plid %>"> <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> </a>
<%- else -%> <%- else -%>
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>"> <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> </a>
<%- end -%> <%- end -%>
</div> </div>
@ -42,7 +42,7 @@
<div class="pure-u"> <div class="pure-u">
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/feed/playlist/<%= plid %>"> <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> </a>
</div> </div>
</div> </div>
@ -57,15 +57,15 @@
<% else %> <% else %>
<%= author %> | <%= author %> |
<% end %> <% end %>
<%= I18n.translate_count(locale, "generic_videos_count", playlist.video_count) %> | <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
<%= I18n.translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> | <%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> |
<% case playlist.as(InvidiousPlaylist).privacy when %> <% case playlist.as(InvidiousPlaylist).privacy when %>
<% when PlaylistPrivacy::Public %> <% 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 %> <% 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 %> <% 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 %> <% end %>
</b> </b>
<% else %> <% else %>
@ -76,25 +76,25 @@
<% subtitle = playlist.subtitle || "" %> <% subtitle = playlist.subtitle || "" %>
<span><%= HTML.escape(subtitle[0..subtitle.rindex(" • ") || subtitle.size]) %></span> | <span><%= HTML.escape(subtitle[0..subtitle.rindex(" • ") || subtitle.size]) %></span> |
<% end %> <% end %>
<%= I18n.translate_count(locale, "generic_videos_count", playlist.video_count) %> | <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
<%= I18n.translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> <%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %>
</b> </b>
<% end %> <% end %>
<% if !playlist.is_a? InvidiousPlaylist %> <% if !playlist.is_a? InvidiousPlaylist %>
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<a rel="noreferrer noopener" href="https://www.youtube.com/playlist?list=<%= playlist.id %>"> <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> </a>
<span> | </span> <span> | </span>
<% if env.get("preferences").as(Preferences).automatic_instance_redirect%> <% if env.get("preferences").as(Preferences).automatic_instance_redirect%>
<a href="/redirect?referer=<%= env.get?("current_page") %>"> <a href="/redirect?referer=<%= env.get?("current_page") %>">
<%= I18n.translate(locale, "Switch Invidious Instance") %> <%= translate(locale, "Switch Invidious Instance") %>
</a> </a>
<% else %> <% else %>
<a href="https://redirect.invidious.io/playlist?list=<%= playlist.id %>"> <a href="https://redirect.invidious.io/playlist?list=<%= playlist.id %>">
<%= I18n.translate(locale, "Switch Invidious Instance") %> <%= translate(locale, "Switch Invidious Instance") %>
</a> </a>
<% end %> <% end %>
</div> </div>

View File

@ -18,7 +18,7 @@
<% else %> <% else %>
<noscript> <noscript>
<a href="/post/<%= id %>?ucid=<%= ucid %>&nojs=1"> <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> </a>
</noscript> </noscript>
<% end %> <% end %>
@ -29,12 +29,12 @@
<%= <%=
{ {
"id" => id, "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_comments_text" => "",
"reddit_permalink_text" => "", "reddit_permalink_text" => "",
"comments_text" => HTML.escape(I18n.translate(locale, "View `x` comments", "{commentCount}")), "comments_text" => HTML.escape(translate(locale, "View `x` comments", "{commentCount}")),
"hide_replies_text" => HTML.escape(I18n.translate(locale, "Hide replies")), "hide_replies_text" => HTML.escape(translate(locale, "Hide replies")),
"show_replies_text" => HTML.escape(I18n.translate(locale, "Show replies")), "show_replies_text" => HTML.escape(translate(locale, "Show replies")),
"params" => { "params" => {
"comments": ["youtube"] "comments": ["youtube"]
}, },

View File

@ -11,9 +11,9 @@
<%- if items.empty? -%> <%- if items.empty? -%>
<div class="h-box no-results-error"> <div class="h-box no-results-error">
<div> <div>
<%= I18n.translate(locale, "search_message_no_results") %><br/><br/> <%= translate(locale, "search_message_no_results") %><br/><br/>
<%= I18n.translate(locale, "search_message_change_filters_or_query") %><br/><br/> <%= translate(locale, "search_message_change_filters_or_query") %><br/><br/>
<%= I18n.translate(locale, "search_message_use_another_instance", redirect_url) %> <%= translate(locale, "search_message_use_another_instance", redirect_url) %>
</div> </div>
</div> </div>
<%- else -%> <%- else -%>

View File

@ -1,7 +1,7 @@
<% content_for "header" do %> <% 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> <title>
Invidious - <%= I18n.translate(locale, "search") %> Invidious - <%= translate(locale, "search") %>
</title> </title>
<link rel="stylesheet" href="/css/empty.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/empty.css?v=<%= ASSET_COMMIT %>">
<% end %> <% end %>

View File

@ -42,7 +42,7 @@
<div class="pure-u-1 pure-u-md-8-24 user-field"> <div class="pure-u-1 pure-u-md-8-24 user-field">
<% if env.get? "user" %> <% if env.get? "user" %>
<div class="pure-u-1-4"> <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" %> <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i> <i class="icon ion-ios-sunny"></i>
<% else %> <% else %>
@ -51,7 +51,7 @@
</a> </a>
</div> </div>
<div class="pure-u-1-4"> <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 %> <% notification_count = env.get("user").as(Invidious::User).notifications.size %>
<% if CONFIG.enable_user_notifications && notification_count > 0 %> <% if CONFIG.enable_user_notifications && notification_count > 0 %>
<span id="notification_count"><%= notification_count %></span> <i class="icon ion-ios-notifications"></i> <span id="notification_count"><%= notification_count %></span> <i class="icon ion-ios-notifications"></i>
@ -61,7 +61,7 @@
</a> </a>
</div> </div>
<div class="pure-u-1-4"> <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> <i class="icon ion-ios-cog"></i>
</a> </a>
</div> </div>
@ -74,13 +74,13 @@
<form action="/signout?referer=<%= env.get?("current_page") %>" method="post"> <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) || "") %>"> <input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<a class="pure-menu-heading" href="#"> <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> </a>
</form> </form>
</div> </div>
<% else %> <% else %>
<div class="pure-u-1-3"> <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" %> <% if dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i> <i class="icon ion-ios-sunny"></i>
<% else %> <% else %>
@ -89,14 +89,14 @@
</a> </a>
</div> </div>
<div class="pure-u-1-3"> <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> <i class="icon ion-ios-cog"></i>
</a> </a>
</div> </div>
<% if CONFIG.login_enabled %> <% if CONFIG.login_enabled %>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading"> <a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<%= I18n.translate(locale, "Log in") %> <%= translate(locale, "Log in") %>
</a> </a>
</div> </div>
<% end %> <% end %>
@ -118,38 +118,38 @@
<span> <span>
<i class="icon ion-logo-github"></i> <i class="icon ion-logo-github"></i>
<% if CONFIG.modified_source_code_url %> <% 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="https://github.com/iv-org/invidious"><%= 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="<%= CONFIG.modified_source_code_url %>"><%= translate(locale, "footer_modfied_source_code") %></a>
<% else %> <% 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 %> <% end %>
</span> </span>
<span> <span>
<i class="icon ion-ios-paper"></i> <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> </span>
</div> </div>
<div class="pure-u-1 pure-u-md-1-3"> <div class="pure-u-1 pure-u-md-1-3">
<span> <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>
<span> <span>
<i class="icon ion-logo-javascript"></i> <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>
<span> <span>
<i class="icon ion-ios-paper"></i> <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> </span>
</div> </div>
<div class="pure-u-1 pure-u-md-1-3"> <div class="pure-u-1 pure-u-md-1-3">
<span> <span>
<i class="icon ion-ios-wallet"></i> <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>
<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>
</div> </div>
</footer> </footer>
@ -163,8 +163,8 @@
<script id="notification_data" type="application/json"> <script id="notification_data" type="application/json">
<%= <%=
{ {
"upload_text" => HTML.escape(I18n.translate(locale, "`x` uploaded a video")), "upload_text" => HTML.escape(translate(locale, "`x` uploaded a video")),
"live_upload_text" => HTML.escape(I18n.translate(locale, "`x` is live")) "live_upload_text" => HTML.escape(translate(locale, "`x` is live"))
}.to_pretty_json }.to_pretty_json
%> %>
</script> </script>

View File

@ -1,22 +1,22 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Token") %> - Invidious</title> <title><%= translate(locale, "Token") %> - Invidious</title>
<% end %> <% end %>
<% if env.get? "access_token" %> <% if env.get? "access_token" %>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3> <h3>
<%= I18n.translate(locale, "Token") %> <%= translate(locale, "Token") %>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:center"> <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> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:right"> <h3 style="text-align:right">
<a href="/preferences"><%= I18n.translate(locale, "Preferences") %></a> <a href="/preferences"><%= translate(locale, "Preferences") %></a>
</h3> </h3>
</div> </div>
</div> </div>
@ -30,9 +30,9 @@
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/authorize_token" method="post"> <form class="pure-form pure-form-aligned" action="/authorize_token" method="post">
<% if callback_url %> <% 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 %> <% else %>
<legend><%= I18n.translate(locale, "Authorize token?") %></legend> <legend><%= translate(locale, "Authorize token?") %></legend>
<% end %> <% end %>
<div class="pure-g"> <div class="pure-g">
@ -48,7 +48,7 @@
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary"> <button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary">
<%= I18n.translate(locale, "Yes") %> <%= translate(locale, "Yes") %>
</button> </button>
</div> </div>
<div class="pure-u-1-2"> <div class="pure-u-1-2">
@ -57,7 +57,7 @@
<% else %> <% else %>
<a class="pure-button" href="/"> <a class="pure-button" href="/">
<% end %> <% end %>
<%= I18n.translate(locale, "No") %> <%= translate(locale, "No") %>
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Change password") %> - Invidious</title> <title><%= translate(locale, "Change password") %> - Invidious</title>
<% end %> <% end %>
<div class="pure-g"> <div class="pure-g">
@ -7,20 +7,20 @@
<div class="pure-u-1 pure-u-lg-3-5"> <div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/change_password?referer=<%= URI.encode_www_form(referer) %>" method="post"> <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> <fieldset>
<label for="password"><%= I18n.translate(locale, "Password") %> :</label> <label for="password"><%= translate(locale, "Password") %> :</label>
<input required class="pure-input-1" name="password" type="password" placeholder="<%= I18n.translate(locale, "Password") %>"> <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> <label for="new_password[0]"><%= translate(locale, "New password") %> :</label>
<input required class="pure-input-1" name="new_password[0]" type="password" placeholder="<%= I18n.translate(locale, "New password") %>"> <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> <label for="new_password[1]"><%= translate(locale, "New password") %> :</label>
<input required class="pure-input-1" name="new_password[1]" type="password" placeholder="<%= I18n.translate(locale, "New password") %>"> <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"> <button type="submit" name="action" value="change_password" class="pure-button pure-button-primary">
<%= I18n.translate(locale, "Change password") %> <%= translate(locale, "Change password") %>
</button> </button>
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>"> <input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">

View File

@ -1,20 +1,20 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Clear watch history") %> - Invidious</title> <title><%= translate(locale, "Clear watch history") %> - Invidious</title>
<% end %> <% end %>
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/clear_watch_history?referer=<%= URI.encode_www_form(referer) %>" method="post"> <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-g">
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary"> <button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary">
<%= I18n.translate(locale, "Yes") %> <%= translate(locale, "Yes") %>
</button> </button>
</div> </div>
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<a class="pure-button" href="<%= URI.encode_www_form(referer) %>"> <a class="pure-button" href="<%= URI.encode_www_form(referer) %>">
<%= I18n.translate(locale, "No") %> <%= translate(locale, "No") %>
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,67 +1,67 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Import and Export Data") %> - Invidious</title> <title><%= translate(locale, "Import and Export Data") %> - Invidious</title>
<% end %> <% end %>
<div class="h-box"> <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"> <form class="pure-form pure-form-aligned" enctype="multipart/form-data" action="/data_control?referer=<%= URI.encode_www_form(referer) %>" method="post">
<fieldset> <fieldset>
<legend><%= I18n.translate(locale, "Import") %></legend> <legend><%= translate(locale, "Import") %></legend>
<div class="pure-control-group"> <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"> <input type="file" id="import_invidious" name="import_invidious">
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="import_youtube"> <label for="import_youtube">
<a rel="noopener noreferrer" target="_blank" href="https://github.com/iv-org/documentation/blob/master/docs/export-youtube-subscriptions.md"> <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> </a>
</label> </label>
<input type="file" id="import_youtube" name="import_youtube"> <input type="file" id="import_youtube" name="import_youtube">
</div> </div>
<div class="pure-control-group"> <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"> <input type="file" id="import_youtube_pl" name="import_youtube_pl">
</div> </div>
<div class="pure-control-group"> <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"> <input type="file" id="import_youtube_wh" name="import_youtube_wh">
</div> </div>
<div class="pure-control-group"> <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"> <input type="file" id="import_freetube" name="import_freetube">
</div> </div>
<div class="pure-control-group"> <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"> <input type="file" id="import_newpipe_subscriptions" name="import_newpipe_subscriptions">
</div> </div>
<div class="pure-control-group"> <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"> <input type="file" id="import_newpipe" name="import_newpipe">
</div> </div>
<div class="pure-controls"> <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> </div>
<legend><%= I18n.translate(locale, "Export") %></legend> <legend><%= translate(locale, "Export") %></legend>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -1,20 +1,20 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Delete account") %> - Invidious</title> <title><%= translate(locale, "Delete account") %> - Invidious</title>
<% end %> <% end %>
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/delete_account?referer=<%= URI.encode_www_form(referer) %>" method="post"> <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-g">
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<button type="submit" name="submit" value="delete_account" class="pure-button pure-button-primary"> <button type="submit" name="submit" value="delete_account" class="pure-button pure-button-primary">
<%= I18n.translate(locale, "Yes") %> <%= translate(locale, "Yes") %>
</button> </button>
</div> </div>
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<a class="pure-button" href="<%= URI.encode_www_form(referer) %>"> <a class="pure-button" href="<%= URI.encode_www_form(referer) %>">
<%= I18n.translate(locale, "No") %> <%= translate(locale, "No") %>
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Log in") %> - Invidious</title> <title><%= translate(locale, "Log in") %> - Invidious</title>
<% end %> <% end %>
<div class="pure-g"> <div class="pure-g">
@ -13,15 +13,15 @@
<% if email %> <% if email %>
<input name="email" type="hidden" value="<%= HTML.escape(email) %>"> <input name="email" type="hidden" value="<%= HTML.escape(email) %>">
<% else %> <% else %>
<label for="email"><%= I18n.translate(locale, "User ID") %> :</label> <label for="email"><%= translate(locale, "User ID") %> :</label>
<input required class="pure-input-1" name="email" type="text" placeholder="<%= I18n.translate(locale, "User ID") %>"> <input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>">
<% end %> <% end %>
<% if password %> <% if password %>
<input name="password" type="hidden" value="<%= HTML.escape(password) %>"> <input name="password" type="hidden" value="<%= HTML.escape(password) %>">
<% else %> <% else %>
<label for="password"><%= I18n.translate(locale, "Password") %> :</label> <label for="password"><%= translate(locale, "Password") %> :</label>
<input required class="pure-input-1" name="password" type="password" placeholder="<%= I18n.translate(locale, "Password") %>"> <input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
<% end %> <% end %>
<% if captcha %> <% if captcha %>
@ -30,15 +30,15 @@
<% captcha[:tokens].each_with_index do |token, i| %> <% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>"> <input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
<% end %> <% 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"> <input type="text" name="answer" type="text" placeholder="h:mm:ss">
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary"> <button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<%= I18n.translate(locale, "Register") %> <%= translate(locale, "Register") %>
</button> </button>
<% else %> <% else %>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary"> <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> </button>
<% end %> <% end %>
</fieldset> </fieldset>

View File

@ -1,49 +1,49 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Preferences") %> - Invidious</title> <title><%= translate(locale, "Preferences") %> - Invidious</title>
<% end %> <% end %>
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/preferences?referer=<%= URI.encode_www_form(referer) %>" method="post"> <form class="pure-form pure-form-aligned" action="/preferences?referer=<%= URI.encode_www_form(referer) %>" method="post">
<fieldset> <fieldset>
<legend><%= I18n.translate(locale, "preferences_category_player") %></legend> <legend><%= translate(locale, "preferences_category_player") %></legend>
<div class="pure-control-group"> <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 %>> <input name="video_loop" id="video_loop" type="checkbox" <% if preferences.video_loop %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="preload" id="preload" type="checkbox" <% if preferences.preload %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="autoplay" id="autoplay" type="checkbox" <% if preferences.autoplay %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="continue" id="continue" type="checkbox" <% if preferences.continue %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="continue_autoplay" id="continue_autoplay" type="checkbox" <% if preferences.continue_autoplay %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="local" id="local" type="checkbox" <% if preferences.local && !CONFIG.disabled?("local") %>checked<% end %> <% if CONFIG.disabled?("local") %>disabled<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="listen" id="listen" type="checkbox" <% if preferences.listen %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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"> <select name="speed" id="speed">
<% {2.0, 1.75, 1.5, 1.25, 1.0, 0.75, 0.5, 0.25}.each do |option| %> <% {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> <option <% if preferences.speed == option %> selected <% end %>><%= option %></option>
@ -52,11 +52,11 @@
</div> </div>
<div class="pure-control-group"> <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"> <select name="quality" id="quality">
<% {"dash", "hd720", "medium", "small"}.each do |option| %> <% {"dash", "hd720", "medium", "small"}.each do |option| %>
<% if !(option == "dash" && CONFIG.disabled?("dash")) %> <% 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 %>
<% end %> <% end %>
</select> </select>
@ -64,108 +64,108 @@
<% if !CONFIG.disabled?("dash") %> <% if !CONFIG.disabled?("dash") %>
<div class="pure-control-group"> <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"> <select name="quality_dash" id="quality_dash">
<% {"auto", "best", "4320p", "2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p", "worst"}.each do |option| %> <% {"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 %> <% end %>
</select> </select>
</div> </div>
<% end %> <% end %>
<div class="pure-control-group"> <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 %>"> <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> <span class="pure-form-message-inline" id="volume-value"><%= preferences.volume %></span>
</div> </div>
<div class="pure-control-group"> <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| %> <% preferences.comments.each_with_index do |comments, index| %>
<select name="comments[<%= index %>]" id="comments[<%= index %>]"> <select name="comments[<%= index %>]" id="comments[<%= index %>]">
<% {"", "youtube", "reddit"}.each do |option| %> <% {"", "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 %> <% end %>
</select> </select>
<% end %> <% end %>
</div> </div>
<div class="pure-control-group"> <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| %> <% preferences.captions.each_with_index do |caption, index| %>
<select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]"> <select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]">
<% Invidious::Videos::Captions::LANGUAGES.each do |option| %> <% 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 %> <% end %>
</select> </select>
<% end %> <% end %>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="related_videos" id="related_videos" type="checkbox" <% if preferences.related_videos %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="annotations" id="annotations" type="checkbox" <% if preferences.annotations %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="extend_desc" id="extend_desc" type="checkbox" <% if preferences.extend_desc %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="vr_mode" id="vr_mode" type="checkbox" <% if preferences.vr_mode %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="save_player_pos" id="save_player_pos" type="checkbox" <% if preferences.save_player_pos %>checked<% end %>>
</div> </div>
<legend><%= I18n.translate(locale, "preferences_category_visual") %></legend> <legend><%= translate(locale, "preferences_category_visual") %></legend>
<div class="pure-control-group"> <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"> <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> <option value="<%= iso_name %>" <% if preferences.locale == iso_name %> selected <% end %>><%= HTML.escape(full_name) %></option>
<% end %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <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"> <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> <option value="<%= option %>" <% if preferences.region == option %> selected <% end %>><%= option %></option>
<% end %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <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"> <select name="player_style" id="player_style">
<% {"invidious", "youtube"}.each do |option| %> <% {"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 %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <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"> <select name="dark_mode" id="dark_mode">
<% {"", "light", "dark"}.each do |option| %> <% {"", "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 %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="thin_mode" id="thin_mode" type="checkbox" <% if preferences.thin_mode %>checked<% end %>>
</div> </div>
@ -176,187 +176,187 @@
<% end %> <% end %>
<div class="pure-control-group"> <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"> <select name="default_home" id="default_home">
<% feed_options.each do |option| %> <% 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 %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <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| %> <% (feed_options.size - 1).times do |index| %>
<select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]"> <select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]">
<% feed_options.each do |option| %> <% 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 %> <% end %>
</select> </select>
<% end %> <% end %>
</div> </div>
<% if env.get? "user" %> <% if env.get? "user" %>
<div class="pure-control-group"> <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 %>> <input name="show_nick" id="show_nick" type="checkbox" <% if preferences.show_nick %>checked<% end %>>
</div> </div>
<% end %> <% end %>
<legend><%= I18n.translate(locale, "preferences_category_misc") %></legend> <legend><%= translate(locale, "preferences_category_misc") %></legend>
<div class="pure-control-group"> <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 %>> <input name="automatic_instance_redirect" id="automatic_instance_redirect" type="checkbox" <% if preferences.automatic_instance_redirect %>checked<% end %>>
</div> </div>
<% if env.get? "user" %> <% if env.get? "user" %>
<legend><%= I18n.translate(locale, "preferences_category_subscription") %></legend> <legend><%= translate(locale, "preferences_category_subscription") %></legend>
<div class="pure-control-group"> <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 %>> <input name="watch_history" id="watch_history" type="checkbox" <% if preferences.watch_history %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>"> <input name="max_results" id="max_results" type="number" value="<%= preferences.max_results %>">
</div> </div>
<div class="pure-control-group"> <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"> <select name="sort" id="sort">
<% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %> <% {"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 %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<% if preferences.unseen_only %> <% 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 %> <% 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 %> <% end %>
<input name="latest_only" id="latest_only" type="checkbox" <% if preferences.latest_only %>checked<% end %>> <input name="latest_only" id="latest_only" type="checkbox" <% if preferences.latest_only %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>>
</div> </div>
<% if CONFIG.enable_user_notifications %> <% if CONFIG.enable_user_notifications %>
<div class="pure-control-group"> <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 %>> <input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>>
</div> </div>
<% # Web notifications are only supported over HTTPS %> <% # Web notifications are only supported over HTTPS %>
<% if Kemal.config.ssl || CONFIG.https_only %> <% if Kemal.config.ssl || CONFIG.https_only %>
<div class="pure-control-group"> <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> </div>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
<% if env.get?("user") && CONFIG.admins.includes? env.get?("user").as(Invidious::User).email %> <% 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"> <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"> <select name="admin_default_home" id="admin_default_home">
<% feed_options.each do |option| %> <% 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 %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <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| %> <% (feed_options.size - 1).times do |index| %>
<select name="admin_feed_menu[<%= index %>]" id="admin_feed_menu[<%= index %>]"> <select name="admin_feed_menu[<%= index %>]" id="admin_feed_menu[<%= index %>]">
<% feed_options.each do |option| %> <% 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 %> <% end %>
</select> </select>
<% end %> <% end %>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="popular_enabled" id="popular_enabled" type="checkbox" <% if CONFIG.popular_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="captcha_enabled" id="captcha_enabled" type="checkbox" <% if CONFIG.captcha_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="login_enabled" id="login_enabled" type="checkbox" <% if CONFIG.login_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="registration_enabled" id="registration_enabled" type="checkbox" <% if CONFIG.registration_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>> <input name="statistics_enabled" id="statistics_enabled" type="checkbox" <% if CONFIG.statistics_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <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 %>"> <input name="modified_source_code_url" id="modified_source_code_url" type="url" value="<%= CONFIG.modified_source_code_url %>">
</div> </div>
<% end %> <% end %>
<% if env.get? "user" %> <% if env.get? "user" %>
<legend><%= I18n.translate(locale, "preferences_category_data") %></legend> <legend><%= translate(locale, "preferences_category_data") %></legend>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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>
<div class="pure-control-group"> <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> </div>
<% end %> <% end %>
<div class="pure-controls"> <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> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -1,26 +1,26 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Subscription manager") %> - Invidious</title> <title><%= translate(locale, "Subscription manager") %> - Invidious</title>
<% end %> <% end %>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3> <h3>
<a href="/feed/subscriptions"> <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> </a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:center"> <h3 style="text-align:center">
<a href="/feed/history"> <a href="/feed/history">
<%= I18n.translate(locale, "Watch history") %> <%= translate(locale, "Watch history") %>
</a> </a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3 style="text-align:right"> <h3 style="text-align:right">
<a href="/data_control?referer=<%= URI.encode_www_form(referer) %>"> <a href="/data_control?referer=<%= URI.encode_www_form(referer) %>">
<%= I18n.translate(locale, "Import/export") %> <%= translate(locale, "Import/export") %>
</a> </a>
</h3> </h3>
</div> </div>
@ -39,7 +39,7 @@
<h3 style="padding-right:0.5em"> <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"> <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 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> </form>
</h3> </h3>
</div> </div>

View File

@ -1,17 +1,17 @@
<% content_for "header" do %> <% content_for "header" do %>
<title><%= I18n.translate(locale, "Token manager") %> - Invidious</title> <title><%= translate(locale, "Token manager") %> - Invidious</title>
<% end %> <% end %>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3> <h3>
<%= I18n.translate_count(locale, "tokens_count", tokens.size, I18n::NumberFormatting::HtmlSpan) %> <%= translate_count(locale, "tokens_count", tokens.size, NumberFormatting::HtmlSpan) %>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3"></div> <div class="pure-u-1-3"></div>
<div class="pure-u-1-3" style="text-align:right"> <div class="pure-u-1-3" style="text-align:right">
<h3> <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> </h3>
</div> </div>
</div> </div>
@ -25,13 +25,13 @@
</h4> </h4>
</div> </div>
<div class="pure-u-1-5" style="text-align:center"> <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>
<div class="pure-u-1-5" style="text-align:right"> <div class="pure-u-1-5" style="text-align:right">
<h3 style="padding-right:0.5em"> <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"> <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 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> </form>
</h3> </h3>
</div> </div>

View File

@ -35,11 +35,11 @@ we're going to need to do it here in order to allow for translations.
--> -->
<style> <style>
#descexpansionbutton ~ label > a::after { #descexpansionbutton ~ label > a::after {
content: "<%= I18n.translate(locale, "Show more") %>" content: "<%= translate(locale, "Show more") %>"
} }
#descexpansionbutton:checked ~ label > a::after { #descexpansionbutton:checked ~ label > a::after {
content: "<%= I18n.translate(locale, "Show less") %>" content: "<%= translate(locale, "Show less") %>"
} }
</style> </style>
<% end %> <% 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, "length_seconds" => video.length_seconds.to_f,
"play_next" => !video.related_videos.empty? && !plid && params.continue, "play_next" => !video.related_videos.empty? && !plid && params.continue,
"next_video" => video.related_videos.select { |rv| rv["id"]? }[0]?.try &.["id"], "next_video" => video.related_videos.select { |rv| rv["id"]? }[0]?.try &.["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" => HTML.escape(I18n.translate(locale, "View Reddit comments")), "reddit_comments_text" => HTML.escape(translate(locale, "View Reddit comments")),
"reddit_permalink_text" => HTML.escape(I18n.translate(locale, "View more comments on Reddit")), "reddit_permalink_text" => HTML.escape(translate(locale, "View more comments on Reddit")),
"comments_text" => HTML.escape(I18n.translate(locale, "View `x` comments", "{commentCount}")), "comments_text" => HTML.escape(translate(locale, "View `x` comments", "{commentCount}")),
"hide_replies_text" => HTML.escape(I18n.translate(locale, "Hide replies")), "hide_replies_text" => HTML.escape(translate(locale, "Hide replies")),
"show_replies_text" => HTML.escape(I18n.translate(locale, "Show replies")), "show_replies_text" => HTML.escape(translate(locale, "Show replies")),
"params" => params, "params" => params,
"preferences" => preferences, "preferences" => preferences,
"premiere_timestamp" => video.premiere_timestamp.try &.to_unix, "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> <h1>
<%= title %> <%= title %>
<% if params.listen %> <% 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> <i class="icon ion-ios-videocam"></i>
</a> </a>
<% else %> <% 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> <i class="icon ion-md-headset"></i>
</a> </a>
<% end %> <% end %>
@ -90,7 +90,7 @@ we're going to need to do it here in order to allow for translations.
<% if !video.is_listed %> <% if !video.is_listed %>
<h3> <h3>
<i class="icon ion-ios-unlock"></i> <%= I18n.translate(locale, "Unlisted") %> <i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
</h3> </h3>
<% end %> <% end %>
@ -100,11 +100,11 @@ we're going to need to do it here in order to allow for translations.
</h3> </h3>
<% elsif video.premiere_timestamp.try &.> Time.utc %> <% elsif video.premiere_timestamp.try &.> Time.utc %>
<h3> <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> </h3>
<% elsif video.live_now %> <% elsif video.live_now %>
<h3> <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> </h3>
<% end %> <% end %>
</div> </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) link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param)
end 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-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 %>"><%= I18n.translate(locale, "videoinfo_youTube_embed_link") %></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> </span>
<p id="watch-on-another-invidious-instance"> <p id="watch-on-another-invidious-instance">
<%- link_iv_other = IV::Frontend::Misc.redirect_url(env) -%> <%- 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>
<p id="embed-link"> <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 = URI.new(path: "/embed/#{id}")
link_iv_embed = IV::HttpServer::Utils.add_params_to_url(link_iv_embed, params_iv_embed) 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>
<p id="annotations"> <p id="annotations">
<% if params.annotations %> <% if params.annotations %>
<a href="/watch?<%= env.params.query %>&iv_load_policy=3"> <a href="/watch?<%= env.params.query %>&iv_load_policy=3">
<%= I18n.translate(locale, "Hide annotations") %> <%= translate(locale, "Hide annotations") %>
</a> </a>
<% else %> <% else %>
<a href="/watch?<%= env.params.query %>&iv_load_policy=1"> <a href="/watch?<%= env.params.query %>&iv_load_policy=1">
<%=I18n.translate(locale, "Show annotations")%> <%=translate(locale, "Show annotations")%>
</a> </a>
<% end %> <% end %>
</p> </p>
@ -160,7 +160,7 @@ we're going to need to do it here in order to allow for translations.
<% if !playlists.empty? %> <% if !playlists.empty? %>
<form data-onsubmit="return_false" class="pure-form pure-form-stacked" action="/playlist_ajax?action=add_video" method="post" target="_blank"> <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"> <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"> <select style="width:100%" name="playlist_id" id="playlist_id">
<% playlists.each do |plid, playlist_title| %> <% playlists.each do |plid, playlist_title| %>
<option data-plid="<%= plid %>" value="<%= plid %>"><%= HTML.escape(playlist_title) %></option> <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="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
<input type="hidden" name="video_id" value="<%= video.id %>"> <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"> <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> </button>
</form> </form>
<script id="playlist_data" type="application/json"> <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="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="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="dislikes" style="display: none; visibility: hidden;"></p>
<p id="genre"><%= I18n.translate(locale, "Genre: ") %> <p id="genre"><%= translate(locale, "Genre: ") %>
<% if !video.genre_url %> <% if !video.genre_url %>
<%= video.genre %> <%= video.genre %>
<% else %> <% else %>
@ -199,21 +199,21 @@ we're going to need to do it here in order to allow for translations.
</p> </p>
<% if video.license %> <% if video.license %>
<% if video.license.empty? %> <% 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 %> <% else %>
<p id="license"><%= I18n.translate(locale, "License: ") %><%= video.license %></p> <p id="license"><%= translate(locale, "License: ") %><%= video.license %></p>
<% end %> <% end %>
<% 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="wilson" style="display: none; visibility: hidden;"></p>
<p id="rating" style="display: none; visibility: hidden;"></p> <p id="rating" style="display: none; visibility: hidden;"></p>
<p id="engagement" style="display: none; visibility: hidden;"></p> <p id="engagement" style="display: none; visibility: hidden;"></p>
<% if video.allowed_regions.size != REGIONS.size %> <% if video.allowed_regions.size != REGIONS.size %>
<p id="allowed_regions"> <p id="allowed_regions">
<% if video.allowed_regions.size < REGIONS.size // 2 %> <% 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 %> <% 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 %> <% end %>
</p> </p>
<% end %> <% end %>
@ -245,9 +245,9 @@ we're going to need to do it here in order to allow for translations.
<div class="h-box"> <div class="h-box">
<p id="published-date"> <p id="published-date">
<% if video.premiere_timestamp.try &.> Time.utc %> <% 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 %> <% 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 %> <% end %>
</p> </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"/> <input id="music-desc-expansion" type="checkbox"/>
<label for="music-desc-expansion"> <label for="music-desc-expansion">
<h3 id="music-description-title"> <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-up"></span>
<span class="icon ion-ios-arrow-down"></span> <span class="icon ion-ios-arrow-down"></span>
</h3> </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"> <div id="music-description-box">
<% video.music.each do |music| %> <% video.music.each do |music| %>
<div class="music-item"> <div class="music-item">
<p class="music-song"><%= I18n.translate(locale, "Song: ") %><%= music.song %></p> <p class="music-song"><%= translate(locale, "Song: ") %><%= music.song %></p>
<p class="music-artist"><%= I18n.translate(locale, "Artist: ") %><%= music.artist %></p> <p class="music-artist"><%= translate(locale, "Artist: ") %><%= music.artist %></p>
<p class="music-album"><%= I18n.translate(locale, "Album: ") %><%= music.album %></p> <p class="music-album"><%= translate(locale, "Album: ") %><%= music.album %></p>
</div> </div>
<% end %> <% end %>
</div> </div>
@ -293,7 +293,7 @@ we're going to need to do it here in order to allow for translations.
<% else %> <% else %>
<noscript> <noscript>
<a href="/watch?<%= env.params.query %>&nojs=1"> <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> </a>
</noscript> </noscript>
<% end %> <% 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? %> <% if !video.related_videos.empty? %>
<div <% if plid %>style="display:none"<% end %>> <div <% if plid %>style="display:none"<% end %>>
<div class="pure-control-group"> <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 %>> <input name="continue" id="continue" type="checkbox" <% if params.continue %>checked<% end %>>
</div> </div>
<hr> <hr>
@ -356,7 +356,7 @@ we're going to need to do it here in order to allow for translations.
<b class="width:100%"><%= <b class="width:100%"><%=
views = rv["view_count"]?.try &.to_i? views = rv["view_count"]?.try &.to_i?
views ||= rv["view_count_short"]?.try { |x| short_text_to_number(x) } 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> %></b>
</div> </div>
</h5> </h5>

View File

@ -144,7 +144,7 @@ def get_ytimg_pool(subdomain)
if pool = YTIMG_POOLS[subdomain]? if pool = YTIMG_POOLS[subdomain]?
return pool return pool
else 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) pool = YoutubeConnectionPool.new(URI.parse("https://#{subdomain}.ytimg.com"), capacity: CONFIG.pool_size)
YTIMG_POOLS[subdomain] = pool YTIMG_POOLS[subdomain] = pool

View File

@ -452,7 +452,7 @@ private module Parsers
end end
content_container["items"]?.try &.as_a.each do |item| 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) contents << result if result.is_a?(SearchItem)
end end
@ -1017,13 +1017,9 @@ module HelperExtractors
end end
end end
module YoutubeJSONParser # Parses an item from Youtube's JSON response into a more usable structure.
extend self # The end result can either be a SearchVideo, SearchPlaylist or SearchChannel.
Log = ::Log.for(self) 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 # 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 # hassle of modifying every author_fallback and author_id_fallback arg usage
# which is more often than not nil. # 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 # 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. # applicable to itself. If not nil is returned and the next parser is attempted.
ITEM_PARSERS.each do |parser| 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) 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 return result
else else
Log.trace { "Parser \"#{parser.parser_name}\" does not apply. Cycling to the next one..." } Log.trace { "parse_item: Parser \"#{parser.parser_name}\" does not apply. Cycling to the next one..." }
end
end end
end end
end
# Parses multiple items from YouTube's initial JSON response into a more usable structure. # Parses multiple items from YouTube's initial JSON response into a more usable structure.
# The end result is an array of SearchItem. # The end result is an array of SearchItem.
# #
# This function yields the container so that items can be parsed separately. # This function yields the container so that items can be parsed separately.
# #
def extract_items(initial_data : InitialData, &) def extract_items(initial_data : InitialData, &)
if unpackaged_data = initial_data["contents"]?.try &.as_h if unpackaged_data = initial_data["contents"]?.try &.as_h
elsif unpackaged_data = initial_data["response"]?.try &.as_h elsif unpackaged_data = initial_data["response"]?.try &.as_h
elsif unpackaged_data = initial_data.dig?("onResponseReceivedActions", 1).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(). # This is identical to the parser cycling of parse_item().
ITEM_CONTAINER_EXTRACTOR.each do |extractor| 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) 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 # Extract items in container
container.each { |item| yield item } container.each { |item| yield item }
else else
Log.trace { "Extractor \"#{extractor.extractor_name}\" does not apply. Cycling to the next one..." } Log.trace { "extract_items: Extractor \"#{extractor.extractor_name}\" does not apply. Cycling to the next one..." }
end
end end
end end
end
# Wrapper using the block function above # Wrapper using the block function above
def extract_items( def extract_items(
initial_data : InitialData, initial_data : InitialData,
author_fallback : String? = nil, author_fallback : String? = nil,
author_id_fallback : String? = nil, author_id_fallback : String? = nil,
) : {Array(SearchItem), String?} ) : {Array(SearchItem), String?}
items = [] of SearchItem items = [] of SearchItem
continuation = nil continuation = nil
@ -1091,5 +1087,4 @@ module YoutubeJSONParser
end end
return items, continuation return items, continuation
end
end end

View File

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