From a7723e6ded3238c6b0d845da85bd9ae8777ab41f Mon Sep 17 00:00:00 2001 From: afrmtbl Date: Sat, 30 Mar 2019 21:18:34 -0400 Subject: [PATCH 001/210] Implement "fields" parameter from the YouTube Data API (#429) * Implement fields handling --- src/invidious/helpers/handlers.cr | 10 ++ src/invidious/helpers/json_filter.cr | 248 +++++++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 src/invidious/helpers/json_filter.cr diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr index 9d5ebd23..b613488a 100644 --- a/src/invidious/helpers/handlers.cr +++ b/src/invidious/helpers/handlers.cr @@ -97,6 +97,16 @@ class APIHandler < Kemal::Handler if env.response.headers["Content-Type"]?.try &.== "application/json" response = JSON.parse(response) + if env.params.query["fields"]? + fields_text = env.params.query["fields"] + begin + JSONFilter.filter(response, fields_text) + rescue ex + env.response.status_code = 400 + response = {"error" => ex.message} + end + end + if env.params.query["pretty"]? && env.params.query["pretty"] == "1" response = response.to_pretty_json else diff --git a/src/invidious/helpers/json_filter.cr b/src/invidious/helpers/json_filter.cr new file mode 100644 index 00000000..e4b57cea --- /dev/null +++ b/src/invidious/helpers/json_filter.cr @@ -0,0 +1,248 @@ +module JSONFilter + alias BracketIndex = Hash(Int64, Int64) + + alias GroupedFieldsValue = String | Array(GroupedFieldsValue) + alias GroupedFieldsList = Array(GroupedFieldsValue) + + class FieldsParser + class ParseError < Exception + end + + # Returns the `Regex` pattern used to match nest groups + def self.nest_group_pattern : Regex + # uses a '.' character to match json keys as they are allowed + # to contain any unicode codepoint + /(?:|,)(?[^,\n]*?)\(/ + end + + # Returns the `Regex` pattern used to check if there are any empty nest groups + def self.unnamed_nest_group_pattern : Regex + /^\(|\(\(|\/\(/ + end + + def self.parse_fields(fields_text : String) : Nil + if fields_text.empty? + raise FieldsParser::ParseError.new "Fields is empty" + end + + opening_bracket_count = fields_text.count('(') + closing_bracket_count = fields_text.count(')') + + if opening_bracket_count != closing_bracket_count + bracket_type = opening_bracket_count > closing_bracket_count ? "opening" : "closing" + raise FieldsParser::ParseError.new "There are too many #{bracket_type} brackets (#{opening_bracket_count}:#{closing_bracket_count})" + elsif match_result = unnamed_nest_group_pattern.match(fields_text) + raise FieldsParser::ParseError.new "Unnamed nest group at position #{match_result.begin}" + end + + # first, handle top-level single nested properties: items/id, playlistItems/snippet, etc + parse_single_nests(fields_text) { |nest_list| yield nest_list } + + # next, handle nest groups: items(id, etag, etc) + parse_nest_groups(fields_text) { |nest_list| yield nest_list } + end + + def self.parse_single_nests(fields_text : String) : Nil + single_nests = remove_nest_groups(fields_text) + + if !single_nests.empty? + property_nests = single_nests.split(',') + + property_nests.each do |nest| + nest_list = nest.split('/') + if nest_list.includes? "" + raise FieldsParser::ParseError.new "Empty key in nest list: #{nest_list}" + end + yield nest_list + end + # else + # raise FieldsParser::ParseError.new "Empty key in nest list 22: #{fields_text} | #{single_nests}" + end + end + + def self.parse_nest_groups(fields_text : String) : Nil + nest_stack = [] of NamedTuple(group_name: String, closing_bracket_index: Int64) + bracket_pairs = get_bracket_pairs(fields_text, true) + + text_index = 0 + regex_index = 0 + + while regex_result = self.nest_group_pattern.match(fields_text, regex_index) + raw_match = regex_result[0] + group_name = regex_result["groupname"] + + text_index = regex_result.begin + regex_index = regex_result.end + + if text_index.nil? || regex_index.nil? + raise FieldsParser::ParseError.new "Received invalid index while parsing nest groups: text_index: #{text_index} | regex_index: #{regex_index}" + end + + offset = raw_match.starts_with?(',') ? 1 : 0 + + opening_bracket_index = (text_index + group_name.size) + offset + closing_bracket_index = bracket_pairs[opening_bracket_index] + content_start = opening_bracket_index + 1 + + content = fields_text[content_start...closing_bracket_index] + + if content.empty? + raise FieldsParser::ParseError.new "Empty nest group at position #{content_start}" + else + content = remove_nest_groups(content) + end + + while nest_stack.size > 0 && closing_bracket_index > nest_stack[nest_stack.size - 1][:closing_bracket_index] + if nest_stack.size + nest_stack.pop + end + end + + group_name.split('/').each do |group_name| + nest_stack.push({ + group_name: group_name, + closing_bracket_index: closing_bracket_index, + }) + end + + if !content.empty? + properties = content.split(',') + + properties.each do |prop| + nest_list = nest_stack.map { |nest_prop| nest_prop[:group_name] } + + if !prop.empty? + if prop.includes?('/') + parse_single_nests(prop) { |list| nest_list += list } + else + nest_list.push prop + end + else + raise FieldsParser::ParseError.new "Empty key in nest list: #{nest_list << prop}" + end + + yield nest_list + end + end + end + end + + def self.remove_nest_groups(text : String) : String + content_bracket_pairs = get_bracket_pairs(text, false) + + content_bracket_pairs.each_key.to_a.reverse.each do |opening_bracket| + closing_bracket = content_bracket_pairs[opening_bracket] + last_comma = text.rindex(',', opening_bracket) || 0 + + text = text[0...last_comma] + text[closing_bracket + 1...text.size] + end + + return text.starts_with?(',') ? text[1...text.size] : text + end + + def self.get_bracket_pairs(text : String, recursive = true) : BracketIndex + istart = [] of Int64 + bracket_index = BracketIndex.new + + text.each_char_with_index do |char, index| + if char == '(' + istart.push(index.to_i64) + end + + if char == ')' + begin + opening = istart.pop + if recursive || (!recursive && istart.size == 0) + bracket_index[opening] = index.to_i64 + end + rescue + raise FieldsParser::ParseError.new "No matching opening parenthesis at: #{index}" + end + end + end + + if istart.size != 0 + idx = istart.pop + raise FieldsParser::ParseError.new "No matching closing parenthesis at: #{idx}" + end + + return bracket_index + end + end + + class FieldsGrouper + alias SkeletonValue = Hash(String, SkeletonValue) + + def self.create_json_skeleton(fields_text : String) : SkeletonValue + root_hash = {} of String => SkeletonValue + + FieldsParser.parse_fields(fields_text) do |nest_list| + current_item = root_hash + nest_list.each do |key| + if current_item[key]? + current_item = current_item[key] + else + current_item[key] = {} of String => SkeletonValue + current_item = current_item[key] + end + end + end + root_hash + end + + def self.create_grouped_fields_list(json_skeleton : SkeletonValue) : GroupedFieldsList + grouped_fields_list = GroupedFieldsList.new + json_skeleton.each do |key, value| + grouped_fields_list.push key + + nested_keys = create_grouped_fields_list(value) + grouped_fields_list.push nested_keys unless nested_keys.empty? + end + return grouped_fields_list + end + end + + class FilterError < Exception + end + + def self.filter(item : JSON::Any, fields_text : String, in_place : Bool = true) + skeleton = FieldsGrouper.create_json_skeleton(fields_text) + grouped_fields_list = FieldsGrouper.create_grouped_fields_list(skeleton) + filter(item, grouped_fields_list, in_place) + end + + def self.filter(item : JSON::Any, grouped_fields_list : GroupedFieldsList, in_place : Bool = true) : JSON::Any + item = item.clone unless in_place + + if !item.as_h? && !item.as_a? + raise FilterError.new "Can't filter '#{item}' by #{grouped_fields_list}" + end + + top_level_keys = Array(String).new + grouped_fields_list.each do |value| + if value.is_a? String + top_level_keys.push value + elsif value.is_a? Array + if !top_level_keys.empty? + key_to_filter = top_level_keys.last + + if item.as_h? + filter(item[key_to_filter], value, in_place: true) + elsif item.as_a? + item.as_a.each { |arr_item| filter(arr_item[key_to_filter], value, in_place: true) } + end + else + raise FilterError.new "Tried to filter while top level keys list is empty" + end + end + end + + if item.as_h? + item.as_h.select! top_level_keys + elsif item.as_a? + item.as_a.map { |value| filter(value, top_level_keys, in_place: true) } + end + + item + end +end From ab7e1b42bdd9750bb3f8f25d164a176f89127971 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 31 Mar 2019 22:07:06 -0500 Subject: [PATCH 002/210] Add '/api/v1/annotations/:id' --- src/invidious.cr | 66 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index f93f17e4..d579ff4e 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -70,11 +70,13 @@ PG_URL = URI.new( ) PG_DB = DB.open PG_URL -YT_URL = URI.parse("https://www.youtube.com") -REDDIT_URL = URI.parse("https://www.reddit.com") +ARCHIVE_URL = URI.parse("https://archive.org") LOGIN_URL = URI.parse("https://accounts.google.com") PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com") +REDDIT_URL = URI.parse("https://www.reddit.com") TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@protonmail.com.json") +YT_URL = URI.parse("https://www.youtube.com") +CHARS_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" CURRENT_BRANCH = {{ "#{`git branch | sed -n '/\* /s///p'`.strip}" }} CURRENT_COMMIT = {{ "#{`git rev-list HEAD --max-count=1 --abbrev-commit`.strip}" }} CURRENT_VERSION = {{ "#{`git describe --tags --abbrev=0`.strip}" }} @@ -2824,6 +2826,66 @@ get "/api/v1/insights/:id" do |env| next response.to_json end +get "/api/v1/annotations/:id" do |env| + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + + env.response.content_type = "text/xml" + + id = env.params.url["id"] + source = env.params.query["source"]? + source ||= "archive" + + if !id.match(/[a-zA-Z0-9_-]{11}/) + env.response.status_code = 400 + next + end + + annotations = "" + + case source + when "archive" + index = CHARS_SAFE.index(id[0]).not_nil!.to_s.rjust(2, '0') + + # IA doesn't handle leading hyphens, + # so we use https://archive.org/details/youtubeannotations_64 + if index == "62" + index = "64" + id = id.sub(/^-/, 'A') + end + + file = URI.escape("#{id[0, 3]}/#{id}.xml") + + client = make_client(ARCHIVE_URL) + location = client.get("/download/youtubeannotations_#{index}/#{id[0, 2]}.tar/#{file}") + + if !location.headers["Location"]? + env.response.status_code = location.status_code + end + + response = HTTP::Client.get(URI.parse(location.headers["Location"])) + + if response.status_code != 200 + env.response.status_code = response.status_code + next + end + + annotations = response.body + when "youtube" + client = make_client(YT_URL) + + response = client.get("/annotations_invideo?video_id=#{id}") + + if response.status_code != 200 + env.response.status_code = response.status_code + next + end + + annotations = response.body + end + + annotations +end + get "/api/v1/videos/:id" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? From 1fd7ff5655191385d68f3c65191ed70c0b2e6f0b Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Tue, 2 Apr 2019 08:51:28 -0500 Subject: [PATCH 003/210] Add scheme to author thumbnail --- src/invidious/helpers/helpers.cr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index ad744c74..33d7b6f4 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -200,6 +200,12 @@ def extract_items(nodeset, ucid = nil, author_name = nil) author_thumbnail = node.xpath_node(%q(.//div/span/img)).try &.["data-thumb"]? author_thumbnail ||= node.xpath_node(%q(.//div/span/img)).try &.["src"] + if author_thumbnail + author_thumbnail = URI.parse(author_thumbnail) + author_thumbnail.scheme = "https" + author_thumbnail = author_thumbnail.to_s + end + author_thumbnail ||= "" subscriber_count = node.xpath_node(%q(.//span[contains(@class, "yt-subscriber-count")])).try &.["title"].delete(",").to_i? From bd4f5ebcdfb2e4bcbe7872fc76cdbf26e7dd4f80 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 3 Apr 2019 11:35:58 -0500 Subject: [PATCH 004/210] Add option to configure default user preferences --- src/invidious.cr | 16 ++--- src/invidious/channels.cr | 4 +- src/invidious/helpers/helpers.cr | 104 ++++++++++++++++++++++++++----- src/invidious/helpers/macros.cr | 18 +++++- src/invidious/mixes.cr | 4 +- src/invidious/playlists.cr | 4 +- src/invidious/search.cr | 8 +-- src/invidious/users.cr | 102 ++++++++++++++++-------------- src/invidious/videos.cr | 22 +++---- 9 files changed, 192 insertions(+), 90 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index d579ff4e..e97198d8 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1198,22 +1198,22 @@ post "/preferences" do |env| local = local == "on" speed = env.params.body["speed"]?.try &.as(String).to_f? - speed ||= DEFAULT_USER_PREFERENCES.speed + speed ||= CONFIG.default_user_preferences.speed quality = env.params.body["quality"]?.try &.as(String) - quality ||= DEFAULT_USER_PREFERENCES.quality + quality ||= CONFIG.default_user_preferences.quality volume = env.params.body["volume"]?.try &.as(String).to_i? - volume ||= DEFAULT_USER_PREFERENCES.volume + volume ||= CONFIG.default_user_preferences.volume comments = [] of String 2.times do |i| - comments << (env.params.body["comments[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.comments[i]) + comments << (env.params.body["comments[#{i}]"]?.try &.as(String) || CONFIG.default_user_preferences.comments[i]) end captions = [] of String 3.times do |i| - captions << (env.params.body["captions[#{i}]"]?.try &.as(String) || DEFAULT_USER_PREFERENCES.captions[i]) + captions << (env.params.body["captions[#{i}]"]?.try &.as(String) || CONFIG.default_user_preferences.captions[i]) end related_videos = env.params.body["related_videos"]?.try &.as(String) @@ -1225,7 +1225,7 @@ post "/preferences" do |env| redirect_feed = redirect_feed == "on" locale = env.params.body["locale"]?.try &.as(String) - locale ||= DEFAULT_USER_PREFERENCES.locale + locale ||= CONFIG.default_user_preferences.locale dark_mode = env.params.body["dark_mode"]?.try &.as(String) dark_mode ||= "off" @@ -1236,10 +1236,10 @@ post "/preferences" do |env| thin_mode = thin_mode == "on" max_results = env.params.body["max_results"]?.try &.as(String).to_i? - max_results ||= DEFAULT_USER_PREFERENCES.max_results + max_results ||= CONFIG.default_user_preferences.max_results sort = env.params.body["sort"]?.try &.as(String) - sort ||= DEFAULT_USER_PREFERENCES.sort + sort ||= CONFIG.default_user_preferences.sort latest_only = env.params.body["latest_only"]?.try &.as(String) latest_only ||= "off" diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index b24159db..0a2f3503 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -1,5 +1,5 @@ struct InvidiousChannel - add_mapping({ + db_mapping({ id: String, author: String, updated: Time, @@ -9,7 +9,7 @@ struct InvidiousChannel end struct ChannelVideo - add_mapping({ + db_mapping({ id: String, title: String, published: Time, diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 33d7b6f4..f6c22f64 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -1,4 +1,76 @@ +require "./macros" + +struct ConfigPreferences + module StringToArray + def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder) + yaml.sequence do + value.each do |element| + yaml.scalar element + end + end + end + + def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Array(String) + begin + unless node.is_a?(YAML::Nodes::Sequence) + node.raise "Expected sequence, not #{node.class}" + end + + result = [] of String + node.nodes.each do + unless node.is_a?(YAML::Nodes::Scalar) + node.raise "Expected scalar, not #{node.class}" + end + + result << node.value + end + rescue ex + if node.is_a?(YAML::Nodes::Scalar) + result = [node.value, ""] + else + result = ["", ""] + end + end + + result + end + end + + yaml_mapping({ + autoplay: {type: Bool, default: false}, + captions: {type: Array(String), default: ["", "", ""], converter: StringToArray}, + comments: {type: Array(String), default: ["youtube", ""], converter: StringToArray}, + continue: {type: Bool, default: false}, + dark_mode: {type: Bool, default: false}, + latest_only: {type: Bool, default: false}, + listen: {type: Bool, default: false}, + local: {type: Bool, default: false}, + locale: {type: String, default: "en-US"}, + max_results: {type: Int32, default: 40}, + notifications_only: {type: Bool, default: false}, + quality: {type: String, default: "hd720"}, + redirect_feed: {type: Bool, default: false}, + related_videos: {type: Bool, default: true}, + sort: {type: String, default: "published"}, + speed: {type: Float32, default: 1.0_f32}, + thin_mode: {type: Bool, default: false}, + unseen_only: {type: Bool, default: false}, + video_loop: {type: Bool, default: false}, + volume: {type: Int32, default: 100}, + }) +end + struct Config + module ConfigPreferencesConverter + def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Preferences + Preferences.new(*ConfigPreferences.new(ctx, node).to_tuple) + end + + def self.to_yaml(value : Preferences, yaml : YAML::Nodes::Builder) + value.to_yaml(yaml) + end + end + YAML.mapping({ channel_threads: Int32, # Number of threads to use for crawling videos from channels (for updating subscriptions) feed_threads: Int32, # Number of threads to use for updating feeds @@ -9,20 +81,24 @@ user: String, port: Int32, dbname: String, ), - full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel - https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// - hmac_key: String?, # HMAC signing key for CSRF tokens and verifying pubsub subscriptions - domain: String?, # Domain to be used for links to resources on the site where an absolute URL is required - use_pubsub_feeds: {type: Bool, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) - default_home: {type: String, default: "Top"}, - feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending", "Subscriptions"]}, - top_enabled: {type: Bool, default: true}, - captcha_enabled: {type: Bool, default: true}, - login_enabled: {type: Bool, default: true}, - registration_enabled: {type: Bool, default: true}, - statistics_enabled: {type: Bool, default: false}, - admins: {type: Array(String), default: [] of String}, - external_port: {type: Int32 | Nil, default: nil}, + full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel + https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// + hmac_key: String?, # HMAC signing key for CSRF tokens and verifying pubsub subscriptions + domain: String?, # Domain to be used for links to resources on the site where an absolute URL is required + use_pubsub_feeds: {type: Bool, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) + default_home: {type: String, default: "Top"}, + feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending", "Subscriptions"]}, + top_enabled: {type: Bool, default: true}, + captcha_enabled: {type: Bool, default: true}, + login_enabled: {type: Bool, default: true}, + registration_enabled: {type: Bool, default: true}, + statistics_enabled: {type: Bool, default: false}, + admins: {type: Array(String), default: [] of String}, + external_port: {type: Int32?, default: nil}, + default_user_preferences: {type: Preferences, + default: Preferences.new(*ConfigPreferences.from_yaml("").to_tuple), + converter: ConfigPreferencesConverter, + }, }) end diff --git a/src/invidious/helpers/macros.cr b/src/invidious/helpers/macros.cr index 5991faaf..fda34ed7 100644 --- a/src/invidious/helpers/macros.cr +++ b/src/invidious/helpers/macros.cr @@ -1,4 +1,4 @@ -macro add_mapping(mapping) +macro db_mapping(mapping) def initialize({{*mapping.keys.map { |id| "@#{id}".id }}}) end @@ -18,6 +18,22 @@ macro json_mapping(mapping) end JSON.mapping({{mapping}}) + YAML.mapping({{mapping}}) +end + +macro yaml_mapping(mapping) + def initialize({{*mapping.keys.map { |id| "@#{id}".id }}}) + end + + def to_a + return [{{*mapping.keys.map { |id| "@#{id}".id }}}] + end + + def to_tuple + return { {{*mapping.keys.map { |id| "@#{id}".id }}} } + end + + YAML.mapping({{mapping}}) end macro templated(filename, template = "template") diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index d290ac14..65e03a06 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -1,5 +1,5 @@ struct MixVideo - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -11,7 +11,7 @@ struct MixVideo end struct Mix - add_mapping({ + db_mapping({ title: String, id: String, videos: Array(MixVideo), diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 027bcae5..d04a5942 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -1,5 +1,5 @@ struct PlaylistVideo - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -13,7 +13,7 @@ struct PlaylistVideo end struct Playlist - add_mapping({ + db_mapping({ title: String, id: String, author: String, diff --git a/src/invidious/search.cr b/src/invidious/search.cr index 04eb63a2..9c985552 100644 --- a/src/invidious/search.cr +++ b/src/invidious/search.cr @@ -1,5 +1,5 @@ struct SearchVideo - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -17,7 +17,7 @@ struct SearchVideo end struct SearchPlaylistVideo - add_mapping({ + db_mapping({ title: String, id: String, length_seconds: Int32, @@ -25,7 +25,7 @@ struct SearchPlaylistVideo end struct SearchPlaylist - add_mapping({ + db_mapping({ title: String, id: String, author: String, @@ -37,7 +37,7 @@ struct SearchPlaylist end struct SearchChannel - add_mapping({ + db_mapping({ author: String, ucid: String, author_thumbnail: String, diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 9140378e..80fc496c 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -11,7 +11,7 @@ struct User end end - add_mapping({ + db_mapping({ updated: Time, notifications: Array(String), subscriptions: Array(String), @@ -26,29 +26,6 @@ struct User }) end -DEFAULT_USER_PREFERENCES = Preferences.new( - video_loop: false, - autoplay: false, - continue: false, - local: false, - listen: false, - speed: 1.0_f32, - quality: "hd720", - volume: 100, - comments: ["youtube", ""], - captions: ["", "", ""], - related_videos: true, - redirect_feed: false, - locale: "en-US", - dark_mode: false, - thin_mode: false, - max_results: 40, - sort: "published", - latest_only: false, - unseen_only: false, - notifications_only: false, -) - struct Preferences module StringToArray def self.to_json(value : Array(String), json : JSON::Builder) @@ -71,29 +48,62 @@ struct Preferences result end + + def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder) + yaml.sequence do + value.each do |element| + yaml.scalar element + end + end + end + + def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Array(String) + begin + unless node.is_a?(YAML::Nodes::Sequence) + node.raise "Expected sequence, not #{node.class}" + end + + result = [] of String + node.nodes.each do + unless node.is_a?(YAML::Nodes::Scalar) + node.raise "Expected scalar, not #{node.class}" + end + + result << node.value + end + rescue ex + if node.is_a?(YAML::Nodes::Scalar) + result = [node.value, ""] + else + result = ["", ""] + end + end + + result + end end json_mapping({ - video_loop: {type: Bool, default: DEFAULT_USER_PREFERENCES.video_loop}, - autoplay: {type: Bool, default: DEFAULT_USER_PREFERENCES.autoplay}, - continue: {type: Bool, default: DEFAULT_USER_PREFERENCES.continue}, - local: {type: Bool, default: DEFAULT_USER_PREFERENCES.local}, - listen: {type: Bool, default: DEFAULT_USER_PREFERENCES.listen}, - speed: {type: Float32, default: DEFAULT_USER_PREFERENCES.speed}, - quality: {type: String, default: DEFAULT_USER_PREFERENCES.quality}, - volume: {type: Int32, default: DEFAULT_USER_PREFERENCES.volume}, - comments: {type: Array(String), default: DEFAULT_USER_PREFERENCES.comments, converter: StringToArray}, - captions: {type: Array(String), default: DEFAULT_USER_PREFERENCES.captions, converter: StringToArray}, - redirect_feed: {type: Bool, default: DEFAULT_USER_PREFERENCES.redirect_feed}, - related_videos: {type: Bool, default: DEFAULT_USER_PREFERENCES.related_videos}, - dark_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.dark_mode}, - thin_mode: {type: Bool, default: DEFAULT_USER_PREFERENCES.thin_mode}, - max_results: {type: Int32, default: DEFAULT_USER_PREFERENCES.max_results}, - sort: {type: String, default: DEFAULT_USER_PREFERENCES.sort}, - latest_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.latest_only}, - unseen_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.unseen_only}, - notifications_only: {type: Bool, default: DEFAULT_USER_PREFERENCES.notifications_only}, - locale: {type: String, default: DEFAULT_USER_PREFERENCES.locale}, + autoplay: {type: Bool, default: CONFIG.default_user_preferences.autoplay}, + captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: StringToArray}, + comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: StringToArray}, + continue: {type: Bool, default: CONFIG.default_user_preferences.continue}, + dark_mode: {type: Bool, default: CONFIG.default_user_preferences.dark_mode}, + latest_only: {type: Bool, default: CONFIG.default_user_preferences.latest_only}, + listen: {type: Bool, default: CONFIG.default_user_preferences.listen}, + local: {type: Bool, default: CONFIG.default_user_preferences.local}, + locale: {type: String, default: CONFIG.default_user_preferences.locale}, + max_results: {type: Int32, default: CONFIG.default_user_preferences.max_results}, + notifications_only: {type: Bool, default: CONFIG.default_user_preferences.notifications_only}, + quality: {type: String, default: CONFIG.default_user_preferences.quality}, + redirect_feed: {type: Bool, default: CONFIG.default_user_preferences.redirect_feed}, + related_videos: {type: Bool, default: CONFIG.default_user_preferences.related_videos}, + sort: {type: String, default: CONFIG.default_user_preferences.sort}, + speed: {type: Float32, default: CONFIG.default_user_preferences.speed}, + thin_mode: {type: Bool, default: CONFIG.default_user_preferences.thin_mode}, + unseen_only: {type: Bool, default: CONFIG.default_user_preferences.unseen_only}, + video_loop: {type: Bool, default: CONFIG.default_user_preferences.video_loop}, + volume: {type: Int32, default: CONFIG.default_user_preferences.volume}, }) end @@ -174,7 +184,7 @@ def fetch_user(sid, headers, db) token = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) - user = User.new(Time.now, [] of String, channels, email, DEFAULT_USER_PREFERENCES, nil, token, [] of String) + user = User.new(Time.now, [] of String, channels, email, CONFIG.default_user_preferences, nil, token, [] of String) return user, sid end @@ -182,7 +192,7 @@ def create_user(sid, email, password) password = Crypto::Bcrypt::Password.create(password, cost: 10) token = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) - user = User.new(Time.now, [] of String, [] of String, email, DEFAULT_USER_PREFERENCES, password.to_s, token, [] of String) + user = User.new(Time.now, [] of String, [] of String, email, CONFIG.default_user_preferences, password.to_s, token, [] of String) return user, sid end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 0e9cf6c6..856b6447 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -521,7 +521,7 @@ struct Video return self.info["length_seconds"].to_i end - add_mapping({ + db_mapping({ id: String, info: { type: HTTP::Params, @@ -818,16 +818,16 @@ def process_video_params(query, preferences) volume ||= preferences.volume end - autoplay ||= DEFAULT_USER_PREFERENCES.autoplay.to_unsafe - continue ||= DEFAULT_USER_PREFERENCES.continue.to_unsafe - listen ||= DEFAULT_USER_PREFERENCES.listen.to_unsafe - local ||= DEFAULT_USER_PREFERENCES.local.to_unsafe - preferred_captions ||= DEFAULT_USER_PREFERENCES.captions - quality ||= DEFAULT_USER_PREFERENCES.quality - related_videos ||= DEFAULT_USER_PREFERENCES.related_videos.to_unsafe - speed ||= DEFAULT_USER_PREFERENCES.speed - video_loop ||= DEFAULT_USER_PREFERENCES.video_loop.to_unsafe - volume ||= DEFAULT_USER_PREFERENCES.volume + autoplay ||= CONFIG.default_user_preferences.autoplay.to_unsafe + continue ||= CONFIG.default_user_preferences.continue.to_unsafe + listen ||= CONFIG.default_user_preferences.listen.to_unsafe + local ||= CONFIG.default_user_preferences.local.to_unsafe + preferred_captions ||= CONFIG.default_user_preferences.captions + quality ||= CONFIG.default_user_preferences.quality + related_videos ||= CONFIG.default_user_preferences.related_videos.to_unsafe + speed ||= CONFIG.default_user_preferences.speed + video_loop ||= CONFIG.default_user_preferences.video_loop.to_unsafe + volume ||= CONFIG.default_user_preferences.volume autoplay = autoplay == 1 continue = continue == 1 From f6615a490d830b69e3ead47e188e175912d1775d Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 3 Apr 2019 14:53:28 -0500 Subject: [PATCH 005/210] Allow disabling download widget for specific videos (in compliance with DMCA) --- src/invidious/helpers/helpers.cr | 1 + src/invidious/views/watch.ecr | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index f6c22f64..792fec11 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -99,6 +99,7 @@ user: String, default: Preferences.new(*ConfigPreferences.from_yaml("").to_tuple), converter: ConfigPreferencesConverter, }, + dmca_content: {type: Array(String), default: [] of String}, # For compliance with DMCA, disables download widget using list of video IDs }) end diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 2aa43623..18cbdef7 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -57,6 +57,9 @@

<%= translate(locale, "Watch video on Youtube") %>

+ <% if CONFIG.dmca_content.includes? video.id %> +

Download is disabled.

+ <% else %>
@@ -83,6 +86,7 @@ <%= translate(locale, "Download") %> + <% end %>

<%= number_with_separator(video.views) %>

<%= number_with_separator(video.likes) %>

From 5ef288b840b0308ada72f625b09d15565fe5b0d0 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 3 Apr 2019 18:42:12 -0500 Subject: [PATCH 006/210] Add 'sort_by' to /api/v1/comments --- spec/helpers_spec.cr | 28 ++++++++- src/invidious.cr | 5 +- src/invidious/comments.cr | 121 +++++++++++++++++++++++++++++++++++--- src/invidious/videos.cr | 8 --- 4 files changed, 143 insertions(+), 19 deletions(-) diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr index 6f33babd..307e52d2 100644 --- a/spec/helpers_spec.cr +++ b/spec/helpers_spec.cr @@ -4,8 +4,10 @@ require "spec" require "yaml" require "../src/invidious/helpers/*" require "../src/invidious/channels" +require "../src/invidious/comments" require "../src/invidious/playlists" require "../src/invidious/search" +require "../src/invidious/users" describe "Helpers" do describe "#produce_channel_videos_url" do @@ -16,9 +18,7 @@ describe "Helpers" do produce_channel_videos_url(ucid: "UCXuqSBlHAE6Xw-yeJA0Tunw", page: 20).should eq("/browse_ajax?continuation=4qmFsgJEEhhVQ1h1cVNCbEhBRTZYdy15ZUpBMFR1bncaKEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0l5TUElM0QlM0Q%3D&gl=US&hl=en") - produce_channel_videos_url(ucid: "UC-9-kyTW8ZkZNDHQJ6FgpwQ", auto_generated: true).should eq("/browse_ajax?continuation=4qmFsgJIEhhVQy05LWt5VFc4WmtaTkRIUUo2Rmdwd1EaLEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQTJlZ294TlRVeU1ESXlPVFE1&gl=US&hl=en") - - produce_channel_videos_url(ucid: "UC-9-kyTW8ZkZNDHQJ6FgpwQ", page: 20, auto_generated: true, sort_by: "popular").should eq("/browse_ajax?continuation=4qmFsgJOEhhVQy05LWt5VFc4WmtaTkRIUUo2Rmdwd1EaMkVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQTJlZ294TlRBeU1UY3dNVFE1R0FFJTNE&gl=US&hl=en") + produce_channel_videos_url(ucid: "UC-9-kyTW8ZkZNDHQJ6FgpwQ", page: 20, sort_by: "popular").should eq("/browse_ajax?continuation=4qmFsgJAEhhVQy05LWt5VFc4WmtaTkRIUUo2Rmdwd1EaJEVnWjJhV1JsYjNNd0FqZ0JZQUZxQUxnQkFDQUFlZ0l5TUJnQg%3D%3D&gl=US&hl=en") end end @@ -59,4 +59,26 @@ describe "Helpers" do produce_search_params(content_type: "channel").should eq("CAASAhAC") end end + + describe "#produce_comment_continuation" do + it "correctly produces a continuation token for comments" do + produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA").should eq("EiYSC19jRTh4U3U2c3dFwAEByAEB4AEBogINKP___________wFAABgGMowDCvYCQURTSl9pMnF2SmVGdEwwaHRtUzVfSzVDdGozZUdGVkJNV0w5V2Q0Mm8za21VTDZfbUF6ZExwODUtbGlRWkwwbVlyXzE2QmhhZ2dVcVg2NTJTdjlKcVY2VlhpblNoU1AtWlQ2ckw0Tm9sUEJhUFhWdEpzTzVfckFfcUUzR3ViQXVMRnc5dXpJSVhVMi1IbnBYYmRnUExXVEZhdmZYMjA2aHFXbW1wSHdVT3JteFFWX09YNnRZa00zdXgzclBBS0NEclQ4ZVdMN01VM2JMaU5jbmJna1c4bzBoOEtZTExfOEJQYThMY0hiVHY4cEFvTmtqZXJsWDF4N0s0cHF4YVhQb3l6ODlxTmxuaDZyUng2QVhnQXp6b0hIMWRtY3lROENJQmVPSGctbTRpOFp4ZFg0ZFA4OFhXcklGZy1qSkdocEdQOEpVTURnWmdhdnhWeDIyNWhVRVlaTXlyTEdsZXI1ZW00RmdiRzYyWVdDNTFtb0xETGVZRUEiDyILX2NFOHhTdTZzd0UwACgU") + + produce_comment_continuation("_cE8xSu6swE", "ADSJ_i1yz21HI4xrtsYXVC-2_kfZ6kx1yjYQumXAAxqH3CAd7ZxKxfLdZS1__fqhCtOASRbbpSBGH_tH1J96Dxux-Qfjk-lUbupMqv08Q3aHzGu7p70VoUMHhI2-GoJpnbpmcOxkGzeIuenRS_ym2Y8fkDowhqLPFgsS0n4djnZ2UmC17F3Ch3N1S1UYf1ZVOc991qOC1iW9kJDzyvRQTWCPsJUPneSaAKW-Rr97pdesOkR4i8cNvHZRnQKe2HEfsvlJOb2C3lF1dJBfJeNfnQYeh5hv6_fZN7bt3-JL1Xk3Qc9NXNxmmbDpwAC_yFR8dthFfUJdyIO9Nu1D79MLYeR-H5HxqUJokkJiGIz4lTE_CXXbhAI").should eq("EiYSC19jRTh4U3U2c3dFwAEByAEB4AEBogINKP___________wFAABgGMokDCvMCQURTSl9pMXl6MjFISTR4cnRzWVhWQy0yX2tmWjZreDF5allRdW1YQUF4cUgzQ0FkN1p4S3hmTGRaUzFfX2ZxaEN0T0FTUmJicFNCR0hfdEgxSjk2RHh1eC1RZmprLWxVYnVwTXF2MDhRM2FIekd1N3A3MFZvVU1IaEkyLUdvSnBuYnBtY094a0d6ZUl1ZW5SU195bTJZOGZrRG93aHFMUEZnc1MwbjRkam5aMlVtQzE3RjNDaDNOMVMxVVlmMVpWT2M5OTFxT0MxaVc5a0pEenl2UlFUV0NQc0pVUG5lU2FBS1ctUnI5N3BkZXNPa1I0aThjTnZIWlJuUUtlMkhFZnN2bEpPYjJDM2xGMWRKQmZKZU5mblFZZWg1aHY2X2ZaTjdidDMtSkwxWGszUWM5TlhOeG1tYkRwd0FDX3lGUjhkdGhGZlVKZHlJTzlOdTFENzlNTFllUi1INUh4cVVKb2trSmlHSXo0bFRFX0NYWGJoQUkiDyILX2NFOHhTdTZzd0UwACgU") + + produce_comment_continuation("29-q7YnyUmY", "").should eq("EiYSCzI5LXE3WW55VW1ZwAEByAEB4AEBogINKP___________wFAABgGMhMiDyILMjktcTdZbnlVbVkwAHgC") + + produce_comment_continuation("CvFH_6DNRCY", "").should eq("EiYSC0N2RkhfNkROUkNZwAEByAEB4AEBogINKP___________wFAABgGMhMiDyILQ3ZGSF82RE5SQ1kwAHgC") + end + end + + describe "#produce_comment_reply_continuation" do + it "correctly produces a continuation token for replies to a given comment" do + produce_comment_reply_continuation("cIHQWOoJeag", "UCq6VFHwMzcMXbuKyG7SQYIg", "Ugx1IP_wGVv3WtGWcdV4AaABAg").should eq("EiYSC2NJSFFXT29KZWFnwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd4MUlQX3dHVnYzV3RHV2NkVjRBYUFCQWciAggAKhhVQ3E2VkZId016Y01YYnVLeUc3U1FZSWcyC2NJSFFXT29KZWFnQAFICg%3D%3D") + + produce_comment_reply_continuation("cIHQWOoJeag", "UCq6VFHwMzcMXbuKyG7SQYIg", "Ugza62y_TlmTu9o2RfF4AaABAg").should eq("EiYSC2NJSFFXT29KZWFnwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd6YTYyeV9UbG1UdTlvMlJmRjRBYUFCQWciAggAKhhVQ3E2VkZId016Y01YYnVLeUc3U1FZSWcyC2NJSFFXT29KZWFnQAFICg%3D%3D") + + produce_comment_reply_continuation("_cE8xSu6swE", "UC1AZY74-dGVPe6bfxFwwEMg", "UgyBUaRGHB9Jmt1dsUZ4AaABAg").should eq("EiYSC19jRTh4U3U2c3dFwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd5QlVhUkdIQjlKbXQxZHNVWjRBYUFCQWciAggAKhhVQzFBWlk3NC1kR1ZQZTZiZnhGd3dFTWcyC19jRTh4U3U2c3dFQAFICg%3D%3D") + end + end end diff --git a/src/invidious.cr b/src/invidious.cr index e97198d8..8494f9d9 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2691,11 +2691,14 @@ get "/api/v1/comments/:id" do |env| format = env.params.query["format"]? format ||= "json" + sort_by = env.params.query["sort_by"]?.try &.downcase + sort_by ||= "top" + continuation = env.params.query["continuation"]? if source == "youtube" begin - comments = fetch_youtube_comments(id, PG_DB, continuation, proxies, format, locale, thin_mode, region) + comments = fetch_youtube_comments(id, PG_DB, continuation, proxies, format, locale, thin_mode, region, sort_by: sort_by) rescue ex error_message = {"error" => ex.message}.to_json env.response.status_code = 500 diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index b5133cf2..aa3233d4 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -56,15 +56,14 @@ class RedditListing }) end -def fetch_youtube_comments(id, db, continuation, proxies, format, locale, thin_mode, region) +def fetch_youtube_comments(id, db, continuation, proxies, format, locale, thin_mode, region, sort_by = "top") video = get_video(id, db, proxies, region: region) - session_token = video.info["session_token"]? - itct = video.info["itct"]? - ctoken = video.info["ctoken"]? + + ctoken = produce_comment_continuation(id, cursor: "", sort_by: sort_by) continuation ||= ctoken - if !continuation || !itct || !session_token + if !continuation || !session_token if format == "json" return {"comments" => [] of String}.to_json else @@ -73,7 +72,7 @@ def fetch_youtube_comments(id, db, continuation, proxies, format, locale, thin_m end post_req = { - "session_token" => session_token.not_nil!, + "session_token" => session_token, } post_req = HTTP::Params.encode(post_req) @@ -90,7 +89,7 @@ def fetch_youtube_comments(id, db, continuation, proxies, format, locale, thin_m headers["x-youtube-client-name"] = "1" headers["x-youtube-client-version"] = "2.20180719" - response = client.post("/comment_service_ajax?action_get_comments=1&pbj=1&ctoken=#{continuation}&continuation=#{continuation}&itct=#{itct}&hl=en&gl=US", headers, post_req) + response = client.post("/comment_service_ajax?action_get_comments=1&ctoken=#{continuation}&continuation=#{continuation}&hl=en&gl=US", headers, post_req) response = JSON.parse(response.body) if !response["response"]["continuationContents"]? @@ -516,3 +515,111 @@ def content_to_comment_html(content) return comment_html end + +def produce_comment_continuation(video_id, cursor = "", sort_by = "top") + continuation = IO::Memory.new + + continuation.write(Bytes[0x12, 0x26]) + + continuation.write(Bytes[0x12, video_id.size]) + continuation.print(video_id) + + continuation.write(Bytes[0xc0, 0x01, 0x01]) + continuation.write(Bytes[0xc8, 0x01, 0x01]) + continuation.write(Bytes[0xe0, 0x01, 0x01]) + + continuation.write(Bytes[0xa2, 0x02, 0x0d]) + continuation.write(Bytes[0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]) + + continuation.write(Bytes[0x40, 0x00]) + continuation.write(Bytes[0x18, 0x06]) + + if cursor.empty? + continuation.write(Bytes[0x32]) + continuation.write(write_var_int(video_id.size + 8)) + + continuation.write(Bytes[0x22, video_id.size + 4]) + continuation.write(Bytes[0x22, video_id.size]) + continuation.print(video_id) + + case sort_by + when "top" + continuation.write(Bytes[0x30, 0x00]) + when "new", "newest" + continuation.write(Bytes[0x30, 0x01]) + end + + continuation.write(Bytes[0x78, 0x02]) + else + continuation.write(Bytes[0x32]) + continuation.write(write_var_int(cursor.size + video_id.size + 11)) + + continuation.write(Bytes[0x0a]) + continuation.write(write_var_int(cursor.size)) + continuation.print(cursor) + + continuation.write(Bytes[0x22, video_id.size + 4]) + continuation.write(Bytes[0x22, video_id.size]) + continuation.print(video_id) + + case sort_by + when "top" + continuation.write(Bytes[0x30, 0x00]) + when "new", "newest" + continuation.write(Bytes[0x30, 0x01]) + end + + continuation.write(Bytes[0x28, 0x14]) + end + + continuation.rewind + continuation = continuation.gets_to_end + + continuation = Base64.urlsafe_encode(continuation.to_slice) + continuation = URI.escape(continuation) + + return continuation +end + +def produce_comment_reply_continuation(video_id, ucid, comment_id) + continuation = IO::Memory.new + + continuation.write(Bytes[0x12, 0x26]) + + continuation.write(Bytes[0x12, video_id.size]) + continuation.print(video_id) + + continuation.write(Bytes[0xc0, 0x01, 0x01]) + continuation.write(Bytes[0xc8, 0x01, 0x01]) + continuation.write(Bytes[0xe0, 0x01, 0x01]) + + continuation.write(Bytes[0xa2, 0x02, 0x0d]) + continuation.write(Bytes[0x28, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]) + + continuation.write(Bytes[0x40, 0x00]) + continuation.write(Bytes[0x18, 0x06]) + + continuation.write(Bytes[0x32, ucid.size + video_id.size + comment_id.size + 16]) + continuation.write(Bytes[0x1a, ucid.size + video_id.size + comment_id.size + 14]) + + continuation.write(Bytes[0x12, comment_id.size]) + continuation.print(comment_id) + + continuation.write(Bytes[0x22, 0x02, 0x08, 0x00]) # ?? + + continuation.write(Bytes[ucid.size + video_id.size + 7]) + continuation.write(Bytes[ucid.size]) + continuation.print(ucid) + continuation.write(Bytes[0x32, video_id.size]) + continuation.print(video_id) + continuation.write(Bytes[0x40, 0x01]) + continuation.write(Bytes[0x48, 0x0a]) + + continuation.rewind + continuation = continuation.gets_to_end + + continuation = Base64.urlsafe_encode(continuation.to_slice) + continuation = URI.escape(continuation) + + return continuation +end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 856b6447..3887277a 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -608,14 +608,6 @@ def extract_player_config(body, html) params["session_token"] = md["session_token"] end - if md = body.match(/itct=(?[^"]+)"/) - params["itct"] = md["itct"] - end - - if md = body.match(/'COMMENTS_TOKEN': "(?[^"]+)"/) - params["ctoken"] = md["ctoken"] - end - if md = body.match(/'RELATED_PLAYER_ARGS': (?{"rvs":"[^"]+"})/) params["rvs"] = JSON.parse(md["rvs"])["rvs"].as_s end From 31312747e9052b0d856db37b53cc37e7aa2806a3 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 3 Apr 2019 19:04:33 -0500 Subject: [PATCH 007/210] Fix from_yaml in ConfigPreferences --- src/invidious/helpers/helpers.cr | 8 ++++---- src/invidious/users.cr | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 792fec11..1fe41f80 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -17,12 +17,12 @@ struct ConfigPreferences end result = [] of String - node.nodes.each do - unless node.is_a?(YAML::Nodes::Scalar) - node.raise "Expected scalar, not #{node.class}" + node.nodes.each do |item| + unless item.is_a?(YAML::Nodes::Scalar) + node.raise "Expected scalar, not #{item.class}" end - result << node.value + result << item.value end rescue ex if node.is_a?(YAML::Nodes::Scalar) diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 80fc496c..40f24870 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -64,12 +64,12 @@ struct Preferences end result = [] of String - node.nodes.each do - unless node.is_a?(YAML::Nodes::Scalar) - node.raise "Expected scalar, not #{node.class}" + node.nodes.each do |item| + unless item.is_a?(YAML::Nodes::Scalar) + node.raise "Expected scalar, not #{item.class}" end - result << node.value + result << item.value end rescue ex if node.is_a?(YAML::Nodes::Scalar) From 305d63621777a76af7cab25e900cef179e69070c Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 4 Apr 2019 07:49:53 -0500 Subject: [PATCH 008/210] Add multithreading to pubsub job --- src/invidious.cr | 6 +++-- src/invidious/helpers/helpers.cr | 10 ++++----- src/invidious/jobs.cr | 38 ++++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 8494f9d9..0cffc9af 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2344,7 +2344,8 @@ get "/feed/webhook/:token" do |env| data = "#{time}" end - # The hub will sometimes check if we're still subscribed after delivery errors + # The hub will sometimes check if we're still subscribed after delivery errors, + # so we reply with a 200 as long as the request hasn't expired if Time.now.to_unix - time.to_i > 432000 env.response.status_code = 400 next @@ -2377,11 +2378,12 @@ post "/feed/webhook/:token" do |env| rss = XML.parse_html(body) rss.xpath_nodes("//feed/entry").each do |entry| id = entry.xpath_node("videoid").not_nil!.content + author = entry.xpath_node("author/name").not_nil!.content published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content) updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) video = get_video(id, PG_DB, proxies, region: nil) - video = ChannelVideo.new(id, video.title, published, updated, video.ucid, video.author, video.length_seconds, video.live_now, video.premiere_timestamp) + video = ChannelVideo.new(id, video.title, published, updated, video.ucid, author, video.length_seconds, video.live_now, video.premiere_timestamp) PG_DB.exec("UPDATE users SET notifications = notifications || $1 \ WHERE updated < $2 AND $3 = ANY(subscriptions) AND $1 <> ALL(notifications)", video.id, video.published, video.ucid) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 1fe41f80..3b53a468 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -81,11 +81,11 @@ user: String, port: Int32, dbname: String, ), - full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel - https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// - hmac_key: String?, # HMAC signing key for CSRF tokens and verifying pubsub subscriptions - domain: String?, # Domain to be used for links to resources on the site where an absolute URL is required - use_pubsub_feeds: {type: Bool, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) + full_refresh: Bool, # Used for crawling channels: threads should check all videos uploaded by a channel + https_only: Bool?, # Used to tell Invidious it is behind a proxy, so links to resources should be https:// + hmac_key: String?, # HMAC signing key for CSRF tokens and verifying pubsub subscriptions + domain: String?, # Domain to be used for links to resources on the site where an absolute URL is required + use_pubsub_feeds: {type: Bool | Int32, default: false}, # Subscribe to channels using PubSubHubbub (requires domain, hmac_key) default_home: {type: String, default: "Top"}, feed_menu: {type: Array(String), default: ["Popular", "Top", "Trending", "Subscriptions"]}, top_enabled: {type: Bool, default: true}, diff --git a/src/invidious/jobs.cr b/src/invidious/jobs.cr index a217c1af..82e58c93 100644 --- a/src/invidious/jobs.cr +++ b/src/invidious/jobs.cr @@ -104,15 +104,43 @@ end def subscribe_to_feeds(db, logger, key, config) if config.use_pubsub_feeds + case config.use_pubsub_feeds + when Bool + max_threads = config.use_pubsub_feeds.as(Bool).to_unsafe + when Int32 + max_threads = config.use_pubsub_feeds.as(Int32) + end + max_channel = Channel(Int32).new + spawn do + max_threads = max_channel.receive + active_threads = 0 + active_channel = Channel(Bool).new + loop do - db.query_all("SELECT id FROM channels WHERE CURRENT_TIMESTAMP - subscribed > '4 days' OR subscribed IS NULL") do |rs| + db.query_all("SELECT id FROM channels WHERE CURRENT_TIMESTAMP - subscribed > interval '4 days' OR subscribed IS NULL") do |rs| rs.each do ucid = rs.read(String) - response = subscribe_pubsub(ucid, key, config) - if response.status_code >= 400 - logger.write("#{ucid} : #{response.body}\n") + if active_threads >= max_threads.as(Int32) + if active_channel.receive + active_threads -= 1 + end + end + + active_threads += 1 + + spawn do + begin + response = subscribe_pubsub(ucid, key, config) + + if response.status_code >= 400 + logger.write("#{ucid} : #{response.body}\n") + end + rescue ex + end + + active_channel.send(true) end end end @@ -120,6 +148,8 @@ def subscribe_to_feeds(db, logger, key, config) sleep 1.minute end end + + max_channel.send(max_threads.as(Int32)) end end From c728214af7c437ab38cc2dd99f88a06b18056cbc Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 4 Apr 2019 14:49:32 -0500 Subject: [PATCH 009/210] Fix batch importing of channels --- src/invidious/channels.cr | 41 +++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index 0a2f3503..0eeb8a8b 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -23,26 +23,37 @@ struct ChannelVideo end def get_batch_channels(channels, db, refresh = false, pull_all_videos = true, max_threads = 10) - active_threads = 0 - active_channel = Channel(String | Nil).new + finished_channel = Channel(String | Nil).new - final = [] of String - channels.map do |ucid| - if active_threads >= max_threads - if response = active_channel.receive + spawn do + active_threads = 0 + active_channel = Channel(Nil).new + + channels.each do |ucid| + if active_threads >= max_threads + active_channel.receive active_threads -= 1 - final << response + end + + active_threads += 1 + spawn do + begin + get_channel(ucid, db, refresh, pull_all_videos) + finished_channel.send(ucid) + rescue ex + finished_channel.send(nil) + ensure + active_channel.send(nil) + end end end + end - active_threads += 1 - spawn do - begin - get_channel(ucid, db, refresh, pull_all_videos) - active_channel.send(ucid) - rescue ex - active_channel.send(nil) - end + final = [] of String + channels.size.times do + ucid = finished_channel.receive + if ucid + final << ucid end end From b82fb58dc44b6919e2bebc0995c637d25c7a97e7 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 4 Apr 2019 15:05:54 -0500 Subject: [PATCH 010/210] Fix typo in handling 'controls' param --- src/invidious/videos.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 3887277a..10120c4e 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -851,7 +851,7 @@ def process_video_params(query, preferences) controls = query["controls"]?.try &.to_i? controls ||= 1 - controls = controls == 1 + controls = controls >= 1 params = { autoplay: autoplay, From 59744a96fab8333b7eef7fa2c6546db5c3b357a2 Mon Sep 17 00:00:00 2001 From: micrococo Date: Sat, 6 Apr 2019 00:13:25 +0200 Subject: [PATCH 011/210] Add Spanish translation (#466) * Add Spanish translation --- locales/es.json | 295 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 locales/es.json diff --git a/locales/es.json b/locales/es.json new file mode 100644 index 00000000..67d4d5d3 --- /dev/null +++ b/locales/es.json @@ -0,0 +1,295 @@ +{ + "`x` subscribers": "`x` suscriptores", + "`x` videos": "`x` vídeos", + "LIVE": "DIRECTO", + "Shared `x` ago": "Compartido hace `x`", + "Unsubscribe": "Desuscribirse", + "Subscribe": "Suscribirse", + "Login to subscribe to `x`": "Inicie sesión para suscribirse a `x`", + "View channel on YouTube": "Ver el canal en YouTube", + "newest": "más nuevos", + "oldest": "más viejos", + "popular": "populares", + "last": "último", + "Next page": "Página siguiente", + "Previous page": "Página anterior", + "Clear watch history?": "¿Quiere borrar el historial de reproducción?", + "Yes": "Sí", + "No": "No", + "Import and Export Data": "Importación y exportación de datos", + "Import": "Importar", + "Import Invidious data": "Importar datos de Invidious", + "Import YouTube subscriptions": "Importar suscripciones de YouTube", + "Import FreeTube subscriptions (.db)": "Importar suscripciones de FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importar suscripciones de NewPipe (.json)", + "Import NewPipe data (.zip)": "Importar datos de NewPipe (.zip)", + "Export": "Exportar", + "Export subscriptions as OPML": "Exportar suscripciones como OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar suscripciones como OPML (para NewPipe y FreeTube)", + "Export data as JSON": "Exportar datos como JSON", + "Delete account?": "¿Quiere borrar la cuenta?", + "History": "Historial", + "An alternative front-end to YouTube": "Una interfaz alternativa para YouTube", + "JavaScript license information": "Información de licencia de JavaScript", + "source": "código fuente", + "Login": "Iniciar sesión", + "Login/Register": "Iniciar sesión/Registrarse", + "Login to Google": "Iniciar sesión en Google", + "User ID:": "Nombre:", + "Password:": "Contraseña:", + "Time (h:mm:ss):": "Hora (h:mm:ss):", + "Text CAPTCHA": "CAPTCHA en texto", + "Image CAPTCHA": "CAPTCHA en imagen", + "Sign In": "Iniciar sesión", + "Register": "Registrarse", + "Email:": "Correo:", + "Google verification code:": "Código de verificación de Google:", + "Preferences": "Preferencias", + "Player preferences": "Preferencias del reproductor", + "Always loop: ": "Repetir siempre: ", + "Autoplay: ": "Reproducción automática: ", + "Autoplay next video: ": "Reproducir automáticamente el vídeo siguiente: ", + "Listen by default: ": "Activar el sonido por defecto: ", + "Proxy videos? ": "¿Usar un proxy para los vídeos? ", + "Default speed: ": "Velocidad por defecto: ", + "Preferred video quality: ": "Calidad de vídeo preferida: ", + "Player volume: ": "Volumen del reproductor: ", + "Default comments: ": "Comentarios por defecto: ", + "Default captions: ": "Subtítulos por defecto: ", + "Fallback captions: ": "Subtítulos alternativos: ", + "Show related videos? ": "¿Mostrar vídeos relacionados? ", + "Visual preferences": "Preferencias visuales", + "Dark mode: ": "Modo oscuro: ", + "Thin mode: ": "Modo compacto: ", + "Subscription preferences": "Preferencias de la suscripción", + "Redirect homepage to feed: ": "Redirigir la página de inicio a la fuente: ", + "Number of videos shown in feed: ": "Número de vídeos mostrados en la fuente: ", + "Sort videos by: ": "Ordenar los vídeos por: ", + "published": "fecha de publicación", + "published - reverse": "fecha de publicación: orden inverso", + "alphabetically": "alfabéticamente", + "alphabetically - reverse": "alfabéticamente: orden inverso", + "channel name": "nombre del canal", + "channel name - reverse": "nombre del canal: orden inverso", + "Only show latest video from channel: ": "Mostrar solo el último vídeo del canal: ", + "Only show latest unwatched video from channel: ": "Mostrar solo el último vídeo sin ver del canal: ", + "Only show unwatched: ": "Mostrar solo los no vistos: ", + "Only show notifications (if there are any): ": "Mostrar solo notificaciones (si hay alguna): ", + "Data preferences": "Preferencias de los datos", + "Clear watch history": "Borrar el historial de reproducción", + "Import/Export data": "Importar/Exportar datos", + "Manage subscriptions": "Gestionar las suscripciones", + "Watch history": "Historial de reproducción", + "Delete account": "Borrar cuenta", + "Administrator preferences": "Preferencias de administrador", + "Default homepage: ": "Página de inicio por defecto: ", + "Feed menu: ": "Menú de fuentes: ", + "Top enabled? ": "¿Habilitar los destacados? ", + "CAPTCHA enabled? ": "¿Habilitar los CAPTCHA? ", + "Login enabled? ": "¿Habilitar el inicio de sesión? ", + "Registration enabled? ": "¿Habilitar el registro? ", + "Report statistics? ": "¿Enviar estadísticas? ", + "Save preferences": "Guardar las preferencias", + "Subscription manager": "Gestor de suscripciones", + "`x` subscriptions": "`x` suscripciones", + "Import/Export": "Importar/Exportar", + "unsubscribe": "Desuscribirse", + "Subscriptions": "Suscripciones", + "`x` unseen notifications": "`x` notificaciones sin ver", + "search": "buscar", + "Sign out": "Cerrar la sesión", + "Released under the AGPLv3 by Omar Roth.": "Publicado bajo licencia AGPLv3 por Omar Roth.", + "Source available here.": "Código fuente disponible aquí.", + "View JavaScript license information.": "Ver información de licencia de JavaScript.", + "View privacy policy.": "Ver la política de privacidad.", + "Trending": "Tendencias", + "Unlisted": "", + "Watch video on Youtube": "Ver el vídeo en Youtube", + "Genre: ": "Género: ", + "License: ": "Licencia: ", + "Family friendly? ": "¿Filtrar contenidos? ", + "Wilson score: ": "Puntuación Wilson: ", + "Engagement: ": "Compromiso: ", + "Whitelisted regions: ": "Regiones permitidas: ", + "Blacklisted regions: ": "Regiones bloqueadas: ", + "Shared `x`": "Compartido `x`", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.", + "View YouTube comments": "Ver los comentarios de YouTube", + "View more comments on Reddit": "Ver más comentarios en Reddit", + "View `x` comments": "Ver `x` comentarios", + "View Reddit comments": "Ver los comentarios de Reddit", + "Hide replies": "Ocultar las respuestas", + "Show replies": "Mostrar las respuestas", + "Incorrect password": "Contraseña incorrecta", + "Quota exceeded, try again in a few hours": "Cuota excedida, pruebe otra vez en unas horas", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "No se puede iniciar sesión, asegúrese de que la autentificación de dos factores (autentificador o SMS) esté habilitada.", + "Invalid TFA code": "Código TFA no válido", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Error de inicio de sesion. Puede deberse a que la autentificación de dos factores no está habilitada en su cuenta.", + "Invalid answer": "Respuesta no válida", + "Invalid CAPTCHA": "CAPTCHA no válido", + "CAPTCHA is a required field": "El CAPTCHA es un campo obligatorio", + "User ID is a required field": "El nombre es un campo obligatorio", + "Password is a required field": "La contraseña es un campo obligatorio", + "Invalid username or password": "Nombre o contraseña incorrecto", + "Please sign in using 'Sign in with Google'": "Inicie sesión con «Iniciar sesión con Google»", + "Password cannot be empty": "La contraseña no puede estar en blanco", + "Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres", + "Please sign in": "Inicie sesión, por favor", + "Invidious Private Feed for `x`": "Fuente privada de Invidious para `x`", + "channel:`x`": "canal: `x`", + "Deleted or invalid channel": "El canal no es válido o ha sido borrado", + "This channel does not exist.": "El canal no existe.", + "Could not get channel info.": "No se ha podido obtener información del canal.", + "Could not fetch comments": "No se han podido recuperar los comentarios.", + "View `x` replies": "Ver `x` respuestas", + "`x` ago": "hace `x`", + "Load more": "Cargar más", + "`x` points": "`x` puntos", + "Could not create mix.": "No se ha podido crear la mezcla.", + "Playlist is empty": "La lista de reproducción está vacía", + "Invalid playlist.": "Lista de reproducción no válida.", + "Playlist does not exist.": "La lista de reproducción no existe.", + "Could not pull trending pages.": "No se han podido obtener las páginas de tendencias.", + "Hidden field \"challenge\" is a required field": "El campo oculto «desafío» es un campo obligatorio", + "Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio", + "Invalid challenge": "Desafío no válido", + "Invalid token": "Símbolo no válido", + "Invalid user": "Usuario no válido", + "Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo", + "English": "Inglés", + "English (auto-generated)": "Inglés (autogenerado)", + "Afrikaans": "Afrikáans", + "Albanian": "Albanés", + "Amharic": "Amárico", + "Arabic": "Árabe", + "Armenian": "Armenio", + "Azerbaijani": "Azerbaiyano", + "Bangla": "Bengalí", + "Basque": "Euskera", + "Belarusian": "Bielorruso", + "Bosnian": "Bosnio", + "Bulgarian": "Búlgaro", + "Burmese": "Birmano", + "Catalan": "Catalán", + "Cebuano": "Cebuano", + "Chinese (Simplified)": "Chino (simplificado)", + "Chinese (Traditional)": "Chino (tradicional)", + "Corsican": "Corso", + "Croatian": "Croata", + "Czech": "Checo", + "Danish": "Danés", + "Dutch": "Holandés", + "Esperanto": "Esperanto", + "Estonian": "Estonio", + "Filipino": "Filipino", + "Finnish": "Finés", + "French": "Francés", + "Galician": "Gallego", + "Georgian": "Georgiano", + "German": "Alemán", + "Greek": "Griego", + "Gujarati": "Guyaratí", + "Haitian Creole": "Criollo haitiano", + "Hausa": "Hausa", + "Hawaiian": "Hawaiano", + "Hebrew": "Hebreo", + "Hindi": "Hindi", + "Hmong": "Hmong", + "Hungarian": "Húngaro", + "Icelandic": "Islandés", + "Igbo": "Igbo", + "Indonesian": "Indonesio", + "Irish": "Irlandés", + "Italian": "Italiano", + "Japanese": "Japonés", + "Javanese": "Javanés", + "Kannada": "Canarés", + "Kazakh": "Kazajo", + "Khmer": "Camboyano", + "Korean": "Coreano", + "Kurdish": "Kurdo", + "Kyrgyz": "Kirguís", + "Lao": "Laosiano", + "Latin": "Latín", + "Latvian": "Letón", + "Lithuanian": "Lituano", + "Luxembourgish": "Luxemburgués", + "Macedonian": "Macedonio", + "Malagasy": "Malgache", + "Malay": "Malayo", + "Malayalam": "Malabar", + "Maltese": "Maltés", + "Maori": "Maorí", + "Marathi": "Maratí", + "Mongolian": "Mongol", + "Nepali": "Nepalí", + "Norwegian": "Noruego", + "Nyanja": "Chichewa", + "Pashto": "Pastún", + "Persian": "Persa", + "Polish": "Polaco", + "Portuguese": "Portugués", + "Punjabi": "Panyabí", + "Romanian": "Rumano", + "Russian": "Ruso", + "Samoan": "Samoano", + "Scottish Gaelic": "Gaélico escocés", + "Serbian": "Serbio", + "Shona": "Shona", + "Sindhi": "Sindi", + "Sinhala": "Cingalés", + "Slovak": "Eslovaco", + "Slovenian": "Esloveno", + "Somali": "Somalí", + "Southern Sotho": "Sesoto", + "Spanish": "Español", + "Spanish (Latin America)": "Español (Hispanoamérica)", + "Sundanese": "Sondanés", + "Swahili": "Suajili", + "Swedish": "Sueco", + "Tajik": "Tayiko", + "Tamil": "Tamil", + "Telugu": "Telugu", + "Thai": "Tailandés", + "Turkish": "Turco", + "Ukrainian": "Ucraniano", + "Urdu": "Urdu", + "Uzbek": "Uzbeko", + "Vietnamese": "Vietnamita", + "Welsh": "Galés", + "Western Frisian": "Frisón", + "Xhosa": "Xhosa", + "Yiddish": "Yidis", + "Yoruba": "Yoruba", + "Zulu": "Zulú", + "`x` years": "`x` años", + "`x` months": "`x` meses", + "`x` weeks": "`x` semanas", + "`x` days": "`x` días", + "`x` hours": "`x` horas", + "`x` minutes": "`x` minutos", + "`x` seconds": "`x` segundos", + "Fallback comments: ": "Comentarios alternativos: ", + "Popular": "Populares", + "Top": "Destacados", + "About": "Acerca de", + "Rating: ": "Valoración: ", + "Language: ": "Idioma: ", + "Default": "Por defecto", + "Music": "Música", + "Gaming": "Videojuegos", + "News": "Noticias", + "Movies": "Películas", + "Download": "Descargar", + "Download as: ": "Descargar como: ", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "(edited)": "(editado)", + "Youtube permalink of the comment": "Enlace permanente de YouTube del comentario", + "`x` marked it with a ❤": "`x` lo ha marcado con un ❤", + "Audio mode": "Modo de audio", + "Video mode": "Modo de vídeo", + "Videos": "Vídeos", + "Playlists": "Listas de reproducción", + "Current version: ": "Versión actual: " +} From 7f30d07f4cfebadc41073cd7cd94413171500639 Mon Sep 17 00:00:00 2001 From: dimqua Date: Wed, 27 Mar 2019 16:56:02 +0000 Subject: [PATCH 012/210] Update Russian translation --- locales/ru.json | 590 ++++++++++++++++++++++++------------------------ 1 file changed, 295 insertions(+), 295 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index a2ad7333..5a664b0d 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,297 +1,297 @@ { - "`x` subscribers": "`x` подписчиков", - "`x` videos": "`x` видео", - "LIVE": "ПРЯМОЙ ЭФИР", - "Shared `x` ago": "Опубликовано `x` назад", - "Unsubscribe": "Отписаться", - "Subscribe": "Подписаться", - "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", - "View channel on YouTube": "Канал на YouTube", - "newest": "новые", - "oldest": "старые", - "popular": "популярные", - "last": "недавно обновленные", - "Next page": "Следующая страница", - "Previous page": "Предыдущая страница", - "Clear watch history?": "Очистить историю просмотров?", - "Yes": "Да", - "No": "Нет", - "Import and Export Data": "Импорт и экспорт данных", - "Import": "Импорт", - "Import Invidious data": "Импортировать данные Invidious", - "Import YouTube subscriptions": "Импортировать YouTube подписки", - "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", - "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", - "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", - "Export": "Экспорт", - "Export subscriptions as OPML": "Экспортировать подписки в OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", - "Export data as JSON": "Экспортировать данные в JSON", - "Delete account?": "Удалить аккаунт?", - "History": "История", - "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", - "JavaScript license information": "Лицензии JavaScript", - "source": "источник", - "Login": "Войти", - "Login/Register": "Войти/Регистрация", - "Login to Google": "Войти через Google", - "User ID:": "ID пользователя:", - "Password:": "Пароль:", - "Time (h:mm:ss):": "Время (ч:мм:сс):", - "Text CAPTCHA": "Текст капчи", - "Image CAPTCHA": "Изображение капчи", - "Sign In": "Войти", - "Register": "Регистрация", - "Email:": "Эл. почта:", - "Google verification code:": "Код подтверждения Google:", - "Preferences": "Настройки", - "Player preferences": "Настройки проигрывателя", - "Always loop: ": "Всегда повторять: ", - "Autoplay: ": "Автовоспроизведение: ", - "Autoplay next video: ": "Автовоспроизведение следующего видео: ", - "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", - "Proxy videos? ": "Проксировать видео? ", - "Default speed: ": "Скорость по-умолчанию: ", - "Preferred video quality: ": "Предпочтительное качество видео: ", - "Player volume: ": "Громкость воспроизведения: ", - "Default comments: ": "Источник комментариев: ", - "youtube": "YouTube", - "reddit": "Reddit", - "Default captions: ": "Субтитры по-умолчанию: ", - "Fallback captions: ": "Резервные субтитры: ", - "Show related videos? ": "Показывать похожие видео? ", - "Visual preferences": "Визуальные настройки", - "Dark mode: ": "Темная тема: ", - "Thin mode: ": "Облегченный режим: ", - "Subscription preferences": "Настройки подписок", - "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", - "Number of videos shown in feed: ": "Число видео в ленте: ", - "Sort videos by: ": "Сортировать видео по: ", - "published": "дате публикации", - "published - reverse": "дате - обратный порядок", - "alphabetically": "алфавиту", - "alphabetically - reverse": "алфавиту - обратный порядок", - "channel name": "имени канала", - "channel name - reverse": "имени канала - обратный порядок", - "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", - "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", - "Only show unwatched: ": "Отображать только непросмотренные видео: ", - "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", - "Data preferences": "Настройки данных", - "Clear watch history": "Очистить историю просмотра", - "Import/Export data": "Импорт/Экспорт данных", - "Manage subscriptions": "Управление подписками", - "Watch history": "История просмотров", - "Delete account": "Удалить аккаунт", - "Administrator preferences": "Настройки администратора", - "Default homepage: ": "Главная страница по умолчанию: ", - "Feed menu: ": "Меню ленты: ", - "Top enabled? ": "Включить ТОП? ", - "CAPTCHA enabled? ": "Включить капчу? ", - "Login enabled? ": "Включить логин? ", - "Registration enabled? ": "Включить регистрацию? ", - "Report statistics? ": "Отображать статистику? ", - "Save preferences": "Сохранить настройки", - "Subscription manager": "Менеджер подписок", - "`x` subscriptions": "`x` подписок", - "Import/Export": "Импорт/Экспорт", - "unsubscribe": "отписаться", - "Subscriptions": "Подписки", - "`x` unseen notifications": "`x` новых оповещений", - "search": "поиск", - "Sign out": "Выйти", - "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", - "Source available here.": "Исходный код доступен здесь.", - "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", - "View privacy policy.": "См. политику конфиденциальности.", - "Trending": "В тренде", - "Unlisted": "", - "Watch video on Youtube": "Смотреть на YouTube", - "Genre: ": "Жанр: ", - "License: ": "Лицензия: ", - "Family friendly? ": "Семейный просмотр: ", - "Wilson score: ": "Рейтинг Вильсона: ", - "Engagement: ": "Вовлеченность: ", - "Whitelisted regions: ": "Доступно для: ", - "Blacklisted regions: ": "Недоступно для: ", - "Shared `x`": "Опубликовано `x`", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", - "View YouTube comments": "Смотреть комментарии с YouTube", - "View more comments on Reddit": "Больше комментариев на Reddit", - "View `x` comments": "Показать `x` комментариев", - "View Reddit comments": "Смотреть комментарии с Reddit", - "Hide replies": "Скрыть ответы", - "Show replies": "Показать ответы", - "Incorrect password": "Неправильный пароль", - "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", - "Invalid TFA code": "Неправильный TFA код", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", - "Invalid answer": "Неверный ответ", - "Invalid CAPTCHA": "Неверная капча", - "CAPTCHA is a required field": "Необходимо ввести капчу", - "User ID is a required field": "Необходимо ввести идентификатор пользователя", - "Password is a required field": "Необходимо ввести пароль", - "Invalid username or password": "Недопустимый пароль или имя пользователя", - "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", - "Password cannot be empty": "Пароль не может быть пустым", - "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", - "Please sign in": "Пожалуйста, войдите", - "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", - "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удален или не найден", - "This channel does not exist.": "Такой канал не существует.", - "Could not get channel info.": "Невозможно получить информацию о канале.", - "Could not fetch comments": "Невозможно получить комментарии", - "View `x` replies": "Показать `x` ответов", - "`x` ago": "`x` назад", - "Load more": "Загрузить больше", - "`x` points": "`x` очков", - "Could not create mix.": "Невозможно создать \"микс\".", - "Playlist is empty": "Плейлист пуст", - "Invalid playlist.": "Некорректный плейлист.", - "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", - "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", - "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", - "Invalid challenge": "Неправильный ответ в \"challenge\"", - "Invalid token": "Неправильный токен", - "Invalid user": "Недопустимое имя пользователя", - "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", - "English": "Английский", - "English (auto-generated)": "Английский (созданы автоматически)", - "Afrikaans": "Африкаанс", - "Albanian": "Албанский", - "Amharic": "Амхарский", - "Arabic": "Арабский", - "Armenian": "Армянский", - "Azerbaijani": "Азербайджанский", - "Bangla": "Бенгальский", - "Basque": "Баскский", - "Belarusian": "Белорусский", - "Bosnian": "Боснийский", - "Bulgarian": "Болгарский", - "Burmese": "Бирманский", - "Catalan": "Каталонский", - "Cebuano": "Себуанский", - "Chinese (Simplified)": "Китайский (упрощенный)", - "Chinese (Traditional)": "Китайский (традиционный)", - "Corsican": "Корсиканский", - "Croatian": "Хорватский", - "Czech": "Чешский", - "Danish": "Датский", - "Dutch": "Нидерландский", - "Esperanto": "Эсперанто", - "Estonian": "Эстонский", - "Filipino": "Филиппинский", - "Finnish": "Финский", - "French": "Французский", - "Galician": "Галисийский", - "Georgian": "Грузинский", - "German": "Немецкий", - "Greek": "Греческий", - "Gujarati": "Гуджаратский", - "Haitian Creole": "Гаит. креольский", - "Hausa": "Хауса", - "Hawaiian": "Гавайский", - "Hebrew": "Иврит", - "Hindi": "Хинди", - "Hmong": "Хмонг (мяо)", - "Hungarian": "Венгерский", - "Icelandic": "Исландский", - "Igbo": "Игбо", - "Indonesian": "Индонезийский", - "Irish": "Ирландский", - "Italian": "Итальянский", - "Japanese": "Японский", - "Javanese": "Яванский", - "Kannada": "Каннада", - "Kazakh": "Казахский", - "Khmer": "Кхмерский", - "Korean": "Корейский", - "Kurdish": "Курдский", - "Kyrgyz": "Киргизский", - "Lao": "Лаосский", - "Latin": "Латинский", - "Latvian": "Латышский", - "Lithuanian": "Литовский", - "Luxembourgish": "Люксембургский", - "Macedonian": "Македонский", - "Malagasy": "Малагасийский", - "Malay": "Малайский", - "Malayalam": "Малаялам", - "Maltese": "Мальтийский", - "Maori": "Маори", - "Marathi": "Маратхи", - "Mongolian": "Монгольская", - "Nepali": "Непальский", - "Norwegian": "Норвежский", - "Nyanja": "Ньянджа", - "Pashto": "Пушту", - "Persian": "Персидский", - "Polish": "Польский", - "Portuguese": "Португальский", - "Punjabi": "Панджаби", - "Romanian": "Румынский", - "Russian": "Русский", - "Samoan": "Самоанский", - "Scottish Gaelic": "Шотландский (гэльский)", - "Serbian": "Сербский", - "Shona": "Шона", - "Sindhi": "Синдхи", - "Sinhala": "Сингальский", - "Slovak": "Словацкий", - "Slovenian": "Словенский", - "Somali": "Сомалийский", - "Southern Sotho": "Сесото (южный сото)", - "Spanish": "Испанский", - "Spanish (Latin America)": "Испанский (Латинская Америка)", - "Sundanese": "Сунданский", - "Swahili": "Суахили", - "Swedish": "Шведский", - "Tajik": "Таджикский", - "Tamil": "Тамильский", - "Telugu": "Телугу", - "Thai": "Тайский", - "Turkish": "Турецкий", - "Ukrainian": "Украинский", - "Urdu": "Урду", - "Uzbek": "Узбекский", - "Vietnamese": "Вьетнамский", - "Welsh": "Валлийский", - "Western Frisian": "Западнофризский", - "Xhosa": "Коса", - "Yiddish": "Идиш", - "Yoruba": "Йоруба", - "Zulu": "Зулусский", - "`x` years": "`x` лет", - "`x` months": "`x` месяцев", - "`x` weeks": "`x` недель", - "`x` days": "`x` дней", - "`x` hours": "`x` часов", - "`x` minutes": "`x` минут", - "`x` seconds": "`x` секунд", - "Fallback comments: ": "Резервные комментарии: ", - "Popular": "Популярное", - "Top": "Топ", - "About": "О сайте", - "Rating: ": "Рейтинг: ", - "Language: ": "Язык: ", - "Default": "По-умолчанию", - "Music": "Музыка", - "Gaming": "Игры", - "News": "Новости", - "Movies": "Фильмы", - "Download": "Скачать", - "Download as: ": "Скачать как: ", - "%A %B %-d, %Y": "%-d %B %Y, %A", - "(edited)": "(изменено)", - "Youtube permalink of the comment": "Прямая ссылка на YouTube", - "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", - "Audio mode": "Аудио режим", - "Video mode": "Видео режим", - "Videos": "Видео", - "Playlists": "Плейлисты", - "Current version: ": "Текущая версия: " + "`x` subscribers": "`x` подписчиков", + "`x` videos": "`x` видео", + "LIVE": "ПРЯМОЙ ЭФИР", + "Shared `x` ago": "Опубликовано `x` назад", + "Unsubscribe": "Отписаться", + "Subscribe": "Подписаться", + "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", + "View channel on YouTube": "Канал на YouTube", + "newest": "новые", + "oldest": "старые", + "popular": "популярные", + "last": "недавно обновленные", + "Next page": "Следующая страница", + "Previous page": "Предыдущая страница", + "Clear watch history?": "Очистить историю просмотров?", + "Yes": "Да", + "No": "Нет", + "Import and Export Data": "Импорт и экспорт данных", + "Import": "Импорт", + "Import Invidious data": "Импортировать данные Invidious", + "Import YouTube subscriptions": "Импортировать YouTube подписки", + "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", + "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", + "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", + "Export": "Экспорт", + "Export subscriptions as OPML": "Экспортировать подписки в OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", + "Export data as JSON": "Экспортировать данные в JSON", + "Delete account?": "Удалить аккаунт?", + "History": "История", + "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", + "JavaScript license information": "Лицензии JavaScript", + "source": "источник", + "Login": "Войти", + "Login/Register": "Войти/Регистрация", + "Login to Google": "Войти через Google", + "User ID:": "ID пользователя:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Время (ч:мм:сс):", + "Text CAPTCHA": "Текст капчи", + "Image CAPTCHA": "Изображение капчи", + "Sign In": "Войти", + "Register": "Регистрация", + "Email:": "Эл. почта:", + "Google verification code:": "Код подтверждения Google:", + "Preferences": "Настройки", + "Player preferences": "Настройки проигрывателя", + "Always loop: ": "Всегда повторять: ", + "Autoplay: ": "Автовоспроизведение: ", + "Autoplay next video: ": "Автовоспроизведение следующего видео: ", + "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", + "Proxy videos? ": "Проксировать видео? ", + "Default speed: ": "Скорость по-умолчанию: ", + "Preferred video quality: ": "Предпочтительное качество видео: ", + "Player volume: ": "Громкость воспроизведения: ", + "Default comments: ": "Источник комментариев: ", + "youtube": "YouTube", + "reddit": "Reddit", + "Default captions: ": "Субтитры по-умолчанию: ", + "Fallback captions: ": "Резервные субтитры: ", + "Show related videos? ": "Показывать похожие видео? ", + "Visual preferences": "Визуальные настройки", + "Dark mode: ": "Темная тема: ", + "Thin mode: ": "Облегченный режим: ", + "Subscription preferences": "Настройки подписок", + "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", + "Number of videos shown in feed: ": "Число видео в ленте: ", + "Sort videos by: ": "Сортировать видео по: ", + "published": "дате публикации", + "published - reverse": "дате - обратный порядок", + "alphabetically": "алфавиту", + "alphabetically - reverse": "алфавиту - обратный порядок", + "channel name": "имени канала", + "channel name - reverse": "имени канала - обратный порядок", + "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", + "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", + "Only show unwatched: ": "Отображать только непросмотренные видео: ", + "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", + "Data preferences": "Настройки данных", + "Clear watch history": "Очистить историю просмотра", + "Import/Export data": "Импорт/Экспорт данных", + "Manage subscriptions": "Управление подписками", + "Watch history": "История просмотров", + "Delete account": "Удалить аккаунт", + "Administrator preferences": "Настройки администратора", + "Default homepage: ": "Главная страница по умолчанию: ", + "Feed menu: ": "Меню ленты: ", + "Top enabled? ": "Включить ТОП? ", + "CAPTCHA enabled? ": "Включить капчу? ", + "Login enabled? ": "Включить логин? ", + "Registration enabled? ": "Включить регистрацию? ", + "Report statistics? ": "Отображать статистику? ", + "Save preferences": "Сохранить настройки", + "Subscription manager": "Менеджер подписок", + "`x` subscriptions": "`x` подписок", + "Import/Export": "Импорт/Экспорт", + "unsubscribe": "отписаться", + "Subscriptions": "Подписки", + "`x` unseen notifications": "`x` новых оповещений", + "search": "поиск", + "Sign out": "Выйти", + "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", + "Source available here.": "Исходный код доступен здесь.", + "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", + "View privacy policy.": "См. политику конфиденциальности.", + "Trending": "В тренде", + "Unlisted": "Доступно по ссылке", + "Watch video on Youtube": "Смотреть на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Лицензия: ", + "Family friendly? ": "Семейный просмотр: ", + "Wilson score: ": "Рейтинг Вильсона: ", + "Engagement: ": "Вовлеченность: ", + "Whitelisted regions: ": "Доступно для: ", + "Blacklisted regions: ": "Недоступно для: ", + "Shared `x`": "Опубликовано `x`", + "Premieres in `x`": "Премьера через `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", + "View YouTube comments": "Смотреть комментарии с YouTube", + "View more comments on Reddit": "Больше комментариев на Reddit", + "View `x` comments": "Показать `x` комментариев", + "View Reddit comments": "Смотреть комментарии с Reddit", + "Hide replies": "Скрыть ответы", + "Show replies": "Показать ответы", + "Incorrect password": "Неправильный пароль", + "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", + "Invalid TFA code": "Неправильный TFA код", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", + "Invalid answer": "Неверный ответ", + "Invalid CAPTCHA": "Неверная капча", + "CAPTCHA is a required field": "Необходимо ввести капчу", + "User ID is a required field": "Необходимо ввести идентификатор пользователя", + "Password is a required field": "Необходимо ввести пароль", + "Invalid username or password": "Недопустимый пароль или имя пользователя", + "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", + "Password cannot be empty": "Пароль не может быть пустым", + "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", + "Please sign in": "Пожалуйста, войдите", + "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал удален или не найден", + "This channel does not exist.": "Такой канал не существует.", + "Could not get channel info.": "Невозможно получить информацию о канале.", + "Could not fetch comments": "Невозможно получить комментарии", + "View `x` replies": "Показать `x` ответов", + "`x` ago": "`x` назад", + "Load more": "Загрузить больше", + "`x` points": "`x` очков", + "Could not create mix.": "Невозможно создать \"микс\".", + "Playlist is empty": "Плейлист пуст", + "Invalid playlist.": "Некорректный плейлист.", + "Playlist does not exist.": "Плейлист не существует.", + "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", + "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", + "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", + "Invalid challenge": "Неправильный ответ в \"challenge\"", + "Invalid token": "Неправильный токен", + "Invalid user": "Недопустимое имя пользователя", + "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", + "English": "Английский", + "English (auto-generated)": "Английский (созданы автоматически)", + "Afrikaans": "Африкаанс", + "Albanian": "Албанский", + "Amharic": "Амхарский", + "Arabic": "Арабский", + "Armenian": "Армянский", + "Azerbaijani": "Азербайджанский", + "Bangla": "Бенгальский", + "Basque": "Баскский", + "Belarusian": "Белорусский", + "Bosnian": "Боснийский", + "Bulgarian": "Болгарский", + "Burmese": "Бирманский", + "Catalan": "Каталонский", + "Cebuano": "Себуанский", + "Chinese (Simplified)": "Китайский (упрощенный)", + "Chinese (Traditional)": "Китайский (традиционный)", + "Corsican": "Корсиканский", + "Croatian": "Хорватский", + "Czech": "Чешский", + "Danish": "Датский", + "Dutch": "Нидерландский", + "Esperanto": "Эсперанто", + "Estonian": "Эстонский", + "Filipino": "Филиппинский", + "Finnish": "Финский", + "French": "Французский", + "Galician": "Галисийский", + "Georgian": "Грузинский", + "German": "Немецкий", + "Greek": "Греческий", + "Gujarati": "Гуджаратский", + "Haitian Creole": "Гаит. креольский", + "Hausa": "Хауса", + "Hawaiian": "Гавайский", + "Hebrew": "Иврит", + "Hindi": "Хинди", + "Hmong": "Хмонг (мяо)", + "Hungarian": "Венгерский", + "Icelandic": "Исландский", + "Igbo": "Игбо", + "Indonesian": "Индонезийский", + "Irish": "Ирландский", + "Italian": "Итальянский", + "Japanese": "Японский", + "Javanese": "Яванский", + "Kannada": "Каннада", + "Kazakh": "Казахский", + "Khmer": "Кхмерский", + "Korean": "Корейский", + "Kurdish": "Курдский", + "Kyrgyz": "Киргизский", + "Lao": "Лаосский", + "Latin": "Латинский", + "Latvian": "Латышский", + "Lithuanian": "Литовский", + "Luxembourgish": "Люксембургский", + "Macedonian": "Македонский", + "Malagasy": "Малагасийский", + "Malay": "Малайский", + "Malayalam": "Малаялам", + "Maltese": "Мальтийский", + "Maori": "Маори", + "Marathi": "Маратхи", + "Mongolian": "Монгольская", + "Nepali": "Непальский", + "Norwegian": "Норвежский", + "Nyanja": "Ньянджа", + "Pashto": "Пушту", + "Persian": "Персидский", + "Polish": "Польский", + "Portuguese": "Португальский", + "Punjabi": "Панджаби", + "Romanian": "Румынский", + "Russian": "Русский", + "Samoan": "Самоанский", + "Scottish Gaelic": "Шотландский (гэльский)", + "Serbian": "Сербский", + "Shona": "Шона", + "Sindhi": "Синдхи", + "Sinhala": "Сингальский", + "Slovak": "Словацкий", + "Slovenian": "Словенский", + "Somali": "Сомалийский", + "Southern Sotho": "Сесото (южный сото)", + "Spanish": "Испанский", + "Spanish (Latin America)": "Испанский (Латинская Америка)", + "Sundanese": "Сунданский", + "Swahili": "Суахили", + "Swedish": "Шведский", + "Tajik": "Таджикский", + "Tamil": "Тамильский", + "Telugu": "Телугу", + "Thai": "Тайский", + "Turkish": "Турецкий", + "Ukrainian": "Украинский", + "Urdu": "Урду", + "Uzbek": "Узбекский", + "Vietnamese": "Вьетнамский", + "Welsh": "Валлийский", + "Western Frisian": "Западнофризский", + "Xhosa": "Коса", + "Yiddish": "Идиш", + "Yoruba": "Йоруба", + "Zulu": "Зулусский", + "`x` years": "`x` лет", + "`x` months": "`x` месяцев", + "`x` weeks": "`x` недель", + "`x` days": "`x` дней", + "`x` hours": "`x` часов", + "`x` minutes": "`x` минут", + "`x` seconds": "`x` секунд", + "Fallback comments: ": "Резервные комментарии: ", + "Popular": "Популярное", + "Top": "Топ", + "About": "О сайте", + "Rating: ": "Рейтинг: ", + "Language: ": "Язык: ", + "Default": "По-умолчанию", + "Music": "Музыка", + "Gaming": "Игры", + "News": "Новости", + "Movies": "Фильмы", + "Download": "Скачать", + "Download as: ": "Скачать как: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "(edited)": "(изменено)", + "Youtube permalink of the comment": "Прямая ссылка на YouTube", + "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", + "Audio mode": "Аудио режим", + "Video mode": "Видео режим", + "Videos": "Видео", + "Playlists": "Плейлисты", + "Current version: ": "Текущая версия: " } From fcb37f40f6c9eb7984dc101e21a1947cd29e39fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Tue, 2 Apr 2019 09:16:03 +0000 Subject: [PATCH 013/210] =?UTF-8?q?Update=20Norwegian=20Bokm=C3=A5l=20tran?= =?UTF-8?q?slation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/nb_NO.json | 586 ++++++++++++++++++++++----------------------- 1 file changed, 293 insertions(+), 293 deletions(-) diff --git a/locales/nb_NO.json b/locales/nb_NO.json index e0fbfd77..1ef2f631 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1,295 +1,295 @@ { - "`x` subscribers": "`x` abonnenter", - "`x` videos": "`x` videoer", - "LIVE": "SANNTIDSVISNING", - "Shared `x` ago": "Delt for `x` siden", - "Unsubscribe": "Opphev abonnement", - "Subscribe": "Abonner", - "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", - "View channel on YouTube": "Vis kanal på YouTube", - "newest": "nyeste", - "oldest": "eldste", - "popular": "populært", - "last": "siste", - "Next page": "Neste side", - "Previous page": "Forrige side", - "Clear watch history?": "Tøm visningshistorikk?", - "Yes": "Ja", - "No": "Nei", - "Import and Export Data": "Importer- og eksporter data", - "Import": "Importer", - "Import Invidious data": "Importer Invidious-data", - "Import YouTube subscriptions": "Importer YouTube-abonnenter", - "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", - "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", - "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", - "Export": "Eksporter", - "Export subscriptions as OPML": "Eksporter abonnenter som OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", - "Export data as JSON": "Eksporter data som JSON", - "Delete account?": "Slett konto?", - "History": "Historikk", - "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", - "JavaScript license information": "JavaScript-lisensinformasjon", - "source": "kilde", - "Login": "Logg inn", - "Login/Register": "Logg inn/registrer", - "Login to Google": "Logg inn med Google", - "User ID:": "Bruker-ID:", - "Password:": "Passord:", - "Time (h:mm:ss):": "Tid (h:mm:ss):", - "Text CAPTCHA": "Tekst-CAPTCHA", - "Image CAPTCHA": "Bilde-CAPTCHA", - "Sign In": "Innlogging", - "Register": "Registrer", - "Email:": "E-post:", - "Google verification code:": "Google-bekreftelseskode:", - "Preferences": "Innstillinger", - "Player preferences": "Avspillerinnstillinger", - "Always loop: ": "Alltid gjenta: ", - "Autoplay: ": "Autoavspilling: ", - "Autoplay next video: ": "Autospill neste video: ", - "Listen by default: ": "Lytt som forvalg: ", - "Proxy videos? ": "", - "Default speed: ": "Forvalgt hastighet: ", - "Preferred video quality: ": "Foretrukket videokvalitet: ", - "Player volume: ": "Avspillerlydstyrke: ", - "Default comments: ": "Forvalgte kommentarer: ", - "Default captions: ": "Forvalgte undertitler: ", - "Fallback captions: ": "Tilbakefallsundertitler: ", - "Show related videos? ": "Vis relaterte videoer? ", - "Visual preferences": "Visuelle innstillinger", - "Dark mode: ": "Mørk drakt: ", - "Thin mode: ": "Tynt modus: ", - "Subscription preferences": "Abonnementsinnstillinger", - "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", - "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", - "Sort videos by: ": "Sorter videoer etter: ", - "published": "publisert", - "published - reverse": "publisert - motsatt", - "alphabetically": "alfabetisk", - "alphabetically - reverse": "alfabetisk - motsatt", - "channel name": "kanalnavn", - "channel name - reverse": "kanalnavn - motsatt", - "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", - "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", - "Only show unwatched: ": "Kun vis usette: ", - "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", - "Data preferences": "Datainnstillinger", - "Clear watch history": "Tøm visningshistorikk", - "Import/Export data": "Importer/eksporter data", - "Manage subscriptions": "Behandle abonnementer", - "Watch history": "Visningshistorikk", - "Delete account": "Slett konto", - "Administrator preferences": "Administratorinnstillinger", - "Default homepage: ": "Forvalgt hjemmeside: ", - "Feed menu: ": "Flyt-meny: ", - "Top enabled? ": "Topp påskrudd? ", - "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", - "Login enabled? ": "Innlogging påskrudd? ", - "Registration enabled? ": "Registrering påskrudd? ", - "Report statistics? ": "Innrapporter statistikk? ", - "Save preferences": "Lagre innstillinger", - "Subscription manager": "Abonnementsbehandler", - "`x` subscriptions": "`x` abonnementer", - "Import/Export": "Importer/eksporter", - "unsubscribe": "opphev abonnement", - "Subscriptions": "Abonnement", - "`x` unseen notifications": "`x` usette merknader", - "search": "søk", - "Sign out": "Logg ut", - "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", - "Source available here.": "Kildekode tilgjengelig her.", - "View JavaScript license information.": "Vis JavaScript-lisensinfo.", - "View privacy policy.": "", - "Trending": "Trendsettende", - "Unlisted": "", - "Watch video on Youtube": "Vis video på YouTube", - "Genre: ": "Sjanger: ", - "License: ": "Lisens: ", - "Family friendly? ": "Familievennlig? ", - "Wilson score: ": "Wilson-poengsum: ", - "Engagement: ": "Engasjement: ", - "Whitelisted regions: ": "Hvitlistede regioner: ", - "Blacklisted regions: ": "Svartelistede regioner: ", - "Shared `x`": "Delt `x`", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", - "View YouTube comments": "Vis YouTube-kommentarer", - "View more comments on Reddit": "Vis flere kommenterer på Reddit", - "View `x` comments": "Vis `x` kommentarer", - "View Reddit comments": "Vis Reddit-kommentarer", - "Hide replies": "Skjul svar", - "Show replies": "Vis svar", - "Incorrect password": "Feil passord", - "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", - "Invalid TFA code": "Ugyldig tofaktorkode", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", - "Invalid answer": "Ugyldig svar", - "Invalid CAPTCHA": "Ugyldig CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", - "User ID is a required field": "Bruker-ID er et påkrevd felt", - "Password is a required field": "Passord er et påkrevd felt", - "Invalid username or password": "Ugyldig brukernavn eller passord", - "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", - "Password cannot be empty": "Passordet kan ikke være tomt", - "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", - "Please sign in": "Logg inn", - "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", - "channel:`x`": "kanal `x`", - "Deleted or invalid channel": "Slettet eller ugyldig kanal", - "This channel does not exist.": "Denne kanalen finnes ikke.", - "Could not get channel info.": "Kunne ikke innhente kanalinfo.", - "Could not fetch comments": "Kunne ikke hente kommentarer", - "View `x` replies": "Vis `x` svar", - "`x` ago": "`x` siden", - "Load more": "Last inn flere", - "`x` points": "`x` poeng", - "Could not create mix.": "Kunne ikke opprette miks.", - "Playlist is empty": "Spillelisten er tom", - "Invalid playlist.": "Ugyldig spilleliste.", - "Playlist does not exist.": "Spillelisten finnes ikke.", - "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", - "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", - "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", - "Invalid challenge": "Ugyldig utfordring", - "Invalid token": "Ugyldig symbol", - "Invalid user": "Ugyldig bruker", - "Token is expired, please try again": "Symbol utløpt, prøv igjen", - "English": "Engelsk", - "English (auto-generated)": "Engelsk (auto-generert)", - "Afrikaans": "", - "Albanian": "Albansk", - "Amharic": "", - "Arabic": "Arabisk", - "Armenian": "Armensk", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "Hviterussisk", - "Bosnian": "Bosnisk", - "Bulgarian": "Bulgarsk", - "Burmese": "Burmesisk", - "Catalan": "Katalansk", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "Tsjekkisk", - "Danish": "Dansk", - "Dutch": "", - "Esperanto": "Esperanto", - "Estonian": "", - "Filipino": "", - "Finnish": "Finsk", - "French": "Fransk", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "Ungarsk", - "Icelandic": "Islandsk", - "Igbo": "", - "Indonesian": "Indonesisk", - "Irish": "Irsk", - "Italian": "Italiensk", - "Japanese": "Japansk", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "Norsk bokmål", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "Russisk", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "Serbisk", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "Slovakisk", - "Slovenian": "Slovensk", - "Somali": "Somali", - "Southern Sotho": "", - "Spanish": "Spansk", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "Svensk", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "Tyrkisk", - "Ukrainian": "Ukrainsk", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "Vietnamesisk", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` år", - "`x` months": "`x` måneder", - "`x` weeks": "`x` uker", - "`x` days": "`x` dager", - "`x` hours": "`x` timer", - "`x` minutes": "`x` minutter", - "`x` seconds": "`x` sekunder", - "Fallback comments: ": "Tilbakefallskommentarer: ", - "Popular": "Pupulært", - "Top": "Topp", - "About": "Om", - "Rating: ": "Vurdering: ", - "Language: ": "Språk: ", - "Default": "Forvalg", - "Music": "Musikk", - "Gaming": "Spill", - "News": "Nyheter", - "Movies": "Filmer", - "Download": "Last ned", - "Download as: ": "Last ned som: ", - "%A %B %-d, %Y": "", - "(edited)": "(redigert)", - "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", - "`x` marked it with a ❤": "`x` levnet et ❤", - "Audio mode": "Lydmodus", - "Video mode": "Video-modus", - "Videos": "Videoer", - "Playlists": "Spillelister", - "Current version: ": "Nåværende versjon: " + "`x` subscribers": "`x` abonnenter", + "`x` videos": "`x` videoer", + "LIVE": "SANNTIDSVISNING", + "Shared `x` ago": "Delt for `x` siden", + "Unsubscribe": "Opphev abonnement", + "Subscribe": "Abonner", + "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", + "View channel on YouTube": "Vis kanal på YouTube", + "newest": "nyeste", + "oldest": "eldste", + "popular": "populært", + "last": "siste", + "Next page": "Neste side", + "Previous page": "Forrige side", + "Clear watch history?": "Tøm visningshistorikk?", + "Yes": "Ja", + "No": "Nei", + "Import and Export Data": "Importer- og eksporter data", + "Import": "Importer", + "Import Invidious data": "Importer Invidious-data", + "Import YouTube subscriptions": "Importer YouTube-abonnenter", + "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", + "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", + "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", + "Export": "Eksporter", + "Export subscriptions as OPML": "Eksporter abonnenter som OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", + "Export data as JSON": "Eksporter data som JSON", + "Delete account?": "Slett konto?", + "History": "Historikk", + "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", + "JavaScript license information": "JavaScript-lisensinformasjon", + "source": "kilde", + "Login": "Logg inn", + "Login/Register": "Logg inn/registrer", + "Login to Google": "Logg inn med Google", + "User ID:": "Bruker-ID:", + "Password:": "Passord:", + "Time (h:mm:ss):": "Tid (h:mm:ss):", + "Text CAPTCHA": "Tekst-CAPTCHA", + "Image CAPTCHA": "Bilde-CAPTCHA", + "Sign In": "Innlogging", + "Register": "Registrer", + "Email:": "E-post:", + "Google verification code:": "Google-bekreftelseskode:", + "Preferences": "Innstillinger", + "Player preferences": "Avspillerinnstillinger", + "Always loop: ": "Alltid gjenta: ", + "Autoplay: ": "Autoavspilling: ", + "Autoplay next video: ": "Autospill neste video: ", + "Listen by default: ": "Lytt som forvalg: ", + "Proxy videos? ": "Mellomtjen videoer? ", + "Default speed: ": "Forvalgt hastighet: ", + "Preferred video quality: ": "Foretrukket videokvalitet: ", + "Player volume: ": "Avspillerlydstyrke: ", + "Default comments: ": "Forvalgte kommentarer: ", + "Default captions: ": "Forvalgte undertitler: ", + "Fallback captions: ": "Tilbakefallsundertitler: ", + "Show related videos? ": "Vis relaterte videoer? ", + "Visual preferences": "Visuelle innstillinger", + "Dark mode: ": "Mørk drakt: ", + "Thin mode: ": "Tynt modus: ", + "Subscription preferences": "Abonnementsinnstillinger", + "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", + "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", + "Sort videos by: ": "Sorter videoer etter: ", + "published": "publisert", + "published - reverse": "publisert - motsatt", + "alphabetically": "alfabetisk", + "alphabetically - reverse": "alfabetisk - motsatt", + "channel name": "kanalnavn", + "channel name - reverse": "kanalnavn - motsatt", + "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", + "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", + "Only show unwatched: ": "Kun vis usette: ", + "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", + "Data preferences": "Datainnstillinger", + "Clear watch history": "Tøm visningshistorikk", + "Import/Export data": "Importer/eksporter data", + "Manage subscriptions": "Behandle abonnementer", + "Watch history": "Visningshistorikk", + "Delete account": "Slett konto", + "Administrator preferences": "Administratorinnstillinger", + "Default homepage: ": "Forvalgt hjemmeside: ", + "Feed menu: ": "Flyt-meny: ", + "Top enabled? ": "Topp påskrudd? ", + "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", + "Login enabled? ": "Innlogging påskrudd? ", + "Registration enabled? ": "Registrering påskrudd? ", + "Report statistics? ": "Innrapporter statistikk? ", + "Save preferences": "Lagre innstillinger", + "Subscription manager": "Abonnementsbehandler", + "`x` subscriptions": "`x` abonnementer", + "Import/Export": "Importer/eksporter", + "unsubscribe": "opphev abonnement", + "Subscriptions": "Abonnement", + "`x` unseen notifications": "`x` usette merknader", + "search": "søk", + "Sign out": "Logg ut", + "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", + "Source available here.": "Kildekode tilgjengelig her.", + "View JavaScript license information.": "Vis JavaScript-lisensinfo.", + "View privacy policy.": "Vis personvernspraksis.", + "Trending": "Trendsettende", + "Unlisted": "Ulistet", + "Watch video on Youtube": "Vis video på YouTube", + "Genre: ": "Sjanger: ", + "License: ": "Lisens: ", + "Family friendly? ": "Familievennlig? ", + "Wilson score: ": "Wilson-poengsum: ", + "Engagement: ": "Engasjement: ", + "Whitelisted regions: ": "Hvitlistede regioner: ", + "Blacklisted regions: ": "Svartelistede regioner: ", + "Shared `x`": "Delt `x`", + "Premieres in `x`": "Premiere om `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", + "View YouTube comments": "Vis YouTube-kommentarer", + "View more comments on Reddit": "Vis flere kommenterer på Reddit", + "View `x` comments": "Vis `x` kommentarer", + "View Reddit comments": "Vis Reddit-kommentarer", + "Hide replies": "Skjul svar", + "Show replies": "Vis svar", + "Incorrect password": "Feil passord", + "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", + "Invalid TFA code": "Ugyldig tofaktorkode", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", + "Invalid answer": "Ugyldig svar", + "Invalid CAPTCHA": "Ugyldig CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", + "User ID is a required field": "Bruker-ID er et påkrevd felt", + "Password is a required field": "Passord er et påkrevd felt", + "Invalid username or password": "Ugyldig brukernavn eller passord", + "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", + "Password cannot be empty": "Passordet kan ikke være tomt", + "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", + "Please sign in": "Logg inn", + "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", + "channel:`x`": "kanal `x`", + "Deleted or invalid channel": "Slettet eller ugyldig kanal", + "This channel does not exist.": "Denne kanalen finnes ikke.", + "Could not get channel info.": "Kunne ikke innhente kanalinfo.", + "Could not fetch comments": "Kunne ikke hente kommentarer", + "View `x` replies": "Vis `x` svar", + "`x` ago": "`x` siden", + "Load more": "Last inn flere", + "`x` points": "`x` poeng", + "Could not create mix.": "Kunne ikke opprette miks.", + "Playlist is empty": "Spillelisten er tom", + "Invalid playlist.": "Ugyldig spilleliste.", + "Playlist does not exist.": "Spillelisten finnes ikke.", + "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", + "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", + "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", + "Invalid challenge": "Ugyldig utfordring", + "Invalid token": "Ugyldig symbol", + "Invalid user": "Ugyldig bruker", + "Token is expired, please try again": "Symbol utløpt, prøv igjen", + "English": "Engelsk", + "English (auto-generated)": "Engelsk (auto-generert)", + "Afrikaans": "", + "Albanian": "Albansk", + "Amharic": "", + "Arabic": "Arabisk", + "Armenian": "Armensk", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "Hviterussisk", + "Bosnian": "Bosnisk", + "Bulgarian": "Bulgarsk", + "Burmese": "Burmesisk", + "Catalan": "Katalansk", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "Tsjekkisk", + "Danish": "Dansk", + "Dutch": "", + "Esperanto": "Esperanto", + "Estonian": "", + "Filipino": "", + "Finnish": "Finsk", + "French": "Fransk", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "Ungarsk", + "Icelandic": "Islandsk", + "Igbo": "", + "Indonesian": "Indonesisk", + "Irish": "Irsk", + "Italian": "Italiensk", + "Japanese": "Japansk", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "Norsk bokmål", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "Russisk", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "Serbisk", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "Slovakisk", + "Slovenian": "Slovensk", + "Somali": "Somali", + "Southern Sotho": "", + "Spanish": "Spansk", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "Svensk", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "Tyrkisk", + "Ukrainian": "Ukrainsk", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "Vietnamesisk", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` år", + "`x` months": "`x` måneder", + "`x` weeks": "`x` uker", + "`x` days": "`x` dager", + "`x` hours": "`x` timer", + "`x` minutes": "`x` minutter", + "`x` seconds": "`x` sekunder", + "Fallback comments: ": "Tilbakefallskommentarer: ", + "Popular": "Pupulært", + "Top": "Topp", + "About": "Om", + "Rating: ": "Vurdering: ", + "Language: ": "Språk: ", + "Default": "Forvalg", + "Music": "Musikk", + "Gaming": "Spill", + "News": "Nyheter", + "Movies": "Filmer", + "Download": "Last ned", + "Download as: ": "Last ned som: ", + "%A %B %-d, %Y": "", + "(edited)": "(redigert)", + "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", + "`x` marked it with a ❤": "`x` levnet et ❤", + "Audio mode": "Lydmodus", + "Video mode": "Video-modus", + "Videos": "Videoer", + "Playlists": "Spillelister", + "Current version: ": "Nåværende versjon: " } From ea0d52c0b85c0207c1766e1dc5d1bd0778485cad Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 5 Apr 2019 17:24:06 -0500 Subject: [PATCH 014/210] Add support for Spanish translation --- src/invidious.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/invidious.cr b/src/invidious.cr index 0cffc9af..53edf843 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -85,6 +85,7 @@ LOCALES = { "ar" => load_locale("ar"), "de" => load_locale("de"), "en-US" => load_locale("en-US"), + "es" => load_locale("es"), "eu" => load_locale("eu"), "fr" => load_locale("fr"), "it" => load_locale("it"), From 8d5f9418297ac8b43540c571b466da82dd9fac0f Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 5 Apr 2019 23:04:56 -0500 Subject: [PATCH 015/210] Update CHANGELOG and bump version --- CHANGELOG.md | 104 +++++++++++++++++++++++++++++++++++++++++---------- shard.yml | 2 +- 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6030f01f..dec40b5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,67 @@ +# 0.16.0 (2019-04-06) + +# Version 0.16.0: API Improvements and Annotations + +Hello again! This past month has seen [116 commits](https://github.com/omarroth/invidious/compare/0.15.0..0.16.0) from 13 contributors and a couple important changes I'd like to announce. + +A privacy policy is now available [here](https://invidio.us/privacy). I've done my best to explain things as clearly as possible without oversimplifying, and would very much recommend reading it if you're concerned about your privacy and want to learn more about how Invidious uses your data. Please let me know if there is anything that needs clarification. + +I'm also very happy to announce that a Spanish translation has been added to the site. You can use it with `?hl=es` or by setting `es` as your default locale. As always I'm extremely grateful to translators for making the site accessible to more people. + +## For Administrators + +Invidious now supports server-to-server [push notifications](https://developers.google.com/youtube/v3/guides/push_notifications). This uses [PubSubHubbub](https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html) to automatically handle new videos sent to an instance, which is less resource intensive and generally faster. Note that it will not pull all videos from a subscribed channel, so recommended usage is in addition to `channel_threads`. Using PubSub requires a valid `domain` that updates can be sent to, and a random string that can be used to sign updates sent to the instance. You can enable it by adding `use_pubsub_feeds: true` to your `config.yml`. See [Configuration](https://github.com/omarroth/invidious/wiki/Configuration) for more info. + +Unfortunately there are a couple necessary changes to the DB to support `liveNow` and `premiereTimestamp` in subscription feeds. Migration scripts have been provided that should be used automatically if following the instructions [here](https://github.com/omarroth/invidious/wiki/Updating). + +You can now configure default user preferences for your instance. This allows you to set default locale, player preferences, and more. See [#415](https://github.com/omarroth/invidious/issues/415) for more details and example usage. + +## For Developers + +The [fields](https://developers.google.com/youtube/v3/getting-started#fields) API has been added with [#429](https://github.com/omarroth/invidious/pull/429) and is now supported on all JSON endpoints, thanks [**@afrmtbl**](https://github.com/afrmtbl)! Synax is straight-forward and can be used to reduce data transfer and create a simpler response for debugging. You can see an example [here](https://invidio.us/api/v1/videos/CvFH_6DNRCY?pretty=1&fields=title,recommendedVideos/title). I've been quite happy using it and hope it is similarly useful for others. + +An `/api/v1/annotations/:id` endpoint has been added for pulling legacy annotation data from [this](https://archive.org/details/youtubeannotations) archive, see below for more details. You can also access annotation data available on YouTube using `?source=youtube`, although this will only return card data as legacy annotations were deleted on January 15th. + +A couple minor changes to existing endpoints: + +- A `premiereTimestamp` field has been added to `/api/v1/videos/:id` +- A `sort_by` param has been added to `/api/v1/comments/:id`, supports `new`, `top`. + +More info is available in the [documentation](https://github.com/omarroth/invidious/wiki/API). + +## Annotations + +I'm pleased to announce that annotation data is finally available from the roughly 1.4 billion videos archived as part of [this](https://www.reddit.com/r/DataHoarder/comments/aa6czg/youtube_annotation_archive/) project. They are accessible from the Internet Archive [here](https://archive.org/details/youtubeannotations) or as a 355GB torrent, see [here](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. A corresponding `/api/v1/annotations/:id` endpoint has been added to Invidious which uses the collection from IA to provide legacy annotations. + +Support for them in the player is possible thanks to [this](https://github.com/afrmtbl/videojs-youtube-annotations) plugin developed by [**@afrmtbl**](https://github.com/afrmtbl). A PR for adding support to the site is available as [#303](https://github.com/omarroth/invidious/pull/303). There's also an [extension](https://github.com/afrmtbl/AnnotationsRestored) for overlaying them on top of the YouTube player (again thanks to [**@afrmtbl**](https://github.com/afrmtbl)), and an [extension](https://tech234a.bitbucket.io/AnnotationsReloaded?src=invidious) for hooking into code still present in the YouTube player itself, developed by [**@tech234a**](https://github.com/tech234a). + +I would recommend reading the [official announcement](https://www.reddit.com/r/DataHoarder/comments/b7imx9/youtube_annotation_archive_annotation_data_from/) for more details. I would like to again thank everyone that helped contribute to this project. + +## Finances + +### Donations + +- [Patreon](https://www.patreon.com/omarroth) : \$42.42 +- [Liberapay](https://liberapay.com/omarroth) : \$70.11 +- Crypto : ~\$1.76 (converted from BCH, BTC, BSV) +- Total : \$114.29 + +### Expenses + +- invidious-load1 (nyc1) : \$10.00 (load balancer) +- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds) +- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node5 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database) +- Total : \$80.00 + +This past month the site saw a couple abnormal peaks in traffic, so an additional webserver has been added to match the increased load. The goal on Patreon has been updated to match the above expenses. + +Thanks everyone! + # 0.15.0 (2019-03-06) ## Version 0.15.0: Preferences and Channel Playlists @@ -42,21 +106,21 @@ There's also more discussion on improving Invidious for streaming music in [#304 ### Donations -[Patreon](https://www.patreon.com/omarroth) : \$42.42 -[Liberapay](https://liberapay.com/omarroth) : \$30.97 -Crypto : ~\$0.00 (converted from BCH, BTC) -Total : \$73.39 +- [Patreon](https://www.patreon.com/omarroth) : \$42.42 +- [Liberapay](https://liberapay.com/omarroth) : \$30.97 +- Crypto : ~\$0.00 (converted from BCH, BTC) +- Total : \$73.39 ### Expenses -invidious-load1 (nyc1) : \$10.00 (load balancer) -invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds) -invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server) -invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server) -invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server) -invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server) -invidious-db1 (s-4vcpu-8gb) : \$40.00 (database) -Total : \$75.00 +- invidious-load1 (nyc1) : \$10.00 (load balancer) +- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds) +- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database) +- Total : \$75.00 It's been very humbling to see how fast the project has grown, and I look forward to making the site even better. Thank you everyone. @@ -121,14 +185,14 @@ Organizing this project has unfortunately taken up quite a bit of my time, and I ### Expenses -invidious-load1 (nyc1) : $10.00 (load balancer) -invidious-update1 (s-1vcpu-1gb) : $5.00 (updates feeds) -invidious-node1 (s-1vcpu-1gb) : $5.00 (web server) -invidious-node2 (s-1vcpu-1gb) : $5.00 (web server) -invidious-node3 (s-1vcpu-1gb) : $5.00 (web server) -invidious-node4 (s-1vcpu-1gb) : $5.00 (web server) -invidious-db1 (s-4vcpu-8gb) : $40.00 (database) -Total : $75.00 +- invidious-load1 (nyc1) : \$10.00 (load balancer) +- invidious-update1 (s-1vcpu-1gb) : \$5.00 (updates feeds) +- invidious-node1 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node2 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node3 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-node4 (s-1vcpu-1gb) : \$5.00 (web server) +- invidious-db1 (s-4vcpu-8gb) : \$40.00 (database) +- Total : \$75.00 As always I'm grateful for everyone's contributions and support. I'll see you all in March. diff --git a/shard.yml b/shard.yml index 2e6bab91..aad65383 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: invidious -version: 0.15.0 +version: 0.16.0 authors: - Omar Roth From c5001f3620d6a9d4d24ce63afdf892a690b16440 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 6 Apr 2019 07:38:33 -0500 Subject: [PATCH 016/210] Add role to psql scripts --- README.md | 6 +++--- config/migrate-scripts/migrate-db-17cf077.sh | 4 ++-- config/migrate-scripts/migrate-db-1c8075c.sh | 8 ++++---- config/migrate-scripts/migrate-db-30e6d29.sh | 4 ++-- config/migrate-scripts/migrate-db-3646395.sh | 4 ++-- config/migrate-scripts/migrate-db-6e51189.sh | 4 ++-- config/migrate-scripts/migrate-db-88b7097.sh | 2 +- config/migrate-scripts/migrate-db-8e884fe.sh | 6 +++--- docker/entrypoint.postgres.sh | 12 ++++++------ 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index d8aaef47..0d02fe32 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ $ exit $ sudo systemctl enable postgresql $ sudo systemctl start postgresql $ sudo -i -u postgres -$ psql -c "CREATE USER kemal WITH PASSWORD 'kemal';" +$ psql -c "CREATE USER kemal WITH PASSWORD 'kemal';" # Change 'kemal' here to a stronger password, and update `password` in config/config.yml $ createdb -O kemal invidious $ psql invidious < /home/invidious/invidious/config/sql/channels.sql $ psql invidious < /home/invidious/invidious/config/sql/videos.sql @@ -143,8 +143,8 @@ $ brew install shards crystal-lang postgres imagemagick librsvg $ git clone https://github.com/omarroth/invidious $ cd invidious $ brew services start postgresql -$ psql -c "CREATE ROLE kemal WITH LOGIN PASSWORD 'kemal';" -$ createdb invidious -U kemal +$ psql -c "CREATE ROLE kemal WITH PASSWORD 'kemal';" # Change 'kemal' here to a stronger password, and update `password` in config/config.yml +$ createdb -O kemal invidious $ psql invidious < config/sql/channels.sql $ psql invidious < config/sql/videos.sql $ psql invidious < config/sql/channel_videos.sql diff --git a/config/migrate-scripts/migrate-db-17cf077.sh b/config/migrate-scripts/migrate-db-17cf077.sh index a544f763..5e5bb214 100755 --- a/config/migrate-scripts/migrate-db-17cf077.sh +++ b/config/migrate-scripts/migrate-db-17cf077.sh @@ -1,4 +1,4 @@ #!/bin/sh -psql invidious -c "ALTER TABLE channels ADD COLUMN subscribed bool;" -psql invidious -c "UPDATE channels SET subscribed = false;" +psql invidious kemal -c "ALTER TABLE channels ADD COLUMN subscribed bool;" +psql invidious kemal -c "UPDATE channels SET subscribed = false;" diff --git a/config/migrate-scripts/migrate-db-1c8075c.sh b/config/migrate-scripts/migrate-db-1c8075c.sh index 92ad1c7b..63954397 100755 --- a/config/migrate-scripts/migrate-db-1c8075c.sh +++ b/config/migrate-scripts/migrate-db-1c8075c.sh @@ -1,7 +1,7 @@ #!/bin/sh -psql invidious -c "ALTER TABLE channel_videos DROP COLUMN live_now CASCADE" -psql invidious -c "ALTER TABLE channel_videos DROP COLUMN premiere_timestamp CASCADE" +psql invidious kemal -c "ALTER TABLE channel_videos DROP COLUMN live_now CASCADE" +psql invidious kemal -c "ALTER TABLE channel_videos DROP COLUMN premiere_timestamp CASCADE" -psql invidious -c "ALTER TABLE channel_videos ADD COLUMN live_now bool" -psql invidious -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz" +psql invidious kemal -c "ALTER TABLE channel_videos ADD COLUMN live_now bool" +psql invidious kemal -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz" diff --git a/config/migrate-scripts/migrate-db-30e6d29.sh b/config/migrate-scripts/migrate-db-30e6d29.sh index 259862df..3a377461 100755 --- a/config/migrate-scripts/migrate-db-30e6d29.sh +++ b/config/migrate-scripts/migrate-db-30e6d29.sh @@ -1,4 +1,4 @@ #!/bin/sh -psql invidious -c "ALTER TABLE channels ADD COLUMN deleted bool;" -psql invidious -c "UPDATE channels SET deleted = false;" +psql invidious kemal -c "ALTER TABLE channels ADD COLUMN deleted bool;" +psql invidious kemal -c "UPDATE channels SET deleted = false;" diff --git a/config/migrate-scripts/migrate-db-3646395.sh b/config/migrate-scripts/migrate-db-3646395.sh index 38a4f665..915f364e 100755 --- a/config/migrate-scripts/migrate-db-3646395.sh +++ b/config/migrate-scripts/migrate-db-3646395.sh @@ -1,5 +1,5 @@ #!/bin/sh psql invidious < config/sql/session_ids.sql -psql invidious -c "INSERT INTO session_ids (SELECT unnest(id), email, CURRENT_TIMESTAMP FROM users) ON CONFLICT (id) DO NOTHING" -psql invidious -c "ALTER TABLE users DROP COLUMN id" +psql invidious kemal -c "INSERT INTO session_ids (SELECT unnest(id), email, CURRENT_TIMESTAMP FROM users) ON CONFLICT (id) DO NOTHING" +psql invidious kemal -c "ALTER TABLE users DROP COLUMN id" diff --git a/config/migrate-scripts/migrate-db-6e51189.sh b/config/migrate-scripts/migrate-db-6e51189.sh index adadc109..ce728118 100755 --- a/config/migrate-scripts/migrate-db-6e51189.sh +++ b/config/migrate-scripts/migrate-db-6e51189.sh @@ -1,4 +1,4 @@ #!/bin/sh -psql invidious -c "ALTER TABLE channel_videos ADD COLUMN live_now bool;" -psql invidious -c "UPDATE channel_videos SET live_now = false;" +psql invidious kemal -c "ALTER TABLE channel_videos ADD COLUMN live_now bool;" +psql invidious kemal -c "UPDATE channel_videos SET live_now = false;" diff --git a/config/migrate-scripts/migrate-db-88b7097.sh b/config/migrate-scripts/migrate-db-88b7097.sh index c63d37f0..6bde8399 100755 --- a/config/migrate-scripts/migrate-db-88b7097.sh +++ b/config/migrate-scripts/migrate-db-88b7097.sh @@ -1,3 +1,3 @@ #!/bin/sh -psql invidious -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz;" +psql invidious kemal -c "ALTER TABLE channel_videos ADD COLUMN premiere_timestamp timestamptz;" diff --git a/config/migrate-scripts/migrate-db-8e884fe.sh b/config/migrate-scripts/migrate-db-8e884fe.sh index 095c7632..1c8dafd1 100755 --- a/config/migrate-scripts/migrate-db-8e884fe.sh +++ b/config/migrate-scripts/migrate-db-8e884fe.sh @@ -1,5 +1,5 @@ #!/bin/sh -psql invidious -c "ALTER TABLE channels DROP COLUMN subscribed" -psql invidious -c "ALTER TABLE channels ADD COLUMN subscribed timestamptz" -psql invidious -c "UPDATE channels SET subscribed = '2019-01-01 00:00:00+00'" +psql invidious kemal -c "ALTER TABLE channels DROP COLUMN subscribed" +psql invidious kemal -c "ALTER TABLE channels ADD COLUMN subscribed timestamptz" +psql invidious kemal -c "UPDATE channels SET subscribed = '2019-01-01 00:00:00+00'" diff --git a/docker/entrypoint.postgres.sh b/docker/entrypoint.postgres.sh index 8f987201..64bcfe48 100755 --- a/docker/entrypoint.postgres.sh +++ b/docker/entrypoint.postgres.sh @@ -12,12 +12,12 @@ if [ ! -f /var/lib/postgresql/data/setupFinished ]; then >&2 echo "### importing table schemas" su postgres -c 'createdb invidious' su postgres -c 'psql -c "CREATE USER kemal WITH PASSWORD '"'kemal'"'"' - su postgres -c 'psql invidious < config/sql/channels.sql' - su postgres -c 'psql invidious < config/sql/videos.sql' - su postgres -c 'psql invidious < config/sql/channel_videos.sql' - su postgres -c 'psql invidious < config/sql/users.sql' - su postgres -c 'psql invidious < config/sql/session_ids.sql' - su postgres -c 'psql invidious < config/sql/nonces.sql' + su postgres -c 'psql invidious kemal < config/sql/channels.sql' + su postgres -c 'psql invidious kemal < config/sql/videos.sql' + su postgres -c 'psql invidious kemal < config/sql/channel_videos.sql' + su postgres -c 'psql invidious kemal < config/sql/users.sql' + su postgres -c 'psql invidious kemal < config/sql/session_ids.sql' + su postgres -c 'psql invidious kemal < config/sql/nonces.sql' touch /var/lib/postgresql/data/setupFinished echo "### invidious database setup finished" exit From ca07d754052197384cd166463900669592a10b2f Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 6 Apr 2019 08:28:53 -0500 Subject: [PATCH 017/210] Add '--version' to command line --- README.md | 6 ++-- src/invidious.cr | 88 ++++++++++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 0d02fe32..70f2c500 100644 --- a/README.md +++ b/README.md @@ -172,15 +172,12 @@ Usage: invidious [arguments] --ssl-key-file FILE SSL key file --ssl-cert-file FILE SSL certificate file -h, --help Shows this help - -t THREADS, --crawl-threads=THREADS - Number of threads for crawling YouTube (default: 0) -c THREADS, --channel-threads=THREADS Number of threads for refreshing channels (default: 1) -f THREADS, --feed-threads=THREADS Number of threads for refreshing feeds (default: 1) - -v THREADS, --video-threads=THREADS - Number of threads for refreshing videos (default: 0) -o OUTPUT, --output=OUTPUT Redirect output (default: STDOUT) + -v, --version Print version ``` Or for development: @@ -188,6 +185,7 @@ Or for development: ```bash $ curl -fsSLo- https://raw.githubusercontent.com/samueleaton/sentry/master/install.cr | crystal eval $ ./sentry +🤖 Your SentryBot is vigilant. beep-boop... ``` ## Documentation diff --git a/src/invidious.cr b/src/invidious.cr index 53edf843..9aa27205 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -31,6 +31,47 @@ require "./invidious/*" CONFIG = Config.from_yaml(File.read("config/config.yml")) HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32) +PG_URL = URI.new( + scheme: "postgres", + user: CONFIG.db[:user], + password: CONFIG.db[:password], + host: CONFIG.db[:host], + port: CONFIG.db[:port], + path: CONFIG.db[:dbname], +) + +PG_DB = DB.open PG_URL +ARCHIVE_URL = URI.parse("https://archive.org") +LOGIN_URL = URI.parse("https://accounts.google.com") +PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com") +REDDIT_URL = URI.parse("https://www.reddit.com") +TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@protonmail.com.json") +YT_URL = URI.parse("https://www.youtube.com") +CHARS_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" +CURRENT_BRANCH = {{ "#{`git branch | sed -n '/\* /s///p'`.strip}" }} +CURRENT_COMMIT = {{ "#{`git rev-list HEAD --max-count=1 --abbrev-commit`.strip}" }} +CURRENT_VERSION = {{ "#{`git describe --tags --abbrev=0`.strip}" }} + +SOFTWARE = { + "name" => "invidious", + "version" => "#{CURRENT_VERSION}-#{CURRENT_COMMIT}", + "branch" => "#{CURRENT_BRANCH}", +} + +LOCALES = { + "ar" => load_locale("ar"), + "de" => load_locale("de"), + "en-US" => load_locale("en-US"), + "es" => load_locale("es"), + "eu" => load_locale("eu"), + "fr" => load_locale("fr"), + "it" => load_locale("it"), + "nb_NO" => load_locale("nb_NO"), + "nl" => load_locale("nl"), + "pl" => load_locale("pl"), + "ru" => load_locale("ru"), +} + config = CONFIG logger = Invidious::LogHandler.new @@ -56,45 +97,14 @@ Kemal.config.extra_options do |parser| FileUtils.mkdir_p(File.dirname(output)) logger = Invidious::LogHandler.new(File.open(output, mode: "a")) end + parser.on("-v", "--version", "Print version") do |output| + puts SOFTWARE.to_pretty_json + exit + end end Kemal::CLI.new ARGV -PG_URL = URI.new( - scheme: "postgres", - user: CONFIG.db[:user], - password: CONFIG.db[:password], - host: CONFIG.db[:host], - port: CONFIG.db[:port], - path: CONFIG.db[:dbname], -) - -PG_DB = DB.open PG_URL -ARCHIVE_URL = URI.parse("https://archive.org") -LOGIN_URL = URI.parse("https://accounts.google.com") -PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com") -REDDIT_URL = URI.parse("https://www.reddit.com") -TEXTCAPTCHA_URL = URI.parse("http://textcaptcha.com/omarroth@protonmail.com.json") -YT_URL = URI.parse("https://www.youtube.com") -CHARS_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" -CURRENT_BRANCH = {{ "#{`git branch | sed -n '/\* /s///p'`.strip}" }} -CURRENT_COMMIT = {{ "#{`git rev-list HEAD --max-count=1 --abbrev-commit`.strip}" }} -CURRENT_VERSION = {{ "#{`git describe --tags --abbrev=0`.strip}" }} - -LOCALES = { - "ar" => load_locale("ar"), - "de" => load_locale("de"), - "en-US" => load_locale("en-US"), - "es" => load_locale("es"), - "eu" => load_locale("eu"), - "fr" => load_locale("fr"), - "it" => load_locale("it"), - "nb_NO" => load_locale("nb_NO"), - "nl" => load_locale("nl"), - "pl" => load_locale("pl"), - "ru" => load_locale("ru"), -} - refresh_channels(PG_DB, logger, config.channel_threads, config.full_refresh) refresh_feeds(PG_DB, logger, config.feed_threads) @@ -108,12 +118,8 @@ if config.statistics_enabled spawn do loop do statistics = { - "version" => "2.0", - "software" => { - "name" => "invidious", - "version" => "#{CURRENT_VERSION}-#{CURRENT_COMMIT}", - "branch" => "#{CURRENT_BRANCH}", - }, + "version" => "2.0", + "software" => SOFTWARE, "openRegistrations" => config.registration_enabled, "usage" => { "users" => { From c8cf4fe09c80a40d7377466c4fb0c749ee4168d9 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 7 Apr 2019 12:59:12 -0500 Subject: [PATCH 018/210] Fix subscription_ajax for Google accounts --- src/invidious.cr | 216 ++++++++++-------- .../components/subscribe_widget_script.ecr | 4 +- 2 files changed, 125 insertions(+), 95 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 9aa27205..b0525b98 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1441,11 +1441,20 @@ get "/modify_notifications" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" - referer = get_referer(env) + sid = env.get? "sid" + referer = get_referer(env, "/") - if user - user = user.as(User) + redirect = env.params.query["redirect"]? + redirect ||= "false" + redirect = redirect == "true" + if !user && !sid + next env.redirect referer + end + + user = user.as(User) + + if !user.password channel_req = {} of String => String channel_req["receive_all_updates"] = env.params.query["receive_all_updates"]? || "true" @@ -1458,30 +1467,132 @@ get "/modify_notifications" do |env| headers["Cookie"] = env.request.headers["Cookie"] client = make_client(YT_URL) - subs = client.get("/subscription_manager?disable_polymer=1", headers) - headers["Cookie"] += "; " + subs.cookies.add_request_headers(headers)["Cookie"] - match = subs.body.match(/'XSRF_TOKEN': "(?[A-Za-z0-9\_\-\=]+)"/) + html = client.get("/subscription_manager?disable_polymer=1", headers) + + cookies = HTTP::Cookies.from_headers(headers) + html.cookies.each do |cookie| + if {"VISITOR_INFO1_LIVE", "YSC", "SIDCC"}.includes? cookie.name + if cookies[cookie.name]? + cookies[cookie.name] = cookie + else + cookies << cookie + end + end + end + headers = cookies.add_request_headers(headers) + + match = html.body.match(/'XSRF_TOKEN': "(?[A-Za-z0-9\_\-\=]+)"/) if match session_token = match["session_token"] else next env.redirect referer end + headers["content-type"] = "application/x-www-form-urlencoded" channel_req["session_token"] = session_token - headers["content-type"] = "application/x-www-form-urlencoded" - subs = XML.parse_html(subs.body) + subs = XML.parse_html(html.body) subs.xpath_nodes(%q(//a[@class="subscription-title yt-uix-sessionlink"]/@href)).each do |channel| channel_id = channel.content.lstrip("/channel/").not_nil! - channel_req["channel_id"] = channel_id - client.post("/subscription_ajax?action_update_subscription_preferences=1", headers, - HTTP::Params.encode(channel_req)).body + client.post("/subscription_ajax?action_update_subscription_preferences=1", headers, form: channel_req) end end - env.redirect referer + if redirect + env.redirect referer + else + env.response.content_type = "application/json" + "{}" + end +end + +# TODO: Add CSRF +get "/subscription_ajax" do |env| + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env, "/") + + redirect = env.params.query["redirect"]? + redirect ||= "false" + redirect = redirect == "true" + + if !user && !sid + next env.redirect referer + end + + user = user.as(User) + + if env.params.query["action_create_subscription_to_channel"]? + action = "action_create_subscription_to_channel" + elsif env.params.query["action_remove_subscriptions"]? + action = "action_remove_subscriptions" + else + next env.redirect referer + end + + channel_id = env.params.query["c"]? + channel_id ||= "" + + if !user.password + headers = HTTP::Headers.new + headers["Cookie"] = env.request.headers["Cookie"] + + client = make_client(YT_URL) + html = client.get("/subscription_manager?disable_polymer=1", headers) + + cookies = HTTP::Cookies.from_headers(headers) + html.cookies.each do |cookie| + if {"VISITOR_INFO1_LIVE", "YSC", "SIDCC"}.includes? cookie.name + if cookies[cookie.name]? + cookies[cookie.name] = cookie + else + cookies << cookie + end + end + end + headers = cookies.add_request_headers(headers) + + match = html.body.match(/'XSRF_TOKEN': "(?[A-Za-z0-9\_\-\=]+)"/) + if match + session_token = match["session_token"] + else + next env.redirect referer + end + + headers["content-type"] = "application/x-www-form-urlencoded" + + post_req = { + "session_token" => session_token, + } + post_url = "/subscription_ajax?#{action}=1&c=#{channel_id}" + + # Sync subscription with YouTube + client.post(post_url, headers, form: post_req) + email = user.email + else + email = user.email + end + + case action + when .starts_with? "action_create" + if !user.subscriptions.includes? channel_id + get_channel(channel_id, PG_DB, false, false) + PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", channel_id, email) + end + when .starts_with? "action_remove" + PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE email = $2", channel_id, email) + end + + if redirect + env.redirect referer + else + env.response.content_type = "application/json" + "{}" + end end get "/subscription_manager" do |env| @@ -1675,87 +1786,6 @@ post "/data_control" do |env| env.redirect referer end -get "/subscription_ajax" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? - - user = env.get? "user" - referer = get_referer(env) - - redirect = env.params.query["redirect"]? - redirect ||= "false" - redirect = redirect == "true" - - if user - user = user.as(User) - - if env.params.query["action_create_subscription_to_channel"]? - action = "action_create_subscription_to_channel" - elsif env.params.query["action_remove_subscriptions"]? - action = "action_remove_subscriptions" - else - next env.redirect referer - end - - channel_id = env.params.query["c"]? - channel_id ||= "" - - if !user.password - headers = HTTP::Headers.new - headers["Cookie"] = env.request.headers["Cookie"] - - client = make_client(YT_URL) - subs = client.get("/subscription_manager?disable_polymer=1", headers) - headers["Cookie"] += "; " + subs.cookies.add_request_headers(headers)["Cookie"] - match = subs.body.match(/'XSRF_TOKEN': "(?[A-Za-z0-9\_\-\=]+)"/) - if match - session_token = match["session_token"] - else - next env.redirect referer - end - - headers["content-type"] = "application/x-www-form-urlencoded" - - post_req = { - "session_token" => session_token, - } - post_req = HTTP::Params.encode(post_req) - post_url = "/subscription_ajax?#{action}=1&c=#{channel_id}" - - # Update user - if client.post(post_url, headers, post_req).status_code == 200 - email = user.email - - case action - when .starts_with? "action_create" - PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", channel_id, email) - when .starts_with? "action_remove" - PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE email = $2", channel_id, email) - end - end - else - email = user.email - - case action - when .starts_with? "action_create" - if !user.subscriptions.includes? channel_id - PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", channel_id, email) - - get_channel(channel_id, PG_DB, false, false) - end - when .starts_with? "action_remove" - PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE email = $2", channel_id, email) - end - end - end - - if redirect - env.redirect referer - else - env.response.content_type = "application/json" - "{}" - end -end - get "/delete_account" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? diff --git a/src/invidious/views/components/subscribe_widget_script.ecr b/src/invidious/views/components/subscribe_widget_script.ecr index a6102135..9bfd8ebb 100644 --- a/src/invidious/views/components/subscribe_widget_script.ecr +++ b/src/invidious/views/components/subscribe_widget_script.ecr @@ -11,7 +11,7 @@ function subscribe(timeouts = 0) { return; } - var url = "/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>"; + var url = "/subscription_ajax?action_create_subscription_to_channel=1&redirect=false&c=<%= ucid %>&referer=<%= env.get("current_page") %>"; var xhr = new XMLHttpRequest(); xhr.responseType = "json"; xhr.timeout = 20000; @@ -46,7 +46,7 @@ function unsubscribe(timeouts = 0) { return; } - var url = "/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>"; + var url = "/subscription_ajax?action_remove_subscriptions=1&redirect=false&c=<%= ucid %>&referer=<%= env.get("current_page") %>"; var xhr = new XMLHttpRequest(); xhr.responseType = "json"; xhr.timeout = 20000; From a1b3b475734816a93f7336fa08b260788c6bc0ec Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 7 Apr 2019 14:01:08 -0500 Subject: [PATCH 019/210] Add CSP, STS, and Referrer-Policy --- src/invidious.cr | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index b0525b98..c7b42578 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -169,6 +169,12 @@ proxies = PROXY_LIST before_all do |env| env.response.headers["X-XSS-Protection"] = "1; mode=block;" env.response.headers["X-Content-Type-Options"] = "nosniff" + env.response.headers["Content-Security-Policy"] = "default-src data: 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' https://*.googlevideo.com:443" + env.response.headers["Referrer-Policy"] = "same-origin" + + if Kemal.config.ssl || config.https_only + env.response.headers["Strict-Transport-Security"] = "max-age=604800; includeSubDomains" + end begin preferences = Preferences.from_json(env.request.cookies["PREFS"]?.try &.value || "{}") @@ -4578,13 +4584,15 @@ end # Add redirect if SSL is enabled if Kemal.config.ssl spawn do - server = HTTP::Server.new do |context| - redirect_url = "https://#{context.request.host}#{context.request.path}" - if context.request.query - redirect_url += "?#{context.request.query}" + server = HTTP::Server.new do |env| + redirect_url = "https://#{env.request.host}#{env.request.path}" + if env.request.query + redirect_url += "?#{env.request.query}" end - context.response.headers.add("Location", redirect_url) - context.response.status_code = 301 + + env.response.headers["Strict-Transport-Security"] = "max-age=604800; includeSubDomains" + env.response.headers["Location"] = redirect_url + env.response.status_code = 301 end server.bind_tcp "0.0.0.0", 80 From 4bc6501b8d6dfdb5a5b24f468b40a6f3f06d19cf Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 8 Apr 2019 09:36:12 -0500 Subject: [PATCH 020/210] Add 'blob' to CSP --- src/invidious.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious.cr b/src/invidious.cr index c7b42578..b0900aa0 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -169,7 +169,7 @@ proxies = PROXY_LIST before_all do |env| env.response.headers["X-XSS-Protection"] = "1; mode=block;" env.response.headers["X-Content-Type-Options"] = "nosniff" - env.response.headers["Content-Security-Policy"] = "default-src data: 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' https://*.googlevideo.com:443" + env.response.headers["Content-Security-Policy"] = "default-src blob: data: 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' https://*.googlevideo.com:443" env.response.headers["Referrer-Policy"] = "same-origin" if Kemal.config.ssl || config.https_only From 4aededf038a7f9ce8e99e29a8e6e7b6c5130e4b5 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 8 Apr 2019 09:39:47 -0500 Subject: [PATCH 021/210] Add media-src blob: to CSP --- src/invidious.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious.cr b/src/invidious.cr index b0900aa0..a58749ac 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -169,7 +169,7 @@ proxies = PROXY_LIST before_all do |env| env.response.headers["X-XSS-Protection"] = "1; mode=block;" env.response.headers["X-Content-Type-Options"] = "nosniff" - env.response.headers["Content-Security-Policy"] = "default-src blob: data: 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' https://*.googlevideo.com:443" + env.response.headers["Content-Security-Policy"] = "default-src blob: data: 'self' 'unsafe-inline' 'unsafe-eval'; media-src blob: 'self' https://*.googlevideo.com:443" env.response.headers["Referrer-Policy"] = "same-origin" if Kemal.config.ssl || config.https_only From c85903383a2a9846538d41c1a9c4eff852789f71 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 8 Apr 2019 09:46:58 -0500 Subject: [PATCH 022/210] Fix to_json for storing user preferences --- src/invidious.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index a58749ac..06e96a53 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -956,7 +956,7 @@ post "/login" do |env| if env.request.cookies["PREFS"]? preferences = env.get("preferences").as(Preferences) - PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email) + PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences.to_json, user.email) cookie = env.request.cookies["PREFS"] cookie.expires = Time.new(1990, 1, 1) @@ -1129,8 +1129,8 @@ post "/login" do |env| end if env.request.cookies["PREFS"]? - preferences = env.get("preferences").as(Preferences).to_json - PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email) + preferences = env.get("preferences").as(Preferences) + PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences.to_json, user.email) cookie = env.request.cookies["PREFS"] cookie.expires = Time.new(1990, 1, 1) From b8c87632e6c8968de6a3c5f840e6183664448160 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Tue, 9 Apr 2019 17:41:25 -0500 Subject: [PATCH 023/210] Add feed link to watch history --- src/invidious/views/history.ecr | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/invidious/views/history.ecr b/src/invidious/views/history.ecr index a68b3274..017f5eae 100644 --- a/src/invidious/views/history.ecr +++ b/src/invidious/views/history.ecr @@ -3,9 +3,14 @@ <% end %>
-
+

<%= translate(locale, "`x` videos", %(#{user.watched.size})) %>

+

<%= translate(locale, "Clear watch history") %> From 5dc45c35e64c1e5882e0c01287b36ec88dff70a7 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 10 Apr 2019 16:23:37 -0500 Subject: [PATCH 024/210] Automatically migrate database --- src/invidious.cr | 10 ++- src/invidious/helpers/helpers.cr | 104 ++++++++++++++++++++++ src/invidious/{ => helpers}/jobs.cr | 2 +- src/invidious/helpers/macros.cr | 16 ++-- src/invidious/{ => helpers}/signatures.cr | 0 5 files changed, 123 insertions(+), 9 deletions(-) rename src/invidious/{ => helpers}/jobs.cr (98%) rename src/invidious/{ => helpers}/signatures.cr (100%) diff --git a/src/invidious.cr b/src/invidious.cr index 06e96a53..adc69829 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -105,10 +105,16 @@ end Kemal::CLI.new ARGV +# Check table integrity +analyze_table(PG_DB, logger, "channel_videos", ChannelVideo) +analyze_table(PG_DB, logger, "nonces", Nonce) +analyze_table(PG_DB, logger, "session_ids", SessionId) +analyze_table(PG_DB, logger, "users", User) +analyze_table(PG_DB, logger, "videos", Video) + +# Start jobs refresh_channels(PG_DB, logger, config.channel_threads, config.full_refresh) - refresh_feeds(PG_DB, logger, config.feed_threads) - subscribe_to_feeds(PG_DB, logger, HMAC_KEY, config) statistics = { diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 3b53a468..f515f28a 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -1,5 +1,20 @@ require "./macros" +struct Nonce + db_mapping({ + nonce: String, + expire: Time, + }) +end + +struct SessionId + db_mapping({ + id: String, + email: String, + issued: String, + }) +end + struct ConfigPreferences module StringToArray def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder) @@ -483,3 +498,92 @@ def extract_shelf_items(nodeset, ucid = nil, author_name = nil) return items end + +def analyze_table(db, logger, table_name, struct_type = nil) + # Create table if it doesn't exist + if !db.query_one?("SELECT true FROM information_schema.tables WHERE table_name = $1", table_name, as: Bool) + db.using_connection do |conn| + conn.as(PG::Connection).exec_all(File.read("config/sql/#{table_name}.sql")) + end + + logger.write("CREATE TABLE #{table_name}\n") + end + + if !struct_type + return + end + + struct_array = struct_type.to_type_tuple + column_array = get_column_array(db, table_name) + column_types = File.read("config/sql/#{table_name}.sql").match(/CREATE TABLE public\.#{table_name}\n\((?[\d\D]*?)\);/) + .try &.["types"].split(",").map { |line| line.strip } + + if !column_types + return + end + + struct_array.each_with_index do |name, i| + if name != column_array[i]? + if !column_array[i]? + new_column = column_types.select { |line| line.starts_with? name }[0] + db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") + logger.write("ALTER TABLE #{table_name} ADD COLUMN #{new_column}\n") + next + end + + # Column doesn't exist + if !column_array.includes? name + new_column = column_types.select { |line| line.starts_with? name }[0] + db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") + end + + # Column exists but in the wrong position, rotate + if struct_array.includes? column_array[i] + until name == column_array[i] + new_column = column_types.select { |line| line.starts_with? column_array[i] }[0]?.try &.gsub("#{column_array[i]}", "#{column_array[i]}_new") + + # There's a column we didn't expect + if !new_column + db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") + logger.write("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]}\n") + + column_array = get_column_array(db, table_name) + next + end + + db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") + logger.write("ALTER TABLE #{table_name} ADD COLUMN #{new_column}\n") + db.exec("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}") + logger.write("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}\n") + db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") + logger.write("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE\n") + db.exec("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}") + logger.write("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}\n") + + column_array = get_column_array(db, table_name) + end + else + db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") + logger.write("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE\n") + end + end + end +end + +class PG::ResultSet + def field(index = @column_index) + @fields.not_nil![index] + end +end + +def get_column_array(db, table_name) + column_array = [] of String + db.query("SELECT * FROM #{table_name} LIMIT 0") do |rs| + rs.column_count.times do |i| + column = rs.as(PG::ResultSet).field(i) + column_array << column.name + end + end + + return column_array +end diff --git a/src/invidious/jobs.cr b/src/invidious/helpers/jobs.cr similarity index 98% rename from src/invidious/jobs.cr rename to src/invidious/helpers/jobs.cr index 82e58c93..ad9e8ada 100644 --- a/src/invidious/jobs.cr +++ b/src/invidious/helpers/jobs.cr @@ -66,7 +66,7 @@ def refresh_feeds(db, logger, max_threads = 1) spawn do begin db.query("SELECT * FROM #{view_name} LIMIT 1") do |rs| - # View doesn't contain same number of rows as ChannelVideo + # Drop view that doesn't contain same number of rows as ChannelVideo if ChannelVideo.from_rs(rs)[0]?.try &.to_a.size.try &.!= rs.column_count db.exec("DROP MATERIALIZED VIEW #{view_name}") raise "valid schema does not exist" diff --git a/src/invidious/helpers/macros.cr b/src/invidious/helpers/macros.cr index fda34ed7..fe1fc94e 100644 --- a/src/invidious/helpers/macros.cr +++ b/src/invidious/helpers/macros.cr @@ -3,10 +3,14 @@ macro db_mapping(mapping) end def to_a - return [{{*mapping.keys.map { |id| "@#{id}".id }}}] + return [ {{*mapping.keys.map { |id| "@#{id}".id }}} ] end - DB.mapping({{mapping}}) + def self.to_type_tuple + return { {{*mapping.keys.map { |id| "#{id}" }}} } + end + + DB.mapping( {{mapping}} ) end macro json_mapping(mapping) @@ -14,11 +18,11 @@ macro json_mapping(mapping) end def to_a - return [{{*mapping.keys.map { |id| "@#{id}".id }}}] + return [ {{*mapping.keys.map { |id| "@#{id}".id }}} ] end - JSON.mapping({{mapping}}) - YAML.mapping({{mapping}}) + JSON.mapping( {{mapping}} ) + YAML.mapping( {{mapping}} ) end macro yaml_mapping(mapping) @@ -26,7 +30,7 @@ macro yaml_mapping(mapping) end def to_a - return [{{*mapping.keys.map { |id| "@#{id}".id }}}] + return [ {{*mapping.keys.map { |id| "@#{id}".id }}} ] end def to_tuple diff --git a/src/invidious/signatures.cr b/src/invidious/helpers/signatures.cr similarity index 100% rename from src/invidious/signatures.cr rename to src/invidious/helpers/signatures.cr From aad0f90a9daf4219b06e874d23efc923bc9b09b6 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 10 Apr 2019 16:58:46 -0500 Subject: [PATCH 025/210] Add 'sign_token' --- spec/helpers_spec.cr | 24 ++++++++++++++++++++++++ src/invidious/users.cr | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/spec/helpers_spec.cr b/spec/helpers_spec.cr index 307e52d2..215ffc76 100644 --- a/spec/helpers_spec.cr +++ b/spec/helpers_spec.cr @@ -1,4 +1,5 @@ require "kemal" +require "openssl/hmac" require "pg" require "spec" require "yaml" @@ -81,4 +82,27 @@ describe "Helpers" do produce_comment_reply_continuation("_cE8xSu6swE", "UC1AZY74-dGVPe6bfxFwwEMg", "UgyBUaRGHB9Jmt1dsUZ4AaABAg").should eq("EiYSC19jRTh4U3U2c3dFwAEByAEB4AEBogINKP___________wFAABgGMk0aSxIaVWd5QlVhUkdIQjlKbXQxZHNVWjRBYUFCQWciAggAKhhVQzFBWlk3NC1kR1ZQZTZiZnhGd3dFTWcyC19jRTh4U3U2c3dFQAFICg%3D%3D") end end + + describe "#sign_token" do + it "correctly signs a given hash" do + token = { + "session" => "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "expires" => 1554680038, + "scopes" => [ + ":notifications", + ":subscriptions/*", + "GET:tokens*", + ], + "signature" => "f//2hS20th8pALF305PJFK+D2aVtvefNnQheILHD2vU=", + } + sign_token("SECRET_KEY", token).should eq(token["signature"]) + + token = { + "session" => "v1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "scopes" => [":notifications", "POST:subscriptions/*"], + "signature" => "fNvXoT0MRAL9eE6lTE33CEg8HitYJDOL9a22rSN2Ihg=", + } + sign_token("SECRET_KEY", token).should eq(token["signature"]) + end + end end diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 40f24870..ce0bd0ab 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -211,6 +211,25 @@ def create_response(user_id, operation, key, db, expire = 6.hours) return challenge, token end +def sign_token(key, hash) + string_to_sign = [] of String + hash.each do |key, value| + if key == "signature" + next + end + + case value + when Array + string_to_sign << "#{key}=#{value.sort.join(",")}" + else + string_to_sign << "#{key}=#{value}" + end + end + + string_to_sign = string_to_sign.sort.join("\n") + return Base64.encode(OpenSSL::HMAC.digest(:sha256, key, string_to_sign)).strip +end + def validate_response(challenge, token, user_id, operation, key, db, locale) if !challenge raise translate(locale, "Hidden field \"challenge\" is a required field") From 373b890e1d0470ac222a5b8fc388e9ffe2350ca4 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 10 Apr 2019 17:09:36 -0500 Subject: [PATCH 026/210] Log command before execution --- src/invidious/helpers/helpers.cr | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index f515f28a..3c75e7a7 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -502,11 +502,11 @@ end def analyze_table(db, logger, table_name, struct_type = nil) # Create table if it doesn't exist if !db.query_one?("SELECT true FROM information_schema.tables WHERE table_name = $1", table_name, as: Bool) + logger.write("CREATE TABLE #{table_name}\n") + db.using_connection do |conn| conn.as(PG::Connection).exec_all(File.read("config/sql/#{table_name}.sql")) end - - logger.write("CREATE TABLE #{table_name}\n") end if !struct_type @@ -526,8 +526,8 @@ def analyze_table(db, logger, table_name, struct_type = nil) if name != column_array[i]? if !column_array[i]? new_column = column_types.select { |line| line.starts_with? name }[0] - db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") logger.write("ALTER TABLE #{table_name} ADD COLUMN #{new_column}\n") + db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") next end @@ -544,27 +544,27 @@ def analyze_table(db, logger, table_name, struct_type = nil) # There's a column we didn't expect if !new_column - db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") logger.write("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]}\n") + db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") column_array = get_column_array(db, table_name) next end - db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") logger.write("ALTER TABLE #{table_name} ADD COLUMN #{new_column}\n") - db.exec("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}") + db.exec("ALTER TABLE #{table_name} ADD COLUMN #{new_column}") logger.write("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}\n") - db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") + db.exec("UPDATE #{table_name} SET #{column_array[i]}_new=#{column_array[i]}") logger.write("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE\n") - db.exec("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}") + db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") logger.write("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}\n") + db.exec("ALTER TABLE #{table_name} RENAME COLUMN #{column_array[i]}_new TO #{column_array[i]}") column_array = get_column_array(db, table_name) end else - db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") logger.write("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE\n") + db.exec("ALTER TABLE #{table_name} DROP COLUMN #{column_array[i]} CASCADE") end end end From 28d5bedcc7376b3749eb62a77decbb46c131dcd2 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 10 Apr 2019 17:16:18 -0500 Subject: [PATCH 027/210] Speed up table creation --- src/invidious/helpers/helpers.cr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 3c75e7a7..bf96842f 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -501,7 +501,9 @@ end def analyze_table(db, logger, table_name, struct_type = nil) # Create table if it doesn't exist - if !db.query_one?("SELECT true FROM information_schema.tables WHERE table_name = $1", table_name, as: Bool) + begin + db.exec("SELECT * FROM #{table_name} LIMIT 0") + rescue ex logger.write("CREATE TABLE #{table_name}\n") db.using_connection do |conn| From 8640d6bb1ee84c095155b2146709c2cac7c7ff2a Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 10 Apr 2019 18:02:13 -0500 Subject: [PATCH 028/210] Add 'extract_polymer_config' --- src/invidious/videos.cr | 162 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 10120c4e..2f5faf98 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -601,6 +601,168 @@ def get_video(id, db, proxies = {} of String => Array({ip: String, port: Int32}) return video end +def extract_polymer_config(body, html) + params = HTTP::Params.new + + params["session_token"] = body.match(/"XSRF_TOKEN":"(?[A-Za-z0-9\_\-\=]+)"/).try &.["session_token"] || "" + + html_info = JSON.parse(body.match(/ytplayer\.config = (?.*?);ytplayer\.load/).try &.["info"] || "{}").try &.["args"]?.try &.as_h + + if html_info + html_info.each do |key, value| + params[key] = value.to_s + end + end + + initial_data = JSON.parse(body.match(/window\["ytInitialData"\] = (?.*?);\n/).try &.["info"] || "{}") + + primary_results = initial_data["contents"]? + .try &.["twoColumnWatchNextResults"]? + .try &.["results"]? + .try &.["results"]? + .try &.["contents"]? + + comment_continuation = primary_results.try &.as_a.select { |object| object["itemSectionRenderer"]? }[0]? + .try &.["itemSectionRenderer"]? + .try &.["continuations"]? + .try &.[0]? + .try &.["nextContinuationData"]? + + params["ctoken"] = comment_continuation.try &.["continuation"]?.try &.as_s || "" + params["itct"] = comment_continuation.try &.["clickTrackingParams"]?.try &.as_s || "" + + recommended_videos = initial_data["contents"]? + .try &.["twoColumnWatchNextResults"]? + .try &.["secondaryResults"]? + .try &.["secondaryResults"]? + .try &.["results"]? + .try &.as_a + + rvs = [] of String + + recommended_videos.try &.each do |compact_renderer| + if compact_renderer["compactRadioRenderer"]? || compact_renderer["compactPlaylistRenderer"]? + # TODO + elsif compact_renderer["compactVideoRenderer"]? + compact_renderer = compact_renderer["compactVideoRenderer"] + + recommended_video = HTTP::Params.new + recommended_video["id"] = compact_renderer["videoId"].as_s + recommended_video["title"] = compact_renderer["title"]["simpleText"].as_s + recommended_video["author"] = compact_renderer["shortBylineText"]["runs"].as_a[0]["text"].as_s + recommended_video["ucid"] = compact_renderer["shortBylineText"]["runs"].as_a[0]["navigationEndpoint"]["browseEndpoint"]["browseId"].as_s + recommended_video["author_thumbnail"] = compact_renderer["channelThumbnail"]["thumbnails"][0]["url"].as_s + + recommended_video["short_view_count_text"] = compact_renderer["shortViewCountText"]["simpleText"].as_s + recommended_video["view_count"] = compact_renderer["viewCountText"]?.try &.["simpleText"]?.try &.as_s.delete(", views").to_i64?.try &.to_s || "0" + recommended_video["length_seconds"] = decode_length_seconds(compact_renderer["lengthText"]?.try &.["simpleText"]?.try &.as_s || "0:00").to_s + + rvs << recommended_video.to_s + end + end + params["rvs"] = rvs.join(",") + + # TODO: Watching now + params["views"] = primary_results.try &.as_a.select { |object| object["videoPrimaryInfoRenderer"]? }[0]? + .try &.["videoPrimaryInfoRenderer"]? + .try &.["viewCount"]? + .try &.["videoViewCountRenderer"]? + .try &.["viewCount"]? + .try &.["simpleText"]? + .try &.as_s.gsub(/\D/, "").to_i64.to_s || "0" + + sentiment_bar = primary_results.try &.as_a.select { |object| object["videoPrimaryInfoRenderer"]? }[0]? + .try &.["videoPrimaryInfoRenderer"]? + .try &.["sentimentBar"]? + .try &.["sentimentBarRenderer"]? + .try &.["tooltip"]? + .try &.as_s + + likes, dislikes = sentiment_bar.try &.split(" / ").map { |a| a.delete(", ").to_i32 }[0, 2] || {0, 0} + + params["likes"] = "#{likes}" + params["dislikes"] = "#{dislikes}" + + published = primary_results.try &.as_a.select { |object| object["videoSecondaryInfoRenderer"]? }[0]? + .try &.["videoSecondaryInfoRenderer"]? + .try &.["dateText"]? + .try &.["simpleText"]? + .try &.as_s.split(" ")[-3..-1].join(" ") + + if published + params["published"] = Time.parse(published, "%b %-d, %Y", Time::Location.local).to_unix.to_s + else + params["published"] = Time.new(1990, 1, 1).to_unix.to_s + end + + params["description_html"] = "

" + + description_html = primary_results.try &.as_a.select { |object| object["videoSecondaryInfoRenderer"]? }[0]? + .try &.["videoSecondaryInfoRenderer"]? + .try &.["description"]? + .try &.["runs"]? + .try &.as_a + + if description_html + params["description_html"] = content_to_comment_html(description_html) + end + + metadata = primary_results.try &.as_a.select { |object| object["videoSecondaryInfoRenderer"]? }[0]? + .try &.["videoSecondaryInfoRenderer"]? + .try &.["metadataRowContainer"]? + .try &.["metadataRowContainerRenderer"]? + .try &.["rows"]? + .try &.as_a + + params["genre"] = "" + params["genre_ucid"] = "" + params["license"] = "" + + metadata.try &.each do |row| + title = row["metadataRowRenderer"]?.try &.["title"]?.try &.["simpleText"]?.try &.as_s + contents = row["metadataRowRenderer"]? + .try &.["contents"]? + .try &.as_a[0]? + + if title.try &.== "Category" + contents = contents.try &.["runs"]? + .try &.as_a[0]? + + params["genre"] = contents.try &.["text"]? + .try &.as_s || "" + params["genre_ucid"] = contents.try &.["navigationEndpoint"]? + .try &.["browseEndpoint"]? + .try &.["browseId"]?.try &.as_s || "" + elsif title.try &.== "License" + contents = contents.try &.["runs"]? + .try &.as_a[0]? + + params["license"] = contents.try &.["text"]? + .try &.as_s || "" + elsif title.try &.== "Licensed to YouTube by" + params["license"] = contents.try &.["simpleText"]? + .try &.as_s || "" + end + end + + author_info = primary_results.try &.as_a.select { |object| object["videoSecondaryInfoRenderer"]? }[0]? + .try &.["videoSecondaryInfoRenderer"]? + .try &.["owner"]? + .try &.["videoOwnerRenderer"]? + + params["author_thumbnail"] = author_info.try &.["thumbnail"]? + .try &.["thumbnails"]? + .try &.as_a[0]? + .try &.["url"]? + .try &.as_s || "" + + params["sub_count_text"] = author_info.try &.["subscriberCountText"]? + .try &.["simpleText"]? + .try &.as_s.gsub(/\D/, "") || "0" + + return params +end + def extract_player_config(body, html) params = HTTP::Params.new From e1c78fcbd304d8a1ed7fbe418526718578c94d6d Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 10 Apr 2019 19:56:38 -0500 Subject: [PATCH 029/210] Update view names to avoid collisions --- src/invidious.cr | 10 ++++----- src/invidious/helpers/jobs.cr | 42 ++++++++++++++++++++++------------- src/invidious/users.cr | 4 ++-- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index adc69829..2a314218 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -666,7 +666,7 @@ get "/search" do |env| user = env.get? "user" if user user = user.as(User) - view_name = "subscriptions_#{sha256(user.email)[0..7]}" + view_name = "subscriptions_#{sha256(user.email)}" end channel = nil @@ -1114,7 +1114,7 @@ post "/login" do |env| PG_DB.exec("INSERT INTO users VALUES (#{args})", user_array) PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.now) - view_name = "subscriptions_#{sha256(user.email)[0..7]}" + view_name = "subscriptions_#{sha256(user.email)}" PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ @@ -1834,7 +1834,7 @@ post "/delete_account" do |env| next templated "error" end - view_name = "subscriptions_#{sha256(user.email)[0..7]}" + view_name = "subscriptions_#{sha256(user.email)}" PG_DB.exec("DELETE FROM users * WHERE email = $1", user.email) PG_DB.exec("DELETE FROM session_ids * WHERE email = $1", user.email) PG_DB.exec("DROP MATERIALIZED VIEW #{view_name}") @@ -1969,7 +1969,7 @@ get "/feed/subscriptions" do |env| notifications = PG_DB.query_one("SELECT notifications FROM users WHERE email = $1", user.email, as: Array(String)) - view_name = "subscriptions_#{sha256(user.email)[0..7]}" + view_name = "subscriptions_#{sha256(user.email)}" if preferences.notifications_only && !notifications.empty? # Only show notifications @@ -2253,7 +2253,7 @@ get "/feed/private" do |env| sort = env.params.query["sort"]? sort ||= "published" - view_name = "subscriptions_#{sha256(user.email)[0..7]}" + view_name = "subscriptions_#{sha256(user.email)}" if latest_only videos = PG_DB.query_all("SELECT DISTINCT ON (ucid) * FROM #{view_name} \ diff --git a/src/invidious/helpers/jobs.cr b/src/invidious/helpers/jobs.cr index ad9e8ada..9afd859f 100644 --- a/src/invidious/helpers/jobs.cr +++ b/src/invidious/helpers/jobs.cr @@ -54,7 +54,7 @@ def refresh_feeds(db, logger, max_threads = 1) db.query("SELECT email FROM users") do |rs| rs.each do email = rs.read(String) - view_name = "subscriptions_#{sha256(email)[0..7]}" + view_name = "subscriptions_#{sha256(email)}" if active_threads >= max_threads if active_channel.receive @@ -65,28 +65,38 @@ def refresh_feeds(db, logger, max_threads = 1) active_threads += 1 spawn do begin - db.query("SELECT * FROM #{view_name} LIMIT 1") do |rs| - # Drop view that doesn't contain same number of rows as ChannelVideo - if ChannelVideo.from_rs(rs)[0]?.try &.to_a.size.try &.!= rs.column_count + # Drop outdated views + column_array = get_column_array(db, view_name) + ChannelVideo.to_type_tuple.each_with_index do |name, i| + if name != column_array[i]? + logger.write("DROP MATERIALIZED VIEW #{view_name}\n") db.exec("DROP MATERIALIZED VIEW #{view_name}") - raise "valid schema does not exist" + raise "view does not exist" end end db.exec("REFRESH MATERIALIZED VIEW #{view_name}") rescue ex - # Create view if it doesn't exist - if ex.message.try &.ends_with?("does not exist") - # While iterating through, we may have an email stored from a deleted account - if db.query_one?("SELECT true FROM users WHERE email = $1", email, as: Bool) - db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ - SELECT * FROM channel_videos WHERE \ - ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{email.gsub("'", "\\'")}')::text[]) \ - ORDER BY published DESC;") - logger.write("CREATE #{view_name}\n") + # Rename old views + begin + legacy_view_name = "subscriptions_#{sha256(email)[0..7]}" + + db.exec("SELECT * FROM #{legacy_view_name} LIMIT 0") + logger.write("RENAME MATERIALIZED VIEW #{legacy_view_name}\n") + db.exec("ALTER MATERIALIZED VIEW #{legacy_view_name} RENAME TO #{view_name}") + rescue ex + begin + # While iterating through, we may have an email stored from a deleted account + if db.query_one?("SELECT true FROM users WHERE email = $1", email, as: Bool) + logger.write("CREATE #{view_name}\n") + db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ + SELECT * FROM channel_videos WHERE \ + ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{email.gsub("'", "\\'")}')::text[]) \ + ORDER BY published DESC;") + end + rescue ex + logger.write("REFRESH #{email} : #{ex.message}\n") end - else - logger.write("REFRESH #{email} : #{ex.message}\n") end end diff --git a/src/invidious/users.cr b/src/invidious/users.cr index ce0bd0ab..f34d14ab 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -125,7 +125,7 @@ def get_user(sid, headers, db, refresh = true) ON CONFLICT (id) DO NOTHING", sid, user.email, Time.now) begin - view_name = "subscriptions_#{sha256(user.email)[0..7]}" + view_name = "subscriptions_#{sha256(user.email)}" db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ @@ -147,7 +147,7 @@ def get_user(sid, headers, db, refresh = true) ON CONFLICT (id) DO NOTHING", sid, user.email, Time.now) begin - view_name = "subscriptions_#{sha256(user.email)[0..7]}" + view_name = "subscriptions_#{sha256(user.email)}" db.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ SELECT * FROM channel_videos WHERE \ ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ From 611555514c31230bcdc1980e0b110cca057b3b72 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 11 Apr 2019 11:53:07 -0500 Subject: [PATCH 030/210] Remove unnecessary XML declaration --- src/invidious/comments.cr | 14 ++++++++------ src/invidious/videos.cr | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index aa3233d4..cd96565b 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -441,8 +441,12 @@ def replace_links(html) end end - html = html.to_xml(options: XML::SaveOptions::NO_DECL) - return html + html = html.xpath_node(%q(//body)).not_nil! + if node = html.xpath_node(%q(./p)) + html = node + end + + return html.to_xml(options: XML::SaveOptions::NO_DECL) end def fill_links(html, scheme, host) @@ -459,12 +463,10 @@ def fill_links(html, scheme, host) end if host == "www.youtube.com" - html = html.xpath_node(%q(//body)).not_nil!.to_xml - else - html = html.to_xml(options: XML::SaveOptions::NO_DECL) + html = html.xpath_node(%q(//body/p)).not_nil! end - return html + return html.to_xml(options: XML::SaveOptions::NO_DECL) end def content_to_comment_html(content) diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 2f5faf98..beaa1330 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -880,7 +880,7 @@ def fetch_video(id, proxies, region) info["avg_rating"] = "#{avg_rating}" description = html.xpath_node(%q(//p[@id="eow-description"])) - description = description ? description.to_xml : "" + description = description ? description.to_xml(options: XML::SaveOptions::NO_DECL) : "" wilson_score = ci_lower_bound(likes, likes + dislikes) From 5e141e869d0817ab179e287967451a7def135674 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 11 Apr 2019 12:08:43 -0500 Subject: [PATCH 031/210] Add subtitles to download widget --- src/invidious.cr | 14 +++++++++++++- src/invidious/views/watch.ecr | 5 +++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/invidious.cr b/src/invidious.cr index 2a314218..9a2bd260 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2722,6 +2722,11 @@ get "/api/v1/captions/:id" do |env| END_CUE end + if title = env.params.query["title"]? + # https://blog.fastmail.com/2011/06/24/download-non-english-filenames/ + env.response.headers["Content-Disposition"] = "attachment; filename=\"#{URI.escape(title)}\"; filename*=UTF-8''#{URI.escape(title)}" + end + webvtt end @@ -4214,11 +4219,18 @@ end get "/latest_version" do |env| if env.params.query["download_widget"]? download_widget = JSON.parse(env.params.query["download_widget"]) + id = download_widget["id"].as_s - itag = download_widget["itag"].as_s title = download_widget["title"].as_s + + if label = download_widget["label"]? + env.redirect "/api/v1/captions/#{id}?label=#{label}&title=#{title}" + next + else + itag = download_widget["itag"].as_s local = "true" end + end id ||= env.params.query["id"]? itag ||= env.params.query["itag"]? diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 18cbdef7..bd49709e 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -79,6 +79,11 @@ <%= itag_to_metadata?(option["itag"]).try &.["height"]? || "~240" %>p - <%= option["type"].split(";")[0] %> <% end %> + <% captions.each do |caption| %> + + <% end %>

From 4e6a931de39526dc90b1a3143b4cbec5751fa14d Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 11 Apr 2019 12:13:25 -0500 Subject: [PATCH 032/210] Make check_tables config option --- src/invidious.cr | 2 ++ src/invidious/helpers/helpers.cr | 1 + 2 files changed, 3 insertions(+) diff --git a/src/invidious.cr b/src/invidious.cr index 9a2bd260..cb095099 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -105,12 +105,14 @@ end Kemal::CLI.new ARGV +if CONFIG.check_tables # Check table integrity analyze_table(PG_DB, logger, "channel_videos", ChannelVideo) analyze_table(PG_DB, logger, "nonces", Nonce) analyze_table(PG_DB, logger, "session_ids", SessionId) analyze_table(PG_DB, logger, "users", User) analyze_table(PG_DB, logger, "videos", Video) +end # Start jobs refresh_channels(PG_DB, logger, config.channel_threads, config.full_refresh) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index bf96842f..d62e7ba6 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -115,6 +115,7 @@ user: String, converter: ConfigPreferencesConverter, }, dmca_content: {type: Array(String), default: [] of String}, # For compliance with DMCA, disables download widget using list of video IDs + check_tables: {type: Bool, default: false}, # Check table integrity, automatically try to add any missing columns, create tables, etc. }) end From aa8ff7ace3602febb4ac4ffb922d18f27628730e Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 11 Apr 2019 13:52:09 -0500 Subject: [PATCH 033/210] Always use ucid for channel search --- src/invidious.cr | 6 +----- src/invidious/search.cr | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index cb095099..6ecc0559 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2521,11 +2521,7 @@ get "/channel/:ucid" do |env| end if !auto_generated - if author.includes?(" ") || author.includes?("-") - env.set "search", "channel:#{ucid} " - else - env.set "search", "channel:#{author.downcase} " - end + env.set "search", "channel:#{ucid} " end if auto_generated diff --git a/src/invidious/search.cr b/src/invidious/search.cr index 9c985552..c3c48af3 100644 --- a/src/invidious/search.cr +++ b/src/invidious/search.cr @@ -53,12 +53,12 @@ alias SearchItem = SearchVideo | SearchChannel | SearchPlaylist def channel_search(query, page, channel) client = make_client(YT_URL) - response = client.get("/user/#{channel}?disable_polymer=1&hl=en&gl=US") + response = client.get("/channel/#{channel}?disable_polymer=1&hl=en&gl=US") document = XML.parse_html(response.body) canonical = document.xpath_node(%q(//link[@rel="canonical"])) if !canonical - response = client.get("/channel/#{channel}?disable_polymer=1&hl=en&gl=US") + response = client.get("/c/#{channel}?disable_polymer=1&hl=en&gl=US") document = XML.parse_html(response.body) canonical = document.xpath_node(%q(//link[@rel="canonical"])) end From d522c864d4028c9836fe85317bf27e3900f55fe6 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 11 Apr 2019 15:28:03 -0500 Subject: [PATCH 034/210] Add dashUrl to /api/v1/videos --- src/invidious.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/invidious.cr b/src/invidious.cr index 6ecc0559..83b389a7 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -3036,6 +3036,8 @@ get "/api/v1/videos/:id" do |env| json.field "hlsUrl", hlsvp end + json.field "dashUrl", "#{make_host_url(config, Kemal.config)}/api/manifest/dash/id/#{id}" + json.field "adaptiveFormats" do json.array do adaptive_fmts.each do |fmt| From 62a4c82e95212a45608708dddd967072b478157f Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 11 Apr 2019 17:00:00 -0500 Subject: [PATCH 035/210] Add storyboards and fix image caching --- src/invidious.cr | 65 +++++++++++++++++++++++++++++-- src/invidious/videos.cr | 86 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 83b389a7..f393e2d6 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2973,6 +2973,9 @@ get "/api/v1/videos/:id" do |env| json.field "videoThumbnails" do generate_thumbnails(json, video.id, config, Kemal.config) end + json.field "storyboards" do + generate_storyboards(json, video.storyboards, config, Kemal.config) + end video.description, description = html_to_content(video.description) @@ -4348,7 +4351,7 @@ get "/videoplayback" do |env| url = "/videoplayback?#{query_params.to_s}" headers = HTTP::Headers.new - {"Accept", "Accept-Encoding", "Connection", "Range"}.each do |header| + {"Accept", "Accept-Encoding", "Cache-Control", "Connection", "If-None-Match", "Range"}.each do |header| if env.request.headers[header]? headers[header] = env.request.headers[header] end @@ -4445,7 +4448,63 @@ get "/ggpht/*" do |env| url = env.request.path.lchop("/ggpht") headers = HTTP::Headers.new - {"Range", "Accept", "Accept-Encoding"}.each do |header| + {"Accept", "Accept-Encoding", "Cache-Control", "Connection", "If-None-Match", "Range"}.each do |header| + if env.request.headers[header]? + headers[header] = env.request.headers[header] + end + end + + client.get(url, headers) do |response| + env.response.status_code = response.status_code + response.headers.each do |key, value| + env.response.headers[key] = value + end + + if response.status_code == 304 + break + end + + chunk_size = 4096 + size = 1 + if response.headers.includes_word?("Content-Encoding", "gzip") + Gzip::Writer.open(env.response) do |deflate| + until size == 0 + size = IO.copy(response.body_io, deflate) + env.response.flush + end + end + elsif response.headers.includes_word?("Content-Encoding", "deflate") + Flate::Writer.open(env.response) do |deflate| + until size == 0 + size = IO.copy(response.body_io, deflate) + env.response.flush + end + end + else + until size == 0 + size = IO.copy(response.body_io, env.response, chunk_size) + env.response.flush + end + end + end +end + +get "/sb/:id/:storyboard/:index" do |env| + id = env.params.url["id"] + storyboard = env.params.url["storyboard"] + index = env.params.url["index"] + + if storyboard.starts_with? "storyboard_live" + host = "https://i.ytimg.com" + else + host = "https://i9.ytimg.com" + end + client = make_client(URI.parse(host)) + + url = "/sb/#{id}/#{storyboard}/#{index}?#{env.params.query}" + + headers = HTTP::Headers.new + {"Accept", "Accept-Encoding", "Cache-Control", "Connection", "If-None-Match", "Range"}.each do |header| if env.request.headers[header]? headers[header] = env.request.headers[header] end @@ -4504,7 +4563,7 @@ get "/vi/:id/:name" do |env| url = "/vi/#{id}/#{name}" headers = HTTP::Headers.new - {"Range", "Accept", "Accept-Encoding"}.each do |header| + {"Accept", "Accept-Encoding", "Cache-Control", "Connection", "If-None-Match", "Range"}.each do |header| if env.request.headers[header]? headers[header] = env.request.headers[header] end diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index beaa1330..9e273a96 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -473,6 +473,75 @@ struct Video return @player_json.not_nil! end + def storyboards + storyboards = self.player_response["storyboards"]? + .try &.as_h + .try &.["playerStoryboardSpecRenderer"]? + + if !storyboards + storyboards = self.player_response["storyboards"]? + .try &.as_h + .try &.["playerLiveStoryboardSpecRenderer"]? + + if storyboard = storyboards.try &.["spec"]? + .try &.as_s + return [{ + url: storyboard.split("#")[0].sub("M$M", "$N"), + width: 106, + height: 60, + count: -1, + interval: 5000, + storyboard_width: 3, + storyboard_height: 3, + storyboard_count: -1, + }] + end + end + + storyboards = storyboards.try &.["spec"]? + .try &.as_s.split("|") + + items = [] of NamedTuple( + url: String, + width: Int32, + height: Int32, + count: Int32, + interval: Int32, + storyboard_width: Int32, + storyboard_height: Int32, + storyboard_count: Int32) + + if !storyboards + return items + end + + url = storyboards.shift + + storyboards.each_with_index do |storyboard, i| + width, height, count, storyboard_width, storyboard_height, interval, _, sigh = storyboard.split("#") + + width = width.to_i + height = height.to_i + count = count.to_i + interval = interval.to_i + storyboard_width = storyboard_width.to_i + storyboard_height = storyboard_height.to_i + + items << { + url: "#{url}&sigh=#{sigh}".sub("$L", i), + width: width, + height: height, + count: count, + interval: interval, + storyboard_width: storyboard_width, + storyboard_height: storyboard_height, + storyboard_count: (count.to_f / (storyboard_width.to_f * storyboard_height.to_f)).ceil.to_i, + } + end + + items + end + def paid reason = self.player_response["playabilityStatus"]?.try &.["reason"]? @@ -1062,3 +1131,20 @@ def generate_thumbnails(json, id, config, kemal_config) end end end + +def generate_storyboards(json, storyboards, config, kemal_config) + json.array do + storyboards.each do |storyboard| + json.object do + json.field "url", storyboard[:url] + json.field "width", storyboard[:width] + json.field "height", storyboard[:height] + json.field "count", storyboard[:count] + json.field "interval", storyboard[:interval] + json.field "storyboardWidth", storyboard[:storyboard_width] + json.field "storyboardHeight", storyboard[:storyboard_height] + json.field "storyboardCount", storyboard[:storyboard_count] + end + end + end +end From 5de300fb359af6d7428e6307e937b4d25e8f36b2 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 11 Apr 2019 17:03:37 -0500 Subject: [PATCH 036/210] Fix default background color for player --- src/invidious/views/components/player.ecr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index f7848b4d..5609da98 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -1,4 +1,4 @@ -
-
+ -
-
+
+
<% if count == 60 %> &sort_by=<%= sort_by %><% end %>"> <%= translate(locale, "Next page") %> diff --git a/src/invidious/views/history.ecr b/src/invidious/views/history.ecr index 017f5eae..9be40a0d 100644 --- a/src/invidious/views/history.ecr +++ b/src/invidious/views/history.ecr @@ -72,15 +72,15 @@ function mark_unwatched(target) {
-
+ -
-
+
+
<% if watched.size >= limit %> <%= translate(locale, "Next page") %> diff --git a/src/invidious/views/login.ecr b/src/invidious/views/login.ecr index 82e9396c..b26aa373 100644 --- a/src/invidious/views/login.ecr +++ b/src/invidious/views/login.ecr @@ -3,8 +3,8 @@ <% end %>
-
-
+
+
@@ -112,5 +112,5 @@ <% end %>
-
+
diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index 98da42dc..6bd0dc9e 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -34,15 +34,15 @@
-
+ -
-
+
+
<% if videos.size == 100 %> <%= translate(locale, "Next page") %> diff --git a/src/invidious/views/playlists.ecr b/src/invidious/views/playlists.ecr index 3f661494..ee7918e7 100644 --- a/src/invidious/views/playlists.ecr +++ b/src/invidious/views/playlists.ecr @@ -65,7 +65,7 @@
-
+
-
+ -
-
+
+
<% if count >= 20 %> <%= translate(locale, "Next page") %> diff --git a/src/invidious/views/subscriptions.ecr b/src/invidious/views/subscriptions.ecr index 744f77a0..88f0f6c4 100644 --- a/src/invidious/views/subscriptions.ecr +++ b/src/invidious/views/subscriptions.ecr @@ -74,15 +74,15 @@ function mark_watched(target) {
-
+ -
-
+
+
-
+ -
+ <% if params[:related_videos] || plid %> -
+
<% if plid %>
From efe86c37b2b47384d49330ab282225072e6b08fa Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 17:10:32 -0500 Subject: [PATCH 050/210] Show subscribe text when not logged in --- locales/ar.json | 2 +- locales/de.json | 2 +- locales/en-US.json | 2 +- locales/es.json | 2 +- locales/eu.json | 2 +- locales/fr.json | 2 +- locales/it.json | 2 +- locales/nb_NO.json | 586 ++++++++--------- locales/nl.json | 2 +- locales/pl.json | 2 +- locales/ru.json | 590 +++++++++--------- .../views/components/subscribe_widget.ecr | 2 +- 12 files changed, 598 insertions(+), 598 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index f82683cf..e41ab16f 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -5,7 +5,7 @@ "Shared `x` ago": "تم رفع الفيديو منذ `x`", "Unsubscribe": "إلغاء الإشتراك", "Subscribe": "إشتراك", - "Login to subscribe to `x`": "سجل الدخول للإشتراك فى `x`", + "View channel on YouTube": "زيارة القناة على موقع يوتيوب", "newest": "الأجدد", "oldest": "الأقدم", diff --git a/locales/de.json b/locales/de.json index 2fcffd75..6b3609e0 100644 --- a/locales/de.json +++ b/locales/de.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Vor `x` geteilt", "Unsubscribe": "Abbestellen", "Subscribe": "Abonnieren", - "Login to subscribe to `x`": "Einloggen um `x` zu abonnieren", + "View channel on YouTube": "Kanal auf YouTube anzeigen", "newest": "neueste", "oldest": "älteste", diff --git a/locales/en-US.json b/locales/en-US.json index 174ffb4f..95012c36 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Shared `x` ago", "Unsubscribe": "Unsubscribe", "Subscribe": "Subscribe", - "Login to subscribe to `x`": "Login to subscribe to `x`", + "View channel on YouTube": "View channel on YouTube", "newest": "newest", "oldest": "oldest", diff --git a/locales/es.json b/locales/es.json index 67d4d5d3..7ad32336 100644 --- a/locales/es.json +++ b/locales/es.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Compartido hace `x`", "Unsubscribe": "Desuscribirse", "Subscribe": "Suscribirse", - "Login to subscribe to `x`": "Inicie sesión para suscribirse a `x`", + "View channel on YouTube": "Ver el canal en YouTube", "newest": "más nuevos", "oldest": "más viejos", diff --git a/locales/eu.json b/locales/eu.json index 4a7342a1..28dabd1a 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Duela `x` partekatua", "Unsubscribe": "Harpidetza kendu", "Subscribe": "Harpidetu", - "Login to subscribe to `x`": "Saioa hasi `x`(e)ra harpidetzeko", + "View channel on YouTube": "Ikusi kanala YouTuben", "newest": "berrienak", "oldest": "zaharrenak", diff --git a/locales/fr.json b/locales/fr.json index 12dc9f2c..6bbc6ff8 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Ajoutée il y a `x`", "Unsubscribe": "Se désabonner", "Subscribe": "S'abonner", - "Login to subscribe to `x`": "Vous devez vous connecter pour vous abonner à `x`", + "View channel on YouTube": "Voir la chaîne sur YouTube", "newest": "Date d'ajout (la plus récente)", "oldest": "Date d'ajout (la plus ancienne)", diff --git a/locales/it.json b/locales/it.json index 76bc15bd..c6706d6b 100644 --- a/locales/it.json +++ b/locales/it.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Condiviso `x` fa", "Unsubscribe": "Disiscriviti", "Subscribe": "Iscriviti", - "Login to subscribe to `x`": "Accedi per iscriverti a `x`", + "View channel on YouTube": "Vedi canale su YouTube", "newest": "Data di aggiunta (più recente)", "oldest": "Data di aggiunta (più vecchia)", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 1ef2f631..9078bffd 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1,295 +1,295 @@ { - "`x` subscribers": "`x` abonnenter", - "`x` videos": "`x` videoer", - "LIVE": "SANNTIDSVISNING", - "Shared `x` ago": "Delt for `x` siden", - "Unsubscribe": "Opphev abonnement", - "Subscribe": "Abonner", - "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", - "View channel on YouTube": "Vis kanal på YouTube", - "newest": "nyeste", - "oldest": "eldste", - "popular": "populært", - "last": "siste", - "Next page": "Neste side", - "Previous page": "Forrige side", - "Clear watch history?": "Tøm visningshistorikk?", - "Yes": "Ja", - "No": "Nei", - "Import and Export Data": "Importer- og eksporter data", - "Import": "Importer", - "Import Invidious data": "Importer Invidious-data", - "Import YouTube subscriptions": "Importer YouTube-abonnenter", - "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", - "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", - "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", - "Export": "Eksporter", - "Export subscriptions as OPML": "Eksporter abonnenter som OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", - "Export data as JSON": "Eksporter data som JSON", - "Delete account?": "Slett konto?", - "History": "Historikk", - "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", - "JavaScript license information": "JavaScript-lisensinformasjon", - "source": "kilde", - "Login": "Logg inn", - "Login/Register": "Logg inn/registrer", - "Login to Google": "Logg inn med Google", - "User ID:": "Bruker-ID:", - "Password:": "Passord:", - "Time (h:mm:ss):": "Tid (h:mm:ss):", - "Text CAPTCHA": "Tekst-CAPTCHA", - "Image CAPTCHA": "Bilde-CAPTCHA", - "Sign In": "Innlogging", - "Register": "Registrer", - "Email:": "E-post:", - "Google verification code:": "Google-bekreftelseskode:", - "Preferences": "Innstillinger", - "Player preferences": "Avspillerinnstillinger", - "Always loop: ": "Alltid gjenta: ", - "Autoplay: ": "Autoavspilling: ", - "Autoplay next video: ": "Autospill neste video: ", - "Listen by default: ": "Lytt som forvalg: ", - "Proxy videos? ": "Mellomtjen videoer? ", - "Default speed: ": "Forvalgt hastighet: ", - "Preferred video quality: ": "Foretrukket videokvalitet: ", - "Player volume: ": "Avspillerlydstyrke: ", - "Default comments: ": "Forvalgte kommentarer: ", - "Default captions: ": "Forvalgte undertitler: ", - "Fallback captions: ": "Tilbakefallsundertitler: ", - "Show related videos? ": "Vis relaterte videoer? ", - "Visual preferences": "Visuelle innstillinger", - "Dark mode: ": "Mørk drakt: ", - "Thin mode: ": "Tynt modus: ", - "Subscription preferences": "Abonnementsinnstillinger", - "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", - "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", - "Sort videos by: ": "Sorter videoer etter: ", - "published": "publisert", - "published - reverse": "publisert - motsatt", - "alphabetically": "alfabetisk", - "alphabetically - reverse": "alfabetisk - motsatt", - "channel name": "kanalnavn", - "channel name - reverse": "kanalnavn - motsatt", - "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", - "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", - "Only show unwatched: ": "Kun vis usette: ", - "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", - "Data preferences": "Datainnstillinger", - "Clear watch history": "Tøm visningshistorikk", - "Import/Export data": "Importer/eksporter data", - "Manage subscriptions": "Behandle abonnementer", - "Watch history": "Visningshistorikk", - "Delete account": "Slett konto", - "Administrator preferences": "Administratorinnstillinger", - "Default homepage: ": "Forvalgt hjemmeside: ", - "Feed menu: ": "Flyt-meny: ", - "Top enabled? ": "Topp påskrudd? ", - "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", - "Login enabled? ": "Innlogging påskrudd? ", - "Registration enabled? ": "Registrering påskrudd? ", - "Report statistics? ": "Innrapporter statistikk? ", - "Save preferences": "Lagre innstillinger", - "Subscription manager": "Abonnementsbehandler", - "`x` subscriptions": "`x` abonnementer", - "Import/Export": "Importer/eksporter", - "unsubscribe": "opphev abonnement", - "Subscriptions": "Abonnement", - "`x` unseen notifications": "`x` usette merknader", - "search": "søk", - "Sign out": "Logg ut", - "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", - "Source available here.": "Kildekode tilgjengelig her.", - "View JavaScript license information.": "Vis JavaScript-lisensinfo.", - "View privacy policy.": "Vis personvernspraksis.", - "Trending": "Trendsettende", - "Unlisted": "Ulistet", - "Watch video on Youtube": "Vis video på YouTube", - "Genre: ": "Sjanger: ", - "License: ": "Lisens: ", - "Family friendly? ": "Familievennlig? ", - "Wilson score: ": "Wilson-poengsum: ", - "Engagement: ": "Engasjement: ", - "Whitelisted regions: ": "Hvitlistede regioner: ", - "Blacklisted regions: ": "Svartelistede regioner: ", - "Shared `x`": "Delt `x`", - "Premieres in `x`": "Premiere om `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", - "View YouTube comments": "Vis YouTube-kommentarer", - "View more comments on Reddit": "Vis flere kommenterer på Reddit", - "View `x` comments": "Vis `x` kommentarer", - "View Reddit comments": "Vis Reddit-kommentarer", - "Hide replies": "Skjul svar", - "Show replies": "Vis svar", - "Incorrect password": "Feil passord", - "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", - "Invalid TFA code": "Ugyldig tofaktorkode", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", - "Invalid answer": "Ugyldig svar", - "Invalid CAPTCHA": "Ugyldig CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", - "User ID is a required field": "Bruker-ID er et påkrevd felt", - "Password is a required field": "Passord er et påkrevd felt", - "Invalid username or password": "Ugyldig brukernavn eller passord", - "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", - "Password cannot be empty": "Passordet kan ikke være tomt", - "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", - "Please sign in": "Logg inn", - "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", - "channel:`x`": "kanal `x`", - "Deleted or invalid channel": "Slettet eller ugyldig kanal", - "This channel does not exist.": "Denne kanalen finnes ikke.", - "Could not get channel info.": "Kunne ikke innhente kanalinfo.", - "Could not fetch comments": "Kunne ikke hente kommentarer", - "View `x` replies": "Vis `x` svar", - "`x` ago": "`x` siden", - "Load more": "Last inn flere", - "`x` points": "`x` poeng", - "Could not create mix.": "Kunne ikke opprette miks.", - "Playlist is empty": "Spillelisten er tom", - "Invalid playlist.": "Ugyldig spilleliste.", - "Playlist does not exist.": "Spillelisten finnes ikke.", - "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", - "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", - "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", - "Invalid challenge": "Ugyldig utfordring", - "Invalid token": "Ugyldig symbol", - "Invalid user": "Ugyldig bruker", - "Token is expired, please try again": "Symbol utløpt, prøv igjen", - "English": "Engelsk", - "English (auto-generated)": "Engelsk (auto-generert)", - "Afrikaans": "", - "Albanian": "Albansk", - "Amharic": "", - "Arabic": "Arabisk", - "Armenian": "Armensk", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "Hviterussisk", - "Bosnian": "Bosnisk", - "Bulgarian": "Bulgarsk", - "Burmese": "Burmesisk", - "Catalan": "Katalansk", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "Tsjekkisk", - "Danish": "Dansk", - "Dutch": "", - "Esperanto": "Esperanto", - "Estonian": "", - "Filipino": "", - "Finnish": "Finsk", - "French": "Fransk", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "Ungarsk", - "Icelandic": "Islandsk", - "Igbo": "", - "Indonesian": "Indonesisk", - "Irish": "Irsk", - "Italian": "Italiensk", - "Japanese": "Japansk", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "Norsk bokmål", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "Russisk", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "Serbisk", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "Slovakisk", - "Slovenian": "Slovensk", - "Somali": "Somali", - "Southern Sotho": "", - "Spanish": "Spansk", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "Svensk", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "Tyrkisk", - "Ukrainian": "Ukrainsk", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "Vietnamesisk", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` år", - "`x` months": "`x` måneder", - "`x` weeks": "`x` uker", - "`x` days": "`x` dager", - "`x` hours": "`x` timer", - "`x` minutes": "`x` minutter", - "`x` seconds": "`x` sekunder", - "Fallback comments: ": "Tilbakefallskommentarer: ", - "Popular": "Pupulært", - "Top": "Topp", - "About": "Om", - "Rating: ": "Vurdering: ", - "Language: ": "Språk: ", - "Default": "Forvalg", - "Music": "Musikk", - "Gaming": "Spill", - "News": "Nyheter", - "Movies": "Filmer", - "Download": "Last ned", - "Download as: ": "Last ned som: ", - "%A %B %-d, %Y": "", - "(edited)": "(redigert)", - "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", - "`x` marked it with a ❤": "`x` levnet et ❤", - "Audio mode": "Lydmodus", - "Video mode": "Video-modus", - "Videos": "Videoer", - "Playlists": "Spillelister", - "Current version: ": "Nåværende versjon: " + "`x` subscribers": "`x` abonnenter", + "`x` videos": "`x` videoer", + "LIVE": "SANNTIDSVISNING", + "Shared `x` ago": "Delt for `x` siden", + "Unsubscribe": "Opphev abonnement", + "Subscribe": "Abonner", + + "View channel on YouTube": "Vis kanal på YouTube", + "newest": "nyeste", + "oldest": "eldste", + "popular": "populært", + "last": "siste", + "Next page": "Neste side", + "Previous page": "Forrige side", + "Clear watch history?": "Tøm visningshistorikk?", + "Yes": "Ja", + "No": "Nei", + "Import and Export Data": "Importer- og eksporter data", + "Import": "Importer", + "Import Invidious data": "Importer Invidious-data", + "Import YouTube subscriptions": "Importer YouTube-abonnenter", + "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", + "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", + "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", + "Export": "Eksporter", + "Export subscriptions as OPML": "Eksporter abonnenter som OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", + "Export data as JSON": "Eksporter data som JSON", + "Delete account?": "Slett konto?", + "History": "Historikk", + "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", + "JavaScript license information": "JavaScript-lisensinformasjon", + "source": "kilde", + "Login": "Logg inn", + "Login/Register": "Logg inn/registrer", + "Login to Google": "Logg inn med Google", + "User ID:": "Bruker-ID:", + "Password:": "Passord:", + "Time (h:mm:ss):": "Tid (h:mm:ss):", + "Text CAPTCHA": "Tekst-CAPTCHA", + "Image CAPTCHA": "Bilde-CAPTCHA", + "Sign In": "Innlogging", + "Register": "Registrer", + "Email:": "E-post:", + "Google verification code:": "Google-bekreftelseskode:", + "Preferences": "Innstillinger", + "Player preferences": "Avspillerinnstillinger", + "Always loop: ": "Alltid gjenta: ", + "Autoplay: ": "Autoavspilling: ", + "Autoplay next video: ": "Autospill neste video: ", + "Listen by default: ": "Lytt som forvalg: ", + "Proxy videos? ": "Mellomtjen videoer? ", + "Default speed: ": "Forvalgt hastighet: ", + "Preferred video quality: ": "Foretrukket videokvalitet: ", + "Player volume: ": "Avspillerlydstyrke: ", + "Default comments: ": "Forvalgte kommentarer: ", + "Default captions: ": "Forvalgte undertitler: ", + "Fallback captions: ": "Tilbakefallsundertitler: ", + "Show related videos? ": "Vis relaterte videoer? ", + "Visual preferences": "Visuelle innstillinger", + "Dark mode: ": "Mørk drakt: ", + "Thin mode: ": "Tynt modus: ", + "Subscription preferences": "Abonnementsinnstillinger", + "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", + "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", + "Sort videos by: ": "Sorter videoer etter: ", + "published": "publisert", + "published - reverse": "publisert - motsatt", + "alphabetically": "alfabetisk", + "alphabetically - reverse": "alfabetisk - motsatt", + "channel name": "kanalnavn", + "channel name - reverse": "kanalnavn - motsatt", + "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", + "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", + "Only show unwatched: ": "Kun vis usette: ", + "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", + "Data preferences": "Datainnstillinger", + "Clear watch history": "Tøm visningshistorikk", + "Import/Export data": "Importer/eksporter data", + "Manage subscriptions": "Behandle abonnementer", + "Watch history": "Visningshistorikk", + "Delete account": "Slett konto", + "Administrator preferences": "Administratorinnstillinger", + "Default homepage: ": "Forvalgt hjemmeside: ", + "Feed menu: ": "Flyt-meny: ", + "Top enabled? ": "Topp påskrudd? ", + "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", + "Login enabled? ": "Innlogging påskrudd? ", + "Registration enabled? ": "Registrering påskrudd? ", + "Report statistics? ": "Innrapporter statistikk? ", + "Save preferences": "Lagre innstillinger", + "Subscription manager": "Abonnementsbehandler", + "`x` subscriptions": "`x` abonnementer", + "Import/Export": "Importer/eksporter", + "unsubscribe": "opphev abonnement", + "Subscriptions": "Abonnement", + "`x` unseen notifications": "`x` usette merknader", + "search": "søk", + "Sign out": "Logg ut", + "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", + "Source available here.": "Kildekode tilgjengelig her.", + "View JavaScript license information.": "Vis JavaScript-lisensinfo.", + "View privacy policy.": "Vis personvernspraksis.", + "Trending": "Trendsettende", + "Unlisted": "Ulistet", + "Watch video on Youtube": "Vis video på YouTube", + "Genre: ": "Sjanger: ", + "License: ": "Lisens: ", + "Family friendly? ": "Familievennlig? ", + "Wilson score: ": "Wilson-poengsum: ", + "Engagement: ": "Engasjement: ", + "Whitelisted regions: ": "Hvitlistede regioner: ", + "Blacklisted regions: ": "Svartelistede regioner: ", + "Shared `x`": "Delt `x`", + "Premieres in `x`": "Premiere om `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", + "View YouTube comments": "Vis YouTube-kommentarer", + "View more comments on Reddit": "Vis flere kommenterer på Reddit", + "View `x` comments": "Vis `x` kommentarer", + "View Reddit comments": "Vis Reddit-kommentarer", + "Hide replies": "Skjul svar", + "Show replies": "Vis svar", + "Incorrect password": "Feil passord", + "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", + "Invalid TFA code": "Ugyldig tofaktorkode", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", + "Invalid answer": "Ugyldig svar", + "Invalid CAPTCHA": "Ugyldig CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", + "User ID is a required field": "Bruker-ID er et påkrevd felt", + "Password is a required field": "Passord er et påkrevd felt", + "Invalid username or password": "Ugyldig brukernavn eller passord", + "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", + "Password cannot be empty": "Passordet kan ikke være tomt", + "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", + "Please sign in": "Logg inn", + "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", + "channel:`x`": "kanal `x`", + "Deleted or invalid channel": "Slettet eller ugyldig kanal", + "This channel does not exist.": "Denne kanalen finnes ikke.", + "Could not get channel info.": "Kunne ikke innhente kanalinfo.", + "Could not fetch comments": "Kunne ikke hente kommentarer", + "View `x` replies": "Vis `x` svar", + "`x` ago": "`x` siden", + "Load more": "Last inn flere", + "`x` points": "`x` poeng", + "Could not create mix.": "Kunne ikke opprette miks.", + "Playlist is empty": "Spillelisten er tom", + "Invalid playlist.": "Ugyldig spilleliste.", + "Playlist does not exist.": "Spillelisten finnes ikke.", + "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", + "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", + "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", + "Invalid challenge": "Ugyldig utfordring", + "Invalid token": "Ugyldig symbol", + "Invalid user": "Ugyldig bruker", + "Token is expired, please try again": "Symbol utløpt, prøv igjen", + "English": "Engelsk", + "English (auto-generated)": "Engelsk (auto-generert)", + "Afrikaans": "", + "Albanian": "Albansk", + "Amharic": "", + "Arabic": "Arabisk", + "Armenian": "Armensk", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "Hviterussisk", + "Bosnian": "Bosnisk", + "Bulgarian": "Bulgarsk", + "Burmese": "Burmesisk", + "Catalan": "Katalansk", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "Tsjekkisk", + "Danish": "Dansk", + "Dutch": "", + "Esperanto": "Esperanto", + "Estonian": "", + "Filipino": "", + "Finnish": "Finsk", + "French": "Fransk", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "Ungarsk", + "Icelandic": "Islandsk", + "Igbo": "", + "Indonesian": "Indonesisk", + "Irish": "Irsk", + "Italian": "Italiensk", + "Japanese": "Japansk", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "Norsk bokmål", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "Russisk", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "Serbisk", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "Slovakisk", + "Slovenian": "Slovensk", + "Somali": "Somali", + "Southern Sotho": "", + "Spanish": "Spansk", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "Svensk", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "Tyrkisk", + "Ukrainian": "Ukrainsk", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "Vietnamesisk", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` år", + "`x` months": "`x` måneder", + "`x` weeks": "`x` uker", + "`x` days": "`x` dager", + "`x` hours": "`x` timer", + "`x` minutes": "`x` minutter", + "`x` seconds": "`x` sekunder", + "Fallback comments: ": "Tilbakefallskommentarer: ", + "Popular": "Pupulært", + "Top": "Topp", + "About": "Om", + "Rating: ": "Vurdering: ", + "Language: ": "Språk: ", + "Default": "Forvalg", + "Music": "Musikk", + "Gaming": "Spill", + "News": "Nyheter", + "Movies": "Filmer", + "Download": "Last ned", + "Download as: ": "Last ned som: ", + "%A %B %-d, %Y": "", + "(edited)": "(redigert)", + "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", + "`x` marked it with a ❤": "`x` levnet et ❤", + "Audio mode": "Lydmodus", + "Video mode": "Video-modus", + "Videos": "Videoer", + "Playlists": "Spillelister", + "Current version: ": "Nåværende versjon: " } diff --git a/locales/nl.json b/locales/nl.json index 68294dc6..8e0dd3de 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Gedeeld `x` geleden", "Unsubscribe": "Abonnement opzeggen", "Subscribe": "Abonneren", - "Login to subscribe to `x`": "Log in om te abonneren op `x`", + "View channel on YouTube": "Bekijk kanaal op Youtube", "newest": "nieuwste", "oldest": "oudste", diff --git a/locales/pl.json b/locales/pl.json index 37410974..4fa8d5d7 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -5,7 +5,7 @@ "Shared `x` ago": "Udostępniono `x` temu", "Unsubscribe": "Odsubskrybuj", "Subscribe": "Subskrybuj", - "Login to subscribe to `x`": "Zaloguj się, aby subskrybować `x`", + "View channel on YouTube": "Wyświetl kanał na YouTube", "newest": "najnowsze", "oldest": "najstarsze", diff --git a/locales/ru.json b/locales/ru.json index 5a664b0d..ce2355d7 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,297 +1,297 @@ { - "`x` subscribers": "`x` подписчиков", - "`x` videos": "`x` видео", - "LIVE": "ПРЯМОЙ ЭФИР", - "Shared `x` ago": "Опубликовано `x` назад", - "Unsubscribe": "Отписаться", - "Subscribe": "Подписаться", - "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", - "View channel on YouTube": "Канал на YouTube", - "newest": "новые", - "oldest": "старые", - "popular": "популярные", - "last": "недавно обновленные", - "Next page": "Следующая страница", - "Previous page": "Предыдущая страница", - "Clear watch history?": "Очистить историю просмотров?", - "Yes": "Да", - "No": "Нет", - "Import and Export Data": "Импорт и экспорт данных", - "Import": "Импорт", - "Import Invidious data": "Импортировать данные Invidious", - "Import YouTube subscriptions": "Импортировать YouTube подписки", - "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", - "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", - "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", - "Export": "Экспорт", - "Export subscriptions as OPML": "Экспортировать подписки в OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", - "Export data as JSON": "Экспортировать данные в JSON", - "Delete account?": "Удалить аккаунт?", - "History": "История", - "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", - "JavaScript license information": "Лицензии JavaScript", - "source": "источник", - "Login": "Войти", - "Login/Register": "Войти/Регистрация", - "Login to Google": "Войти через Google", - "User ID:": "ID пользователя:", - "Password:": "Пароль:", - "Time (h:mm:ss):": "Время (ч:мм:сс):", - "Text CAPTCHA": "Текст капчи", - "Image CAPTCHA": "Изображение капчи", - "Sign In": "Войти", - "Register": "Регистрация", - "Email:": "Эл. почта:", - "Google verification code:": "Код подтверждения Google:", - "Preferences": "Настройки", - "Player preferences": "Настройки проигрывателя", - "Always loop: ": "Всегда повторять: ", - "Autoplay: ": "Автовоспроизведение: ", - "Autoplay next video: ": "Автовоспроизведение следующего видео: ", - "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", - "Proxy videos? ": "Проксировать видео? ", - "Default speed: ": "Скорость по-умолчанию: ", - "Preferred video quality: ": "Предпочтительное качество видео: ", - "Player volume: ": "Громкость воспроизведения: ", - "Default comments: ": "Источник комментариев: ", - "youtube": "YouTube", - "reddit": "Reddit", - "Default captions: ": "Субтитры по-умолчанию: ", - "Fallback captions: ": "Резервные субтитры: ", - "Show related videos? ": "Показывать похожие видео? ", - "Visual preferences": "Визуальные настройки", - "Dark mode: ": "Темная тема: ", - "Thin mode: ": "Облегченный режим: ", - "Subscription preferences": "Настройки подписок", - "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", - "Number of videos shown in feed: ": "Число видео в ленте: ", - "Sort videos by: ": "Сортировать видео по: ", - "published": "дате публикации", - "published - reverse": "дате - обратный порядок", - "alphabetically": "алфавиту", - "alphabetically - reverse": "алфавиту - обратный порядок", - "channel name": "имени канала", - "channel name - reverse": "имени канала - обратный порядок", - "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", - "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", - "Only show unwatched: ": "Отображать только непросмотренные видео: ", - "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", - "Data preferences": "Настройки данных", - "Clear watch history": "Очистить историю просмотра", - "Import/Export data": "Импорт/Экспорт данных", - "Manage subscriptions": "Управление подписками", - "Watch history": "История просмотров", - "Delete account": "Удалить аккаунт", - "Administrator preferences": "Настройки администратора", - "Default homepage: ": "Главная страница по умолчанию: ", - "Feed menu: ": "Меню ленты: ", - "Top enabled? ": "Включить ТОП? ", - "CAPTCHA enabled? ": "Включить капчу? ", - "Login enabled? ": "Включить логин? ", - "Registration enabled? ": "Включить регистрацию? ", - "Report statistics? ": "Отображать статистику? ", - "Save preferences": "Сохранить настройки", - "Subscription manager": "Менеджер подписок", - "`x` subscriptions": "`x` подписок", - "Import/Export": "Импорт/Экспорт", - "unsubscribe": "отписаться", - "Subscriptions": "Подписки", - "`x` unseen notifications": "`x` новых оповещений", - "search": "поиск", - "Sign out": "Выйти", - "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", - "Source available here.": "Исходный код доступен здесь.", - "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", - "View privacy policy.": "См. политику конфиденциальности.", - "Trending": "В тренде", - "Unlisted": "Доступно по ссылке", - "Watch video on Youtube": "Смотреть на YouTube", - "Genre: ": "Жанр: ", - "License: ": "Лицензия: ", - "Family friendly? ": "Семейный просмотр: ", - "Wilson score: ": "Рейтинг Вильсона: ", - "Engagement: ": "Вовлеченность: ", - "Whitelisted regions: ": "Доступно для: ", - "Blacklisted regions: ": "Недоступно для: ", - "Shared `x`": "Опубликовано `x`", - "Premieres in `x`": "Премьера через `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", - "View YouTube comments": "Смотреть комментарии с YouTube", - "View more comments on Reddit": "Больше комментариев на Reddit", - "View `x` comments": "Показать `x` комментариев", - "View Reddit comments": "Смотреть комментарии с Reddit", - "Hide replies": "Скрыть ответы", - "Show replies": "Показать ответы", - "Incorrect password": "Неправильный пароль", - "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", - "Invalid TFA code": "Неправильный TFA код", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", - "Invalid answer": "Неверный ответ", - "Invalid CAPTCHA": "Неверная капча", - "CAPTCHA is a required field": "Необходимо ввести капчу", - "User ID is a required field": "Необходимо ввести идентификатор пользователя", - "Password is a required field": "Необходимо ввести пароль", - "Invalid username or password": "Недопустимый пароль или имя пользователя", - "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", - "Password cannot be empty": "Пароль не может быть пустым", - "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", - "Please sign in": "Пожалуйста, войдите", - "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", - "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удален или не найден", - "This channel does not exist.": "Такой канал не существует.", - "Could not get channel info.": "Невозможно получить информацию о канале.", - "Could not fetch comments": "Невозможно получить комментарии", - "View `x` replies": "Показать `x` ответов", - "`x` ago": "`x` назад", - "Load more": "Загрузить больше", - "`x` points": "`x` очков", - "Could not create mix.": "Невозможно создать \"микс\".", - "Playlist is empty": "Плейлист пуст", - "Invalid playlist.": "Некорректный плейлист.", - "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", - "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", - "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", - "Invalid challenge": "Неправильный ответ в \"challenge\"", - "Invalid token": "Неправильный токен", - "Invalid user": "Недопустимое имя пользователя", - "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", - "English": "Английский", - "English (auto-generated)": "Английский (созданы автоматически)", - "Afrikaans": "Африкаанс", - "Albanian": "Албанский", - "Amharic": "Амхарский", - "Arabic": "Арабский", - "Armenian": "Армянский", - "Azerbaijani": "Азербайджанский", - "Bangla": "Бенгальский", - "Basque": "Баскский", - "Belarusian": "Белорусский", - "Bosnian": "Боснийский", - "Bulgarian": "Болгарский", - "Burmese": "Бирманский", - "Catalan": "Каталонский", - "Cebuano": "Себуанский", - "Chinese (Simplified)": "Китайский (упрощенный)", - "Chinese (Traditional)": "Китайский (традиционный)", - "Corsican": "Корсиканский", - "Croatian": "Хорватский", - "Czech": "Чешский", - "Danish": "Датский", - "Dutch": "Нидерландский", - "Esperanto": "Эсперанто", - "Estonian": "Эстонский", - "Filipino": "Филиппинский", - "Finnish": "Финский", - "French": "Французский", - "Galician": "Галисийский", - "Georgian": "Грузинский", - "German": "Немецкий", - "Greek": "Греческий", - "Gujarati": "Гуджаратский", - "Haitian Creole": "Гаит. креольский", - "Hausa": "Хауса", - "Hawaiian": "Гавайский", - "Hebrew": "Иврит", - "Hindi": "Хинди", - "Hmong": "Хмонг (мяо)", - "Hungarian": "Венгерский", - "Icelandic": "Исландский", - "Igbo": "Игбо", - "Indonesian": "Индонезийский", - "Irish": "Ирландский", - "Italian": "Итальянский", - "Japanese": "Японский", - "Javanese": "Яванский", - "Kannada": "Каннада", - "Kazakh": "Казахский", - "Khmer": "Кхмерский", - "Korean": "Корейский", - "Kurdish": "Курдский", - "Kyrgyz": "Киргизский", - "Lao": "Лаосский", - "Latin": "Латинский", - "Latvian": "Латышский", - "Lithuanian": "Литовский", - "Luxembourgish": "Люксембургский", - "Macedonian": "Македонский", - "Malagasy": "Малагасийский", - "Malay": "Малайский", - "Malayalam": "Малаялам", - "Maltese": "Мальтийский", - "Maori": "Маори", - "Marathi": "Маратхи", - "Mongolian": "Монгольская", - "Nepali": "Непальский", - "Norwegian": "Норвежский", - "Nyanja": "Ньянджа", - "Pashto": "Пушту", - "Persian": "Персидский", - "Polish": "Польский", - "Portuguese": "Португальский", - "Punjabi": "Панджаби", - "Romanian": "Румынский", - "Russian": "Русский", - "Samoan": "Самоанский", - "Scottish Gaelic": "Шотландский (гэльский)", - "Serbian": "Сербский", - "Shona": "Шона", - "Sindhi": "Синдхи", - "Sinhala": "Сингальский", - "Slovak": "Словацкий", - "Slovenian": "Словенский", - "Somali": "Сомалийский", - "Southern Sotho": "Сесото (южный сото)", - "Spanish": "Испанский", - "Spanish (Latin America)": "Испанский (Латинская Америка)", - "Sundanese": "Сунданский", - "Swahili": "Суахили", - "Swedish": "Шведский", - "Tajik": "Таджикский", - "Tamil": "Тамильский", - "Telugu": "Телугу", - "Thai": "Тайский", - "Turkish": "Турецкий", - "Ukrainian": "Украинский", - "Urdu": "Урду", - "Uzbek": "Узбекский", - "Vietnamese": "Вьетнамский", - "Welsh": "Валлийский", - "Western Frisian": "Западнофризский", - "Xhosa": "Коса", - "Yiddish": "Идиш", - "Yoruba": "Йоруба", - "Zulu": "Зулусский", - "`x` years": "`x` лет", - "`x` months": "`x` месяцев", - "`x` weeks": "`x` недель", - "`x` days": "`x` дней", - "`x` hours": "`x` часов", - "`x` minutes": "`x` минут", - "`x` seconds": "`x` секунд", - "Fallback comments: ": "Резервные комментарии: ", - "Popular": "Популярное", - "Top": "Топ", - "About": "О сайте", - "Rating: ": "Рейтинг: ", - "Language: ": "Язык: ", - "Default": "По-умолчанию", - "Music": "Музыка", - "Gaming": "Игры", - "News": "Новости", - "Movies": "Фильмы", - "Download": "Скачать", - "Download as: ": "Скачать как: ", - "%A %B %-d, %Y": "%-d %B %Y, %A", - "(edited)": "(изменено)", - "Youtube permalink of the comment": "Прямая ссылка на YouTube", - "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", - "Audio mode": "Аудио режим", - "Video mode": "Видео режим", - "Videos": "Видео", - "Playlists": "Плейлисты", - "Current version: ": "Текущая версия: " + "`x` subscribers": "`x` подписчиков", + "`x` videos": "`x` видео", + "LIVE": "ПРЯМОЙ ЭФИР", + "Shared `x` ago": "Опубликовано `x` назад", + "Unsubscribe": "Отписаться", + "Subscribe": "Подписаться", + + "View channel on YouTube": "Канал на YouTube", + "newest": "новые", + "oldest": "старые", + "popular": "популярные", + "last": "недавно обновленные", + "Next page": "Следующая страница", + "Previous page": "Предыдущая страница", + "Clear watch history?": "Очистить историю просмотров?", + "Yes": "Да", + "No": "Нет", + "Import and Export Data": "Импорт и экспорт данных", + "Import": "Импорт", + "Import Invidious data": "Импортировать данные Invidious", + "Import YouTube subscriptions": "Импортировать YouTube подписки", + "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", + "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", + "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", + "Export": "Экспорт", + "Export subscriptions as OPML": "Экспортировать подписки в OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", + "Export data as JSON": "Экспортировать данные в JSON", + "Delete account?": "Удалить аккаунт?", + "History": "История", + "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", + "JavaScript license information": "Лицензии JavaScript", + "source": "источник", + "Login": "Войти", + "Login/Register": "Войти/Регистрация", + "Login to Google": "Войти через Google", + "User ID:": "ID пользователя:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Время (ч:мм:сс):", + "Text CAPTCHA": "Текст капчи", + "Image CAPTCHA": "Изображение капчи", + "Sign In": "Войти", + "Register": "Регистрация", + "Email:": "Эл. почта:", + "Google verification code:": "Код подтверждения Google:", + "Preferences": "Настройки", + "Player preferences": "Настройки проигрывателя", + "Always loop: ": "Всегда повторять: ", + "Autoplay: ": "Автовоспроизведение: ", + "Autoplay next video: ": "Автовоспроизведение следующего видео: ", + "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", + "Proxy videos? ": "Проксировать видео? ", + "Default speed: ": "Скорость по-умолчанию: ", + "Preferred video quality: ": "Предпочтительное качество видео: ", + "Player volume: ": "Громкость воспроизведения: ", + "Default comments: ": "Источник комментариев: ", + "youtube": "YouTube", + "reddit": "Reddit", + "Default captions: ": "Субтитры по-умолчанию: ", + "Fallback captions: ": "Резервные субтитры: ", + "Show related videos? ": "Показывать похожие видео? ", + "Visual preferences": "Визуальные настройки", + "Dark mode: ": "Темная тема: ", + "Thin mode: ": "Облегченный режим: ", + "Subscription preferences": "Настройки подписок", + "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", + "Number of videos shown in feed: ": "Число видео в ленте: ", + "Sort videos by: ": "Сортировать видео по: ", + "published": "дате публикации", + "published - reverse": "дате - обратный порядок", + "alphabetically": "алфавиту", + "alphabetically - reverse": "алфавиту - обратный порядок", + "channel name": "имени канала", + "channel name - reverse": "имени канала - обратный порядок", + "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", + "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", + "Only show unwatched: ": "Отображать только непросмотренные видео: ", + "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", + "Data preferences": "Настройки данных", + "Clear watch history": "Очистить историю просмотра", + "Import/Export data": "Импорт/Экспорт данных", + "Manage subscriptions": "Управление подписками", + "Watch history": "История просмотров", + "Delete account": "Удалить аккаунт", + "Administrator preferences": "Настройки администратора", + "Default homepage: ": "Главная страница по умолчанию: ", + "Feed menu: ": "Меню ленты: ", + "Top enabled? ": "Включить ТОП? ", + "CAPTCHA enabled? ": "Включить капчу? ", + "Login enabled? ": "Включить логин? ", + "Registration enabled? ": "Включить регистрацию? ", + "Report statistics? ": "Отображать статистику? ", + "Save preferences": "Сохранить настройки", + "Subscription manager": "Менеджер подписок", + "`x` subscriptions": "`x` подписок", + "Import/Export": "Импорт/Экспорт", + "unsubscribe": "отписаться", + "Subscriptions": "Подписки", + "`x` unseen notifications": "`x` новых оповещений", + "search": "поиск", + "Sign out": "Выйти", + "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", + "Source available here.": "Исходный код доступен здесь.", + "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", + "View privacy policy.": "См. политику конфиденциальности.", + "Trending": "В тренде", + "Unlisted": "Доступно по ссылке", + "Watch video on Youtube": "Смотреть на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Лицензия: ", + "Family friendly? ": "Семейный просмотр: ", + "Wilson score: ": "Рейтинг Вильсона: ", + "Engagement: ": "Вовлеченность: ", + "Whitelisted regions: ": "Доступно для: ", + "Blacklisted regions: ": "Недоступно для: ", + "Shared `x`": "Опубликовано `x`", + "Premieres in `x`": "Премьера через `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", + "View YouTube comments": "Смотреть комментарии с YouTube", + "View more comments on Reddit": "Больше комментариев на Reddit", + "View `x` comments": "Показать `x` комментариев", + "View Reddit comments": "Смотреть комментарии с Reddit", + "Hide replies": "Скрыть ответы", + "Show replies": "Показать ответы", + "Incorrect password": "Неправильный пароль", + "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", + "Invalid TFA code": "Неправильный TFA код", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", + "Invalid answer": "Неверный ответ", + "Invalid CAPTCHA": "Неверная капча", + "CAPTCHA is a required field": "Необходимо ввести капчу", + "User ID is a required field": "Необходимо ввести идентификатор пользователя", + "Password is a required field": "Необходимо ввести пароль", + "Invalid username or password": "Недопустимый пароль или имя пользователя", + "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", + "Password cannot be empty": "Пароль не может быть пустым", + "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", + "Please sign in": "Пожалуйста, войдите", + "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал удален или не найден", + "This channel does not exist.": "Такой канал не существует.", + "Could not get channel info.": "Невозможно получить информацию о канале.", + "Could not fetch comments": "Невозможно получить комментарии", + "View `x` replies": "Показать `x` ответов", + "`x` ago": "`x` назад", + "Load more": "Загрузить больше", + "`x` points": "`x` очков", + "Could not create mix.": "Невозможно создать \"микс\".", + "Playlist is empty": "Плейлист пуст", + "Invalid playlist.": "Некорректный плейлист.", + "Playlist does not exist.": "Плейлист не существует.", + "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", + "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", + "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", + "Invalid challenge": "Неправильный ответ в \"challenge\"", + "Invalid token": "Неправильный токен", + "Invalid user": "Недопустимое имя пользователя", + "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", + "English": "Английский", + "English (auto-generated)": "Английский (созданы автоматически)", + "Afrikaans": "Африкаанс", + "Albanian": "Албанский", + "Amharic": "Амхарский", + "Arabic": "Арабский", + "Armenian": "Армянский", + "Azerbaijani": "Азербайджанский", + "Bangla": "Бенгальский", + "Basque": "Баскский", + "Belarusian": "Белорусский", + "Bosnian": "Боснийский", + "Bulgarian": "Болгарский", + "Burmese": "Бирманский", + "Catalan": "Каталонский", + "Cebuano": "Себуанский", + "Chinese (Simplified)": "Китайский (упрощенный)", + "Chinese (Traditional)": "Китайский (традиционный)", + "Corsican": "Корсиканский", + "Croatian": "Хорватский", + "Czech": "Чешский", + "Danish": "Датский", + "Dutch": "Нидерландский", + "Esperanto": "Эсперанто", + "Estonian": "Эстонский", + "Filipino": "Филиппинский", + "Finnish": "Финский", + "French": "Французский", + "Galician": "Галисийский", + "Georgian": "Грузинский", + "German": "Немецкий", + "Greek": "Греческий", + "Gujarati": "Гуджаратский", + "Haitian Creole": "Гаит. креольский", + "Hausa": "Хауса", + "Hawaiian": "Гавайский", + "Hebrew": "Иврит", + "Hindi": "Хинди", + "Hmong": "Хмонг (мяо)", + "Hungarian": "Венгерский", + "Icelandic": "Исландский", + "Igbo": "Игбо", + "Indonesian": "Индонезийский", + "Irish": "Ирландский", + "Italian": "Итальянский", + "Japanese": "Японский", + "Javanese": "Яванский", + "Kannada": "Каннада", + "Kazakh": "Казахский", + "Khmer": "Кхмерский", + "Korean": "Корейский", + "Kurdish": "Курдский", + "Kyrgyz": "Киргизский", + "Lao": "Лаосский", + "Latin": "Латинский", + "Latvian": "Латышский", + "Lithuanian": "Литовский", + "Luxembourgish": "Люксембургский", + "Macedonian": "Македонский", + "Malagasy": "Малагасийский", + "Malay": "Малайский", + "Malayalam": "Малаялам", + "Maltese": "Мальтийский", + "Maori": "Маори", + "Marathi": "Маратхи", + "Mongolian": "Монгольская", + "Nepali": "Непальский", + "Norwegian": "Норвежский", + "Nyanja": "Ньянджа", + "Pashto": "Пушту", + "Persian": "Персидский", + "Polish": "Польский", + "Portuguese": "Португальский", + "Punjabi": "Панджаби", + "Romanian": "Румынский", + "Russian": "Русский", + "Samoan": "Самоанский", + "Scottish Gaelic": "Шотландский (гэльский)", + "Serbian": "Сербский", + "Shona": "Шона", + "Sindhi": "Синдхи", + "Sinhala": "Сингальский", + "Slovak": "Словацкий", + "Slovenian": "Словенский", + "Somali": "Сомалийский", + "Southern Sotho": "Сесото (южный сото)", + "Spanish": "Испанский", + "Spanish (Latin America)": "Испанский (Латинская Америка)", + "Sundanese": "Сунданский", + "Swahili": "Суахили", + "Swedish": "Шведский", + "Tajik": "Таджикский", + "Tamil": "Тамильский", + "Telugu": "Телугу", + "Thai": "Тайский", + "Turkish": "Турецкий", + "Ukrainian": "Украинский", + "Urdu": "Урду", + "Uzbek": "Узбекский", + "Vietnamese": "Вьетнамский", + "Welsh": "Валлийский", + "Western Frisian": "Западнофризский", + "Xhosa": "Коса", + "Yiddish": "Идиш", + "Yoruba": "Йоруба", + "Zulu": "Зулусский", + "`x` years": "`x` лет", + "`x` months": "`x` месяцев", + "`x` weeks": "`x` недель", + "`x` days": "`x` дней", + "`x` hours": "`x` часов", + "`x` minutes": "`x` минут", + "`x` seconds": "`x` секунд", + "Fallback comments: ": "Резервные комментарии: ", + "Popular": "Популярное", + "Top": "Топ", + "About": "О сайте", + "Rating: ": "Рейтинг: ", + "Language: ": "Язык: ", + "Default": "По-умолчанию", + "Music": "Музыка", + "Gaming": "Игры", + "News": "Новости", + "Movies": "Фильмы", + "Download": "Скачать", + "Download as: ": "Скачать как: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "(edited)": "(изменено)", + "Youtube permalink of the comment": "Прямая ссылка на YouTube", + "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", + "Audio mode": "Аудио режим", + "Video mode": "Видео режим", + "Videos": "Видео", + "Playlists": "Плейлисты", + "Current version: ": "Текущая версия: " } diff --git a/src/invidious/views/components/subscribe_widget.ecr b/src/invidious/views/components/subscribe_widget.ecr index 5cdb6525..df16658d 100644 --- a/src/invidious/views/components/subscribe_widget.ecr +++ b/src/invidious/views/components/subscribe_widget.ecr @@ -18,7 +18,7 @@

"> - <%= translate(locale, "Login to subscribe to `x`", author) %> + <%= translate(locale, "Subscribe") %> | <%= sub_count_text %>

<% end %> From b51fd7fc1325ed0008c5aa7043585013ef62c86a Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 17:43:44 -0500 Subject: [PATCH 051/210] Add view count to video items --- locales/ar.json | 2 +- locales/de.json | 2 +- locales/en-US.json | 6 +++--- locales/es.json | 2 +- locales/eu.json | 2 +- locales/fr.json | 2 +- locales/it.json | 2 +- locales/nb_NO.json | 2 +- locales/nl.json | 2 +- locales/pl.json | 2 +- locales/ru.json | 2 +- src/invidious/helpers/utils.cr | 4 +++- src/invidious/views/components/item.ecr | 14 ++++++++++++-- 13 files changed, 28 insertions(+), 16 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index e41ab16f..90747982 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -5,7 +5,6 @@ "Shared `x` ago": "تم رفع الفيديو منذ `x`", "Unsubscribe": "إلغاء الإشتراك", "Subscribe": "إشتراك", - "View channel on YouTube": "زيارة القناة على موقع يوتيوب", "newest": "الأجدد", "oldest": "الأقدم", @@ -115,6 +114,7 @@ "Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ", "Blacklisted regions: ": "الدول الحظور فيها هذا الفيديو: ", "Shared `x`": "شارك منذ `x`", + "`x` views": "", "Premieres in `x`": "يعرض فى 'x'", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.", "View YouTube comments": "عرض تعليقات اليوتيوب", diff --git a/locales/de.json b/locales/de.json index 6b3609e0..275e44e3 100644 --- a/locales/de.json +++ b/locales/de.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Vor `x` geteilt", "Unsubscribe": "Abbestellen", "Subscribe": "Abonnieren", - "View channel on YouTube": "Kanal auf YouTube anzeigen", "newest": "neueste", "oldest": "älteste", @@ -115,6 +114,7 @@ "Whitelisted regions: ": "Erlaubte Regionen: ", "Blacklisted regions: ": "Unerlaubte Regionen: ", "Shared `x`": "Geteilt `x`", + "`x` views": "", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.", "View YouTube comments": "YouTube Kommentare anzeigen", diff --git a/locales/en-US.json b/locales/en-US.json index 95012c36..7f5e3d3b 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Shared `x` ago", "Unsubscribe": "Unsubscribe", "Subscribe": "Subscribe", - "View channel on YouTube": "View channel on YouTube", "newest": "newest", "oldest": "oldest", @@ -103,7 +102,7 @@ "View JavaScript license information.": "View JavaScript license information.", "View privacy policy.": "View privacy policy.", "Trending": "Trending", - "Unlisted": "", + "Unlisted": "Unlisted", "Watch video on Youtube": "Watch video on Youtube", "Genre: ": "Genre: ", "License: ": "License: ", @@ -113,7 +112,8 @@ "Whitelisted regions: ": "Whitelisted regions: ", "Blacklisted regions: ": "Blacklisted regions: ", "Shared `x`": "Shared `x`", - "Premieres in `x`": "", + "`x` views": "", + "Premieres in `x`": "Premieres in `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.", "View YouTube comments": "View YouTube comments", "View more comments on Reddit": "View more comments on Reddit", diff --git a/locales/es.json b/locales/es.json index 7ad32336..6c9cc7d5 100644 --- a/locales/es.json +++ b/locales/es.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Compartido hace `x`", "Unsubscribe": "Desuscribirse", "Subscribe": "Suscribirse", - "View channel on YouTube": "Ver el canal en YouTube", "newest": "más nuevos", "oldest": "más viejos", @@ -113,6 +112,7 @@ "Whitelisted regions: ": "Regiones permitidas: ", "Blacklisted regions: ": "Regiones bloqueadas: ", "Shared `x`": "Compartido `x`", + "`x` views": "", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.", "View YouTube comments": "Ver los comentarios de YouTube", diff --git a/locales/eu.json b/locales/eu.json index 28dabd1a..5f9d5c09 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Duela `x` partekatua", "Unsubscribe": "Harpidetza kendu", "Subscribe": "Harpidetu", - "View channel on YouTube": "Ikusi kanala YouTuben", "newest": "berrienak", "oldest": "zaharrenak", @@ -113,6 +112,7 @@ "Whitelisted regions: ": "", "Blacklisted regions: ": "", "Shared `x`": "", + "`x` views": "", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", "View YouTube comments": "", diff --git a/locales/fr.json b/locales/fr.json index 6bbc6ff8..162ebe38 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Ajoutée il y a `x`", "Unsubscribe": "Se désabonner", "Subscribe": "S'abonner", - "View channel on YouTube": "Voir la chaîne sur YouTube", "newest": "Date d'ajout (la plus récente)", "oldest": "Date d'ajout (la plus ancienne)", @@ -113,6 +112,7 @@ "Whitelisted regions: ": "Régions en liste blanche : ", "Blacklisted regions: ": "Régions sur liste noire : ", "Shared `x`": "Ajoutée le `x`", + "`x` views": "", "Premieres in `x`": "Première dans `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript soit désactivé. Cliquez ici pour voir les commentaires sans. Gardez à l'esprit que le chargement peut prendre plus de temps.", "View YouTube comments": "Voir les commentaires YouTube", diff --git a/locales/it.json b/locales/it.json index c6706d6b..d67df287 100644 --- a/locales/it.json +++ b/locales/it.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Condiviso `x` fa", "Unsubscribe": "Disiscriviti", "Subscribe": "Iscriviti", - "View channel on YouTube": "Vedi canale su YouTube", "newest": "Data di aggiunta (più recente)", "oldest": "Data di aggiunta (più vecchia)", @@ -113,6 +112,7 @@ "Whitelisted regions: ": "Regioni nella lista bianca: ", "Blacklisted regions: ": "Regioni nella lista nera: ", "Shared `x`": "Condiviso `x`", + "`x` views": "", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Ciao! Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti. Considera che potrebbe volerci più tempo.", "View YouTube comments": "Visualizza i commenti da YouTube", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 9078bffd..926f3f91 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Delt for `x` siden", "Unsubscribe": "Opphev abonnement", "Subscribe": "Abonner", - "View channel on YouTube": "Vis kanal på YouTube", "newest": "nyeste", "oldest": "eldste", @@ -113,6 +112,7 @@ "Whitelisted regions: ": "Hvitlistede regioner: ", "Blacklisted regions: ": "Svartelistede regioner: ", "Shared `x`": "Delt `x`", + "`x` views": "", "Premieres in `x`": "Premiere om `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", "View YouTube comments": "Vis YouTube-kommentarer", diff --git a/locales/nl.json b/locales/nl.json index 8e0dd3de..4129f378 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Gedeeld `x` geleden", "Unsubscribe": "Abonnement opzeggen", "Subscribe": "Abonneren", - "View channel on YouTube": "Bekijk kanaal op Youtube", "newest": "nieuwste", "oldest": "oudste", @@ -113,6 +112,7 @@ "Whitelisted regions: ": "Toegestane regio's: ", "Blacklisted regions: ": "Geblokkeerde regio's: ", "Shared `x`": "`x` gedeeld", + "`x` views": "", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hoi! Het lijkt erop dat je JavaScript uit hebt staan. Klik hier om de reacties te bekijken, hou er rekening mee dat het wat langer duurt om te laden.", "View YouTube comments": "Bekijk YouTube reacties", diff --git a/locales/pl.json b/locales/pl.json index 4fa8d5d7..ee883259 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Udostępniono `x` temu", "Unsubscribe": "Odsubskrybuj", "Subscribe": "Subskrybuj", - "View channel on YouTube": "Wyświetl kanał na YouTube", "newest": "najnowsze", "oldest": "najstarsze", @@ -113,6 +112,7 @@ "Whitelisted regions: ": "Dostępny na obszarach: ", "Blacklisted regions: ": "Niedostępny na obszarach: ", "Shared `x`": "Udostępniono `x`", + "`x` views": "", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", "View YouTube comments": "Wyświetl komentarze z YouTube", diff --git a/locales/ru.json b/locales/ru.json index ce2355d7..811c01de 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Опубликовано `x` назад", "Unsubscribe": "Отписаться", "Subscribe": "Подписаться", - "View channel on YouTube": "Канал на YouTube", "newest": "новые", "oldest": "старые", @@ -115,6 +114,7 @@ "Whitelisted regions: ": "Доступно для: ", "Blacklisted regions: ": "Недоступно для: ", "Shared `x`": "Опубликовано `x`", + "`x` views": "", "Premieres in `x`": "Премьера через `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", "View YouTube comments": "Смотреть комментарии с YouTube", diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index eb8fa80a..eb304d8d 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -189,7 +189,9 @@ def number_to_short_text(number) text = text.rchop(".0") - if number / 1000000 != 0 + if number / 1_000_000_000 != 0 + text += "B" + elsif number / 1_000_000 != 0 text += "M" elsif number / 1000 != 0 text += "K" diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index e2a5195c..2dc0bea4 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -71,7 +71,12 @@ <% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %>
<% elsif Time.now - item.published > 1.minute %> -
<%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %>
+
+
<%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %>
+
+ <%= item.responds_to?(:views) ? translate(locale, "`x` views", number_to_short_text(item.views)) : "" %> +
+
<% end %> <% else %> <% if env.get("preferences").as(Preferences).thin_mode %> @@ -108,7 +113,12 @@ <% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %>
<% elsif Time.now - item.published > 1.minute %> -
<%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %>
+
+
<%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %>
+
+ <%= item.responds_to?(:views) ? translate(locale, "`x` views", number_to_short_text(item.views)) : "" %> +
+
<% end %> <% end %>
From 80c1ebd7689041ac313bc2713ef357326b020e37 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 18:08:00 -0500 Subject: [PATCH 052/210] Support 'sort_by' in reddit /api/v1/comments --- src/invidious.cr | 12 +++++++----- src/invidious/comments.cr | 8 ++++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 2d566ac0..d1388288 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1617,7 +1617,7 @@ get "/subscription_ajax" do |env| } post_url = "/subscription_ajax?#{action}=1&c=#{channel_id}" - # Sync subscription with YouTube + # Sync subscriptions with YouTube client.post(post_url, headers, form: post_req) email = user.email else @@ -2778,12 +2778,12 @@ get "/api/v1/comments/:id" do |env| format = env.params.query["format"]? format ||= "json" - sort_by = env.params.query["sort_by"]?.try &.downcase - sort_by ||= "top" - continuation = env.params.query["continuation"]? + sort_by = env.params.query["sort_by"]?.try &.downcase if source == "youtube" + sort_by ||= "top" + begin comments = fetch_youtube_comments(id, PG_DB, continuation, proxies, format, locale, thin_mode, region, sort_by: sort_by) rescue ex @@ -2794,8 +2794,10 @@ get "/api/v1/comments/:id" do |env| next comments elsif source == "reddit" + sort_by ||= "confidence" + begin - comments, reddit_thread = fetch_reddit_comments(id) + comments, reddit_thread = fetch_reddit_comments(id, sort_by: sort_by) content_html = template_reddit_comments(comments, locale) content_html = fill_links(content_html, "https", "www.reddit.com") diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index cd96565b..645ca2a8 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -249,7 +249,7 @@ def fetch_youtube_comments(id, db, continuation, proxies, format, locale, thin_m return comments end -def fetch_reddit_comments(id) +def fetch_reddit_comments(id, sort_by = "confidence") client = make_client(REDDIT_URL) headers = HTTP::Headers{"User-Agent" => "web:invidious:v#{CURRENT_VERSION} (by /u/omarroth)"} @@ -259,12 +259,16 @@ def fetch_reddit_comments(id) if search_results.status_code == 200 search_results = RedditThing.from_json(search_results.body) + # For videos that have more than one thread, choose the one with the highest score thread = search_results.data.as(RedditListing).children.sort_by { |child| child.data.as(RedditLink).score }[-1] thread = thread.data.as(RedditLink) - result = client.get("/r/#{thread.subreddit}/comments/#{thread.id}.json?limit=100&sort=top", headers).body + result = client.get("/r/#{thread.subreddit}/comments/#{thread.id}.json?limit=100&sort=#{sort_by}", headers).body result = Array(RedditThing).from_json(result) elsif search_results.status_code == 302 + # Previously, if there was only one result then the API would redirect to that result. + # Now, it appears it will still return a listing so this section is likely unnecessary. + result = client.get(search_results.headers["Location"], headers).body result = Array(RedditThing).from_json(result) From ca515f2eae41cdfe79ac0186926a1b56371f16a4 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 18:24:25 -0500 Subject: [PATCH 053/210] Use headset icon for audio mode --- src/invidious/views/watch.ecr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 912fa8d1..8c1c66b6 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -40,7 +40,7 @@ <% else %> " href="/watch?<%= env.params.query %>&listen=1"> - + <% end %> From 9c8f85741ce406adba6d67a78e3c3d2f3501c1ca Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 18:37:43 -0500 Subject: [PATCH 054/210] Fix search when keyword matches operator --- src/invidious.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/invidious.cr b/src/invidious.cr index d1388288..6e4ee891 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -731,6 +731,8 @@ get "/search" do |env| sort = value when "subscriptions" subscriptions = value == "true" + else + operators.delete(operator) end end From f5dd135ed8b59e278787f1ec092559ad3500a3e0 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 19:04:10 -0500 Subject: [PATCH 055/210] Add 'view as playlist' option to trending page --- locales/ar.json | 1 + locales/de.json | 1 + locales/en-US.json | 3 ++- locales/es.json | 1 + locales/eu.json | 1 + locales/fr.json | 1 + locales/it.json | 1 + locales/nb_NO.json | 1 + locales/nl.json | 1 + locales/pl.json | 1 + locales/ru.json | 1 + src/invidious.cr | 4 ++-- src/invidious/trending.cr | 38 +++++++++++++++++++++++++++++++- src/invidious/views/trending.ecr | 7 +++--- 14 files changed, 55 insertions(+), 7 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 90747982..572ea904 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -278,6 +278,7 @@ "About": "حول", "Rating: ": "التقييم", "Language: ": "اللغة", + "View as playlist": "", "Default": "الكل", "Music": "الاغانى", "Gaming": "الألعاب", diff --git a/locales/de.json b/locales/de.json index 275e44e3..440f75e9 100644 --- a/locales/de.json +++ b/locales/de.json @@ -278,6 +278,7 @@ "About": "Über", "Rating: ": "Bewertung: ", "Language: ": "Sprache: ", + "View as playlist": "", "Default": "", "Music": "", "Gaming": "", diff --git a/locales/en-US.json b/locales/en-US.json index 7f5e3d3b..88c035b5 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -112,7 +112,7 @@ "Whitelisted regions: ": "Whitelisted regions: ", "Blacklisted regions: ": "Blacklisted regions: ", "Shared `x`": "Shared `x`", - "`x` views": "", + "`x` views": "`x` views", "Premieres in `x`": "Premieres in `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.", "View YouTube comments": "View YouTube comments", @@ -276,6 +276,7 @@ "About": "About", "Rating: ": "Rating: ", "Language: ": "Language: ", + "View as playlist": "View as playlist", "Default": "Default", "Music": "Music", "Gaming": "Gaming", diff --git a/locales/es.json b/locales/es.json index 6c9cc7d5..9cf54fe1 100644 --- a/locales/es.json +++ b/locales/es.json @@ -276,6 +276,7 @@ "About": "Acerca de", "Rating: ": "Valoración: ", "Language: ": "Idioma: ", + "View as playlist": "", "Default": "Por defecto", "Music": "Música", "Gaming": "Videojuegos", diff --git a/locales/eu.json b/locales/eu.json index 5f9d5c09..ddd00a4c 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -276,6 +276,7 @@ "About": "", "Rating: ": "", "Language: ": "", + "View as playlist": "", "Default": "", "Music": "", "Gaming": "", diff --git a/locales/fr.json b/locales/fr.json index 162ebe38..fec1053e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -276,6 +276,7 @@ "About": "A Propos", "Rating: ": "Évaluation : ", "Language: ": "Langue : ", + "View as playlist": "", "Default": "Défaut", "Music": "Musique", "Gaming": "Jeux Vidéo", diff --git a/locales/it.json b/locales/it.json index d67df287..9bd72ea5 100644 --- a/locales/it.json +++ b/locales/it.json @@ -276,6 +276,7 @@ "About": "A proposito", "Rating: ": "Punteggio: ", "Language: ": "Lingua: ", + "View as playlist": "", "Default": "Predefinito", "Music": "Musica", "Gaming": "Videogiochi", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 926f3f91..6d032e6f 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -276,6 +276,7 @@ "About": "Om", "Rating: ": "Vurdering: ", "Language: ": "Språk: ", + "View as playlist": "", "Default": "Forvalg", "Music": "Musikk", "Gaming": "Spill", diff --git a/locales/nl.json b/locales/nl.json index 4129f378..69c43b5b 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -276,6 +276,7 @@ "About": "", "Rating: ": "", "Language: ": "", + "View as playlist": "", "Default": "", "Music": "", "Gaming": "", diff --git a/locales/pl.json b/locales/pl.json index ee883259..901892eb 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -276,6 +276,7 @@ "About": "Informacje", "Rating: ": "Ocena: ", "Language: ": "Język: ", + "View as playlist": "", "Default": "Domyślnie", "Music": "Muzyka", "Gaming": "Gry", diff --git a/locales/ru.json b/locales/ru.json index 811c01de..a287cb80 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -278,6 +278,7 @@ "About": "О сайте", "Rating: ": "Рейтинг: ", "Language: ": "Язык: ", + "View as playlist": "", "Default": "По-умолчанию", "Music": "Музыка", "Gaming": "Игры", diff --git a/src/invidious.cr b/src/invidious.cr index 6e4ee891..f0d7fbf1 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1955,7 +1955,7 @@ get "/feed/trending" do |env| region ||= "US" begin - trending = fetch_trending(trending_type, proxies, region, locale) + trending, plid = fetch_trending(trending_type, proxies, region, locale) rescue ex error_message = "#{ex.message}" next templated "error" @@ -3205,7 +3205,7 @@ get "/api/v1/trending" do |env| trending_type = env.params.query["type"]? begin - trending = fetch_trending(trending_type, proxies, region, locale) + trending, plid = fetch_trending(trending_type, proxies, region, locale) rescue ex error_message = {"error" => ex.message}.to_json env.response.status_code = 500 diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr index 15630721..8e55f207 100644 --- a/src/invidious/trending.cr +++ b/src/invidious/trending.cr @@ -7,6 +7,8 @@ def fetch_trending(trending_type, proxies, region, locale) region = region.upcase trending = "" + plid = nil + if trending_type && trending_type != "Default" trending_type = trending_type.downcase.capitalize @@ -23,9 +25,11 @@ def fetch_trending(trending_type, proxies, region, locale) url = tabs.select { |tab| tab["channelListSubMenuAvatarRenderer"]["title"]["simpleText"] == trending_type }[0]? if url + url["channelListSubMenuAvatarRenderer"]["navigationEndpoint"]["commandMetadata"]["webCommandMetadata"]["url"] url = url["channelListSubMenuAvatarRenderer"]["navigationEndpoint"]["commandMetadata"]["webCommandMetadata"]["url"].as_s url += "&disable_polymer=1&gl=#{region}&hl=en" trending = client.get(url).body + plid = extract_plid(url) else trending = client.get("/feed/trending?gl=#{region}&hl=en&disable_polymer=1").body end @@ -37,5 +41,37 @@ def fetch_trending(trending_type, proxies, region, locale) nodeset = trending.xpath_nodes(%q(//ul/li[@class="expanded-shelf-content-item-wrapper"])) trending = extract_videos(nodeset) - return trending + return {trending, plid} +end + +def extract_plid(url) + wrapper = HTTP::Params.parse(URI.parse(url).query.not_nil!)["bp"] + + wrapper = URI.unescape(wrapper) + wrapper = Base64.decode(wrapper) + + # 0xe2 0x02 0x2e + wrapper += 3 + + # 0x0a + wrapper += 1 + + # Looks like "/m/[a-z0-9]{5}", not sure what it does here + + item_size = wrapper[0] + wrapper += 1 + item = wrapper[0, item_size] + wrapper += item.size + + # 0x12 + wrapper += 1 + + plid_size = wrapper[0] + wrapper += 1 + plid = wrapper[0, plid_size] + wrapper += plid.size + + plid = String.new(plid) + + return plid end diff --git a/src/invidious/views/trending.ecr b/src/invidious/views/trending.ecr index efd9999a..d6c25266 100644 --- a/src/invidious/views/trending.ecr +++ b/src/invidious/views/trending.ecr @@ -6,9 +6,10 @@ <%= rendered "components/feed_menu" %>
-
-
-
+
+ <% if plid %> + <%= translate(locale, "View as playlist") %> + <% end %>
From 05513bcd1e45584137143570980d8d636f9e2816 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 19:17:56 -0500 Subject: [PATCH 056/210] Fix "placeholder=" text in locales --- locales/ar.json | 8 ++++---- locales/de.json | 8 ++++---- locales/en-US.json | 8 ++++---- locales/es.json | 8 ++++---- locales/eu.json | 4 ++-- locales/fr.json | 8 ++++---- locales/it.json | 8 ++++---- locales/nb_NO.json | 8 ++++---- locales/nl.json | 8 ++++---- locales/pl.json | 8 ++++---- locales/ru.json | 8 ++++---- src/invidious/views/login.ecr | 22 +++++++++++----------- 12 files changed, 53 insertions(+), 53 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 572ea904..15f582fb 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -34,15 +34,15 @@ "Login": "تسجيل الدخول", "Login/Register": "تسجيل الدخول\\إنشاء حساب", "Login to Google": "تسجيل الدخول بإستخدام جوجل", - "User ID:": "إسم المستخدم:", - "Password:": "الرقم السرى:", + "User ID": "إسم المستخدم", + "Password": "الرقم السرى", "Time (h:mm:ss):": "(يجب ان يكتب مثل هذا التنسيق) الوقت (h(ساعات):mm(دقائق):ss(ثوانى)):", "Text CAPTCHA": "CAPTCHA كلامية", "Image CAPTCHA": "CAPTCHA صورية", "Sign In": "تسجيل الدخول", "Register": "انشاء الحساب", - "Email:": "الإيميل:", - "Google verification code:": "رمز تحقق جوجل:", + "Email": "الإيميل", + "Google verification code": "رمز تحقق جوجل", "Preferences": "التفضيلات", "Player preferences": "التفضيلات المشغل", "Always loop: ": "كرر الفيديو دائما: ", diff --git a/locales/de.json b/locales/de.json index 440f75e9..473cf7f2 100644 --- a/locales/de.json +++ b/locales/de.json @@ -34,15 +34,15 @@ "Login": "Einloggen", "Login/Register": "Einloggen/Registrieren", "Login to Google": "In Google einloggen", - "User ID:": "Benutzer ID:", - "Password:": "Passwort:", + "User ID": "Benutzer ID", + "Password": "Passwort", "Time (h:mm:ss):": "Zeit (h:mm:ss):", "Text CAPTCHA": "Text CAPTCHA", "Image CAPTCHA": "Image CAPTCHA", "Sign In": "Einloggen", "Register": "Registrieren", - "Email:": "Email:", - "Google verification code:": "Google Bestätigungscode:", + "Email": "Email", + "Google verification code": "Google Bestätigungscode", "Preferences": "Einstellungen", "Player preferences": "Playereinstellungen", "Always loop: ": "Immer wiederholen: ", diff --git a/locales/en-US.json b/locales/en-US.json index 88c035b5..2b817dd7 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -34,15 +34,15 @@ "Login": "Login", "Login/Register": "Login/Register", "Login to Google": "Login to Google", - "User ID:": "User ID:", - "Password:": "Password:", + "User ID": "User ID", + "Password": "Password", "Time (h:mm:ss):": "Time (h:mm:ss):", "Text CAPTCHA": "Text CAPTCHA", "Image CAPTCHA": "Image CAPTCHA", "Sign In": "Sign In", "Register": "Register", - "Email:": "Email:", - "Google verification code:": "Google verification code:", + "Email": "Email", + "Google verification code": "Google verification code", "Preferences": "Preferences", "Player preferences": "Player preferences", "Always loop: ": "Always loop: ", diff --git a/locales/es.json b/locales/es.json index 9cf54fe1..5a12c7bc 100644 --- a/locales/es.json +++ b/locales/es.json @@ -34,15 +34,15 @@ "Login": "Iniciar sesión", "Login/Register": "Iniciar sesión/Registrarse", "Login to Google": "Iniciar sesión en Google", - "User ID:": "Nombre:", - "Password:": "Contraseña:", + "User ID": "Nombre", + "Password": "Contraseña", "Time (h:mm:ss):": "Hora (h:mm:ss):", "Text CAPTCHA": "CAPTCHA en texto", "Image CAPTCHA": "CAPTCHA en imagen", "Sign In": "Iniciar sesión", "Register": "Registrarse", - "Email:": "Correo:", - "Google verification code:": "Código de verificación de Google:", + "Email": "Correo", + "Google verification code": "Código de verificación de Google", "Preferences": "Preferencias", "Player preferences": "Preferencias del reproductor", "Always loop: ": "Repetir siempre: ", diff --git a/locales/eu.json b/locales/eu.json index ddd00a4c..4ec5c46b 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -34,8 +34,8 @@ "Login": "Saioa hasi", "Login/Register": "Saioa hasi/Izena eman", "Login to Google": "Googlekin hasi saioa", - "User ID:": "Erabiltzaile IDa:", - "Password:": "Pasahitza:", + "User ID": "Erabiltzaile IDa", + "Password": "Pasahitza", "Time (h:mm:ss):": "Denbora (o:mm:ss):", "Text CAPTCHA": "Testu CAPTCHA", "Image CAPTCHA": "Irudi CAPTCHA", diff --git a/locales/fr.json b/locales/fr.json index fec1053e..69427f8e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -34,15 +34,15 @@ "Login": "Se connecter", "Login/Register": "Se connecter/Créer un compte", "Login to Google": "Se connecter avec Google", - "User ID:": "Identifiant utilisateur :", - "Password:": "Mot de passe :", + "User ID": "Identifiant utilisateur", + "Password": "Mot de passe", "Time (h:mm:ss):": "Heure (h:mm:ss) :", "Text CAPTCHA": "CAPTCHA Texte", "Image CAPTCHA": "CAPTCHA Image", "Sign In": "Se connecter", "Register": "S'inscrire", - "Email:": "E-mail :", - "Google verification code:": "Code de vérification Google :", + "Email": "E-mail", + "Google verification code": "Code de vérification Google", "Preferences": "Préférences", "Player preferences": "Préférences du lecteur", "Always loop: ": "Lire en boucle : ", diff --git a/locales/it.json b/locales/it.json index 9bd72ea5..1def7c2e 100644 --- a/locales/it.json +++ b/locales/it.json @@ -34,15 +34,15 @@ "Login": "Entra", "Login/Register": "Entra/Registrati", "Login to Google": "Entra con Google", - "User ID:": "ID utente:", - "Password:": "Password:", + "User ID": "ID utente", + "Password": "Password", "Time (h:mm:ss):": "Orario (h:mm:ss):", "Text CAPTCHA": "Testo del CAPTCHA", "Image CAPTCHA": "Immagine CAPTCHA", "Sign In": "Entra", "Register": "Registrati", - "Email:": "Email:", - "Google verification code:": "Codice di verifica Google:", + "Email": "Email", + "Google verification code": "Codice di verifica Google", "Preferences": "Preferenze", "Player preferences": "Preferenze del riproduttore", "Always loop: ": "Ripeti sempre: ", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 6d032e6f..0de28379 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -34,15 +34,15 @@ "Login": "Logg inn", "Login/Register": "Logg inn/registrer", "Login to Google": "Logg inn med Google", - "User ID:": "Bruker-ID:", - "Password:": "Passord:", + "User ID": "Bruker-ID", + "Password": "Passord", "Time (h:mm:ss):": "Tid (h:mm:ss):", "Text CAPTCHA": "Tekst-CAPTCHA", "Image CAPTCHA": "Bilde-CAPTCHA", "Sign In": "Innlogging", "Register": "Registrer", - "Email:": "E-post:", - "Google verification code:": "Google-bekreftelseskode:", + "Email": "E-post", + "Google verification code": "Google-bekreftelseskode", "Preferences": "Innstillinger", "Player preferences": "Avspillerinnstillinger", "Always loop: ": "Alltid gjenta: ", diff --git a/locales/nl.json b/locales/nl.json index 69c43b5b..ac5f4ea5 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -34,15 +34,15 @@ "Login": "Inloggen", "Login/Register": "Inloggen/Registreren", "Login to Google": "Inloggen op Google", - "User ID:": "Gebruiker ID:", - "Password:": "Wachtwoord:", + "User ID": "Gebruiker ID", + "Password": "Wachtwoord", "Time (h:mm:ss):": "Tijd (h:mm:ss):", "Text CAPTCHA": "Tekst CAPTCHA", "Image CAPTCHA": "Afbeelding CAPTCHA", "Sign In": "Aanmelden", "Register": "Registreren", - "Email:": "Email:", - "Google verification code:": "Google verificatie code:", + "Email": "Email", + "Google verification code": "Google verificatie code", "Preferences": "Voorkeuren", "Player preferences": "Afspeler voorkeuren", "Always loop: ": "Altijd herhalen: ", diff --git a/locales/pl.json b/locales/pl.json index 901892eb..f4c4f6b2 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -34,15 +34,15 @@ "Login": "Zaloguj", "Login/Register": "Zaloguj/Zarejestruj", "Login to Google": "Zaloguj do Google", - "User ID:": "ID użytkownika:", - "Password:": "Hasło:", + "User ID": "ID użytkownika", + "Password": "Hasło", "Time (h:mm:ss):": "Godzina (h:mm:ss):", "Text CAPTCHA": "Tekst CAPTCHA", "Image CAPTCHA": "Obraz CAPTCHA", "Sign In": "Zaloguj się", "Register": "Zarejestruj się", - "Email:": "Email:", - "Google verification code:": "Kod weryfikacyjny Google:", + "Email": "Email", + "Google verification code": "Kod weryfikacyjny Google", "Preferences": "Preferencje", "Player preferences": "Ustawienia odtwarzacza", "Always loop: ": "Zawsze zapętlaj: ", diff --git a/locales/ru.json b/locales/ru.json index a287cb80..80c4d0d1 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -34,15 +34,15 @@ "Login": "Войти", "Login/Register": "Войти/Регистрация", "Login to Google": "Войти через Google", - "User ID:": "ID пользователя:", - "Password:": "Пароль:", + "User ID": "ID пользователя", + "Password": "Пароль", "Time (h:mm:ss):": "Время (ч:мм:сс):", "Text CAPTCHA": "Текст капчи", "Image CAPTCHA": "Изображение капчи", "Sign In": "Войти", "Register": "Регистрация", - "Email:": "Эл. почта:", - "Google verification code:": "Код подтверждения Google:", + "Email": "Эл. почта", + "Google verification code": "Код подтверждения Google", "Preferences": "Настройки", "Player preferences": "Настройки проигрывателя", "Always loop: ": "Всегда повторять: ", diff --git a/src/invidious/views/login.ecr b/src/invidious/views/login.ecr index b26aa373..ec5dc147 100644 --- a/src/invidious/views/login.ecr +++ b/src/invidious/views/login.ecr @@ -25,15 +25,15 @@ <% if email %> <% else %> - - + + "> <% end %> <% if password %> <% else %> - - + + "> <% end %> <% if captcha %> @@ -56,7 +56,7 @@ <% end %> - + "> <% end %> From 4582b6cf76db48fbf5c89ccd326885eb8bd73ee8 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Sat, 13 Apr 2019 23:45:06 +0000 Subject: [PATCH 057/210] Update Esperanto translation --- locales/eo.json | 506 ++++++++++++++++++++++++------------------------ 1 file changed, 253 insertions(+), 253 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index b1d81ca4..0e122a13 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,151 +1,151 @@ { - "`x` subscribers": "", - "`x` videos": "", - "LIVE": "", - "Shared `x` ago": "", - "Unsubscribe": "", - "Subscribe": "", - "Login to subscribe to `x`": "", - "View channel on YouTube": "", - "newest": "", - "oldest": "", - "popular": "", - "last": "", - "Next page": "", - "Previous page": "", - "Clear watch history?": "", - "Yes": "", - "No": "", - "Import and Export Data": "", - "Import": "", - "Import Invidious data": "", - "Import YouTube subscriptions": "", - "Import FreeTube subscriptions (.db)": "", - "Import NewPipe subscriptions (.json)": "", - "Import NewPipe data (.zip)": "", - "Export": "", - "Export subscriptions as OPML": "", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "", - "Export data as JSON": "", - "Delete account?": "", - "History": "", - "An alternative front-end to YouTube": "", - "JavaScript license information": "", - "source": "", - "Login": "", - "Login/Register": "", - "Login to Google": "", - "User ID:": "", - "Password:": "", - "Time (h:mm:ss):": "", - "Text CAPTCHA": "", - "Image CAPTCHA": "", - "Sign In": "", - "Register": "", - "Email:": "", - "Google verification code:": "", - "Preferences": "", - "Player preferences": "", - "Always loop: ": "", - "Autoplay: ": "", - "Autoplay next video: ": "", - "Listen by default: ": "", - "Proxy videos? ": "", - "Default speed: ": "", - "Preferred video quality: ": "", - "Player volume: ": "", - "Default comments: ": "", - "Default captions: ": "", - "Fallback captions: ": "", - "Show related videos? ": "", - "Visual preferences": "", - "Dark mode: ": "", - "Thin mode: ": "", - "Subscription preferences": "", - "Redirect homepage to feed: ": "", - "Number of videos shown in feed: ": "", - "Sort videos by: ": "", - "published": "", - "published - reverse": "", - "alphabetically": "", - "alphabetically - reverse": "", - "channel name": "", - "channel name - reverse": "", - "Only show latest video from channel: ": "", - "Only show latest unwatched video from channel: ": "", - "Only show unwatched: ": "", - "Only show notifications (if there are any): ": "", - "Data preferences": "", - "Clear watch history": "", - "Import/Export data": "", - "Manage subscriptions": "", - "Watch history": "", - "Delete account": "", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", - "Save preferences": "", - "Subscription manager": "", - "`x` subscriptions": "", - "Import/Export": "", - "unsubscribe": "", - "Subscriptions": "", - "`x` unseen notifications": "", - "search": "", - "Sign out": "", - "Released under the AGPLv3 by Omar Roth.": "", - "Source available here.": "", - "View JavaScript license information.": "", - "View privacy policy.": "", + "`x` subscribers": "`x` abonantoj", + "`x` videos": "`x` videoj", + "LIVE": "NUNA", + "Shared `x` ago": "Konigita antaŭ `x`", + "Unsubscribe": "Malaboni", + "Subscribe": "Aboni", + "Login to subscribe to `x`": "Ensaluti por aboni je `x`", + "View channel on YouTube": "Vidi kanalon en YouTube", + "newest": "pli novaj", + "oldest": "pli malnovaj", + "popular": "popularaj", + "last": "lasta", + "Next page": "Sekva paĝo", + "Previous page": "Antaŭa paĝo", + "Clear watch history?": "Ĉu forigi vidohistorion?", + "Yes": "Jes", + "No": "Ne", + "Import and Export Data": "Importi kaj Eksporti Datumojn", + "Import": "Importi", + "Import Invidious data": "Importi datumojn de Invidious", + "Import YouTube subscriptions": "Importi abonojn de YouTube", + "Import FreeTube subscriptions (.db)": "Importi abonojn de FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importi abonojn de NewPipe (.json)", + "Import NewPipe data (.zip)": "Importi datumojn de NewPipe (.zip)", + "Export": "Eksporti", + "Export subscriptions as OPML": "Eksporti abonojn kiel OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporti abonojn kiel OPML (por NewPipe kaj FreeTube)", + "Export data as JSON": "Eksporti datumojn kiel JSON", + "Delete account?": "Ĉu forigi konton?", + "History": "Historio", + "An alternative front-end to YouTube": "Alternativa fasado al YouTube", + "JavaScript license information": "Ĝavoskripta licenca informo", + "source": "fonto", + "Login": "Ensaluti", + "Login/Register": "Ensaluti/Registriĝi", + "Login to Google": "Ensaluti al Google", + "User ID:": "Uzula identigilo:", + "Password:": "Pasvorto:", + "Time (h:mm:ss):": "Horo (h:mm:ss):", + "Text CAPTCHA": "Teksta CAPTCHA", + "Image CAPTCHA": "Bilda CAPTCHA", + "Sign In": "Ensaluti", + "Register": "Registriĝi", + "Email:": "Retpoŝto:", + "Google verification code:": "Kontrolkodo de Google:", + "Preferences": "Agordoj", + "Player preferences": "Spektilaj agordoj", + "Always loop: ": "Ĉiam ripeti: ", + "Autoplay: ": "Aŭtomate ludi: ", + "Autoplay next video: ": "Aŭtomate ludi sekvan videon: ", + "Listen by default: ": "Aŭskulti defaŭlte: ", + "Proxy videos? ": "Ĉu uzi prokuran servilon por videoj? ", + "Default speed: ": "Defaŭlta rapido: ", + "Preferred video quality: ": "Preferita videkvalito: ", + "Player volume: ": "Ludila sonforteco: ", + "Default comments: ": "Defaŭltaj komentoj: ", + "Default captions: ": "Defaŭltaj subtekstoj: ", + "Fallback captions: ": "Retrodefaŭltaj subtekstoj: ", + "Show related videos? ": "Ĉu montri rilatajn videojn? ", + "Visual preferences": "Vidaj preferoj", + "Dark mode: ": "Malhela reĝimo: ", + "Thin mode: ": "Maldika reĝimo: ", + "Subscription preferences": "Abonaj agordoj", + "Redirect homepage to feed: ": "Alidirekti hejmpâgon al fluo: ", + "Number of videos shown in feed: ": "Nombro da videoj montritaj en fluo: ", + "Sort videos by: ": "Ordi videojn laŭ: ", + "published": "publikigo", + "published - reverse": "publitigo - renverse", + "alphabetically": "alfabete", + "alphabetically - reverse": "alfabete - renverse", + "channel name": "kanala nombro", + "channel name - reverse": "kanala nombro - renverse", + "Only show latest video from channel: ": "Nur montri pli novan videon el kanalo: ", + "Only show latest unwatched video from channel: ": "Nur montri pli novan malviditan videon el kanalo: ", + "Only show unwatched: ": "Nur montri malviditajn: ", + "Only show notifications (if there are any): ": "Nur montri sciigojn (se estas): ", + "Data preferences": "Datumagordoj", + "Clear watch history": "Forigi vidohistorion", + "Import/Export data": "Importi/Eksporti datumojn", + "Manage subscriptions": "Administri abonojn", + "Watch history": "Vidohistorio", + "Delete account": "Forigi konton", + "Administrator preferences": "Agordoj de administranto", + "Default homepage: ": "Defaŭlta hejmpaĝo: ", + "Feed menu: ": "Flua menuo: ", + "Top enabled? ": "Ĉu pli bonaj ŝaltitaj? ", + "CAPTCHA enabled? ": "Ĉu CAPTCHA ŝaltita? ", + "Login enabled? ": "Ĉu ensaluto aktivita? ", + "Registration enabled? ": "Ĉu registriĝo aktivita? ", + "Report statistics? ": "Ĉu raporti statistikojn? ", + "Save preferences": "Konservi agordojn", + "Subscription manager": "Administrilo de abonoj", + "`x` subscriptions": "`x` abonoj", + "Import/Export": "Importi/Eksporti", + "unsubscribe": "malaboni", + "Subscriptions": "Abonoj", + "`x` unseen notifications": "`x` neviditaj sciigoj", + "search": "serĉi", + "Sign out": "Elsaluti", + "Released under the AGPLv3 by Omar Roth.": "Eldonita sub la AGPLv3 de Omar Roth.", + "Source available here.": "Fonto havebla ĉi tie.", + "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", + "View privacy policy.": "Vidi regularon pri privateco.", "Trending": "", "Unlisted": "", - "Watch video on Youtube": "", - "Genre: ": "", - "License: ": "", + "Watch video on Youtube": "Vidi videon en Youtube", + "Genre: ": "Ĝenro: ", + "License: ": "Licenco: ", "Family friendly? ": "", "Wilson score: ": "", "Engagement: ": "", "Whitelisted regions: ": "", "Blacklisted regions: ": "", - "Shared `x`": "", + "Shared `x`": "Konigita `x`", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", - "View YouTube comments": "", - "View more comments on Reddit": "", - "View `x` comments": "", - "View Reddit comments": "", - "Hide replies": "", - "Show replies": "", - "Incorrect password": "", + "View YouTube comments": "Vidi komentojn de YouTube", + "View more comments on Reddit": "Vidi pli komentoj en Reddit", + "View `x` comments": "Vidi `x` komentojn", + "View Reddit comments": "Vidi komentojn de Reddit", + "Hide replies": "Kaŝi respondojn", + "Show replies": "Montri respondojn", + "Incorrect password": "Malbona pasvorto", "Quota exceeded, try again in a few hours": "", "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", "Invalid TFA code": "", "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "", - "Invalid CAPTCHA": "", + "Invalid answer": "Nevalida respondo", + "Invalid CAPTCHA": "Nevalida CAPTCHA", "CAPTCHA is a required field": "", "User ID is a required field": "", "Password is a required field": "", - "Invalid username or password": "", - "Please sign in using 'Sign in with Google'": "", + "Invalid username or password": "Nevalida uzantnomo aŭ pasvorto", + "Please sign in using 'Sign in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", "Password cannot be empty": "", "Password cannot be longer than 55 characters": "", - "Please sign in": "", + "Please sign in": "Bonvolu ensaluti", "Invidious Private Feed for `x`": "", - "channel:`x`": "", - "Deleted or invalid channel": "", - "This channel does not exist.": "", + "channel:`x`": "kanalo:`x`", + "Deleted or invalid channel": "Forigita aŭ nevalida kanalo", + "This channel does not exist.": "Ĉi tiu kanalo ne ekzistas.", "Could not get channel info.": "", "Could not fetch comments": "", - "View `x` replies": "", - "`x` ago": "", - "Load more": "", - "`x` points": "", + "View `x` replies": "Vidi `x` respondojn", + "`x` ago": "antaŭ `x`", + "Load more": "Ŝarĝi pli", + "`x` points": "`x` poentoj", "Could not create mix.": "", "Playlist is empty": "", "Invalid playlist.": "", @@ -155,141 +155,141 @@ "Hidden field \"token\" is a required field": "", "Invalid challenge": "", "Invalid token": "", - "Invalid user": "", + "Invalid user": "Nevalida uzanto", "Token is expired, please try again": "", - "English": "", - "English (auto-generated)": "", - "Afrikaans": "", - "Albanian": "", - "Amharic": "", - "Arabic": "", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "", - "Bosnian": "", - "Bulgarian": "", - "Burmese": "", - "Catalan": "", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "", - "Danish": "", - "Dutch": "", - "Esperanto": "", - "Estonian": "", - "Filipino": "", - "Finnish": "", - "French": "", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "", - "Icelandic": "", - "Igbo": "", - "Indonesian": "", - "Irish": "", - "Italian": "", - "Japanese": "", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "", - "Slovenian": "", - "Somali": "", - "Southern Sotho": "", - "Spanish": "", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "", - "Ukrainian": "", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "", - "`x` months": "", - "`x` weeks": "", - "`x` days": "", - "`x` hours": "", - "`x` minutes": "", - "`x` seconds": "", + "English": "Angla", + "English (auto-generated)": "Angla (aŭtomate generita)", + "Afrikaans": "Afrikansa", + "Albanian": "Albana", + "Amharic": "Amhara", + "Arabic": "Araba", + "Armenian": "Armena", + "Azerbaijani": "Azerbajĝana", + "Bangla": "Bengala", + "Basque": "Eŭska", + "Belarusian": "Belorusa", + "Bosnian": "Bosna", + "Bulgarian": "Bulgara", + "Burmese": "Birma", + "Catalan": "Kataluna", + "Cebuano": "Cebua", + "Chinese (Simplified)": "Ĉina (simpligita)", + "Chinese (Traditional)": "Ĉina (tradicia)", + "Corsican": "Korsika", + "Croatian": "Kroata", + "Czech": "Ĉeĥa", + "Danish": "Dana", + "Dutch": "Nederlanda", + "Esperanto": "Esperanto", + "Estonian": "Estona", + "Filipino": "Filipina", + "Finnish": "Finna", + "French": "Franca", + "Galician": "Galega", + "Georgian": "Kartvela", + "German": "Germana", + "Greek": "Greka", + "Gujarati": "Guĝarata", + "Haitian Creole": "Haitia kreola", + "Hausa": "Haŭsa", + "Hawaiian": "Havaja", + "Hebrew": "Hebrea", + "Hindi": "Hindia", + "Hmong": "Miaa", + "Hungarian": "Hungara", + "Icelandic": "Islanda", + "Igbo": "Igba", + "Indonesian": "Indonezia", + "Irish": "Irlanda", + "Italian": "Itala", + "Japanese": "Japana", + "Javanese": "Java", + "Kannada": "Kanara", + "Kazakh": "Kazaĥa", + "Khmer": "Kmera", + "Korean": "Korea", + "Kurdish": "Kurda", + "Kyrgyz": "Kirgiza", + "Lao": "Laosa", + "Latin": "Latina", + "Latvian": "Latva", + "Lithuanian": "Litova", + "Luxembourgish": "Luksemburga", + "Macedonian": "Makedona", + "Malagasy": "Malagasa", + "Malay": "Malaja", + "Malayalam": "Malajala", + "Maltese": "Malta", + "Maori": "Maoria", + "Marathi": "Marata", + "Mongolian": "Mongola", + "Nepali": "Nepala", + "Norwegian": "Norvega", + "Nyanja": "Njanĝa", + "Pashto": "Paŝtuna", + "Persian": "Persa", + "Polish": "Pola", + "Portuguese": "Portugala", + "Punjabi": "Panĝaba", + "Romanian": "Rumana", + "Russian": "Rusa", + "Samoan": "Samoa", + "Scottish Gaelic": "Skotgaela", + "Serbian": "Serba", + "Shona": "Ŝona", + "Sindhi": "Sinda", + "Sinhala": "Sinhala", + "Slovak": "Slovaka", + "Slovenian": "Slovena", + "Somali": "Somala", + "Southern Sotho": "Sota", + "Spanish": "Hispana", + "Spanish (Latin America)": "Hispana (Latinameriko)", + "Sundanese": "Sunda", + "Swahili": "Svahila", + "Swedish": "Sveda", + "Tajik": "Taĝika", + "Tamil": "Tamila", + "Telugu": "Telugua", + "Thai": "Taja", + "Turkish": "Turka", + "Ukrainian": "Ukraina", + "Urdu": "Urduo", + "Uzbek": "Uzbeka", + "Vietnamese": "Vjetnama", + "Welsh": "Kimra", + "Western Frisian": "Okcidentfrisa", + "Xhosa": "Kosa", + "Yiddish": "Jida", + "Yoruba": "Joruba", + "Zulu": "Zulua", + "`x` years": "`x` jaroj", + "`x` months": "`x` monatoj", + "`x` weeks": "`x` semajnoj", + "`x` days": "`x` tagoj", + "`x` hours": "`x` horoj", + "`x` minutes": "`x` minutoj", + "`x` seconds": "`x` sekundoj", "Fallback comments: ": "", - "Popular": "", - "Top": "", - "About": "", + "Popular": "Popularaj", + "Top": "Supraj", + "About": "Pri", "Rating: ": "", - "Language: ": "", - "Default": "", - "Music": "", + "Language: ": "Lingvo: ", + "Default": "Defaŭlte", + "Music": "Musiko", "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", + "News": "Novaĵoj", + "Movies": "Filmoj", + "Download": "Elŝuti", + "Download as: ": "Elŝuti kiel: ", "%A %B %-d, %Y": "", - "(edited)": "", + "(edited)": "(redaktita)", "Youtube permalink of the comment": "", "`x` marked it with a ❤": "", "Audio mode": "", "Video mode": "", - "Videos": "", + "Videos": "Videoj", "Playlists": "", - "Current version: ": "" + "Current version: ": "Nuna versio: " } From 0ed56b706b089fd370eb59c6d100bca6738c54f9 Mon Sep 17 00:00:00 2001 From: Tolstovka Date: Sun, 14 Apr 2019 00:41:32 +0000 Subject: [PATCH 058/210] Update Russian translation --- locales/ru.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 5a664b0d..76ec4f8e 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -51,13 +51,13 @@ "Autoplay next video: ": "Автовоспроизведение следующего видео: ", "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", "Proxy videos? ": "Проксировать видео? ", - "Default speed: ": "Скорость по-умолчанию: ", + "Default speed: ": "Скорость по умолчанию: ", "Preferred video quality: ": "Предпочтительное качество видео: ", "Player volume: ": "Громкость воспроизведения: ", "Default comments: ": "Источник комментариев: ", "youtube": "YouTube", "reddit": "Reddit", - "Default captions: ": "Субтитры по-умолчанию: ", + "Default captions: ": "Субтитры по умолчанию: ", "Fallback captions: ": "Резервные субтитры: ", "Show related videos? ": "Показывать похожие видео? ", "Visual preferences": "Визуальные настройки", @@ -86,7 +86,7 @@ "Administrator preferences": "Настройки администратора", "Default homepage: ": "Главная страница по умолчанию: ", "Feed menu: ": "Меню ленты: ", - "Top enabled? ": "Включить ТОП? ", + "Top enabled? ": "Включить топ? ", "CAPTCHA enabled? ": "Включить капчу? ", "Login enabled? ": "Включить логин? ", "Registration enabled? ": "Включить регистрацию? ", @@ -110,7 +110,7 @@ "Genre: ": "Жанр: ", "License: ": "Лицензия: ", "Family friendly? ": "Семейный просмотр: ", - "Wilson score: ": "Рейтинг Вильсона: ", + "Wilson score: ": "Рейтинг Уилсона: ", "Engagement: ": "Вовлеченность: ", "Whitelisted regions: ": "Доступно для: ", "Blacklisted regions: ": "Недоступно для: ", From c273a8ee69a5a0ab1884c799b7f1f8ba780f4cda Mon Sep 17 00:00:00 2001 From: Tolstovka Date: Sun, 14 Apr 2019 00:03:50 +0000 Subject: [PATCH 059/210] Update Ukrainian translation --- locales/uk.json | 302 ++++++++++++++++++++++++------------------------ 1 file changed, 151 insertions(+), 151 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index b1d81ca4..33236183 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -1,156 +1,156 @@ { - "`x` subscribers": "", - "`x` videos": "", - "LIVE": "", - "Shared `x` ago": "", - "Unsubscribe": "", - "Subscribe": "", - "Login to subscribe to `x`": "", - "View channel on YouTube": "", - "newest": "", - "oldest": "", - "popular": "", - "last": "", - "Next page": "", - "Previous page": "", - "Clear watch history?": "", - "Yes": "", - "No": "", - "Import and Export Data": "", - "Import": "", - "Import Invidious data": "", - "Import YouTube subscriptions": "", - "Import FreeTube subscriptions (.db)": "", - "Import NewPipe subscriptions (.json)": "", - "Import NewPipe data (.zip)": "", - "Export": "", - "Export subscriptions as OPML": "", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "", - "Export data as JSON": "", - "Delete account?": "", - "History": "", - "An alternative front-end to YouTube": "", - "JavaScript license information": "", - "source": "", - "Login": "", - "Login/Register": "", - "Login to Google": "", - "User ID:": "", - "Password:": "", - "Time (h:mm:ss):": "", - "Text CAPTCHA": "", - "Image CAPTCHA": "", - "Sign In": "", - "Register": "", - "Email:": "", - "Google verification code:": "", - "Preferences": "", - "Player preferences": "", - "Always loop: ": "", - "Autoplay: ": "", - "Autoplay next video: ": "", - "Listen by default: ": "", - "Proxy videos? ": "", - "Default speed: ": "", - "Preferred video quality: ": "", - "Player volume: ": "", - "Default comments: ": "", - "Default captions: ": "", - "Fallback captions: ": "", - "Show related videos? ": "", - "Visual preferences": "", - "Dark mode: ": "", - "Thin mode: ": "", - "Subscription preferences": "", - "Redirect homepage to feed: ": "", - "Number of videos shown in feed: ": "", - "Sort videos by: ": "", - "published": "", - "published - reverse": "", - "alphabetically": "", - "alphabetically - reverse": "", - "channel name": "", - "channel name - reverse": "", - "Only show latest video from channel: ": "", - "Only show latest unwatched video from channel: ": "", - "Only show unwatched: ": "", - "Only show notifications (if there are any): ": "", - "Data preferences": "", - "Clear watch history": "", - "Import/Export data": "", - "Manage subscriptions": "", - "Watch history": "", - "Delete account": "", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", - "Save preferences": "", - "Subscription manager": "", - "`x` subscriptions": "", - "Import/Export": "", - "unsubscribe": "", - "Subscriptions": "", - "`x` unseen notifications": "", - "search": "", - "Sign out": "", - "Released under the AGPLv3 by Omar Roth.": "", - "Source available here.": "", - "View JavaScript license information.": "", - "View privacy policy.": "", - "Trending": "", + "`x` subscribers": "`x` підписник / підписників / підписника", + "`x` videos": "`x` відео", + "LIVE": "ПРЯМИЙ ЕФІР", + "Shared `x` ago": "Розміщено `x` назад", + "Unsubscribe": "Відписатися", + "Subscribe": "Підписатися", + "Login to subscribe to `x`": "Увійдіть, щоб підписатися на `x`", + "View channel on YouTube": "Подивитися канал на YouTube", + "newest": "найновіше", + "oldest": "найстаріше", + "popular": "популярне", + "last": "останнє", + "Next page": "Наступна сторінка", + "Previous page": "Попередня сторінка", + "Clear watch history?": "Очистити історію переглядів?", + "Yes": "Так", + "No": "Ні", + "Import and Export Data": "Імпорт і експорт даних", + "Import": "Імпорт", + "Import Invidious data": "Імпортувати дані Invidious", + "Import YouTube subscriptions": "Імпортувати підписки з YouTube", + "Import FreeTube subscriptions (.db)": "Імпортувати підписки з FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Імпортувати підписки з NewPipe (.json)", + "Import NewPipe data (.zip)": "Імпортувати дані з NewPipe (.zip)", + "Export": "Експорт", + "Export subscriptions as OPML": "Експортувати підписки у форматі OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Експортувати підписки у форматі OPML (для NewPipe та FreeTube)", + "Export data as JSON": "Експортувати дані у форматі JSON", + "Delete account?": "Видалити обліківку?", + "History": "Історія", + "An alternative front-end to YouTube": "Альтернативний фронтенд до YouTube", + "JavaScript license information": "Інформація щодо ліцензій JavaScript", + "source": "джерело", + "Login": "Увійти", + "Login/Register": "Увійти або зареєструватися", + "Login to Google": "Увійти через Google", + "User ID:": "ID користувача:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Час (г:мм:сс):", + "Text CAPTCHA": "Текст капчі", + "Image CAPTCHA": "Зображення капчі", + "Sign In": "Увійти", + "Register": "Зареєструватися", + "Email:": "Електронна пошта:", + "Google verification code:": "Код підтвердження Google:", + "Preferences": "Налаштування", + "Player preferences": "Налаштування програвача", + "Always loop: ": "Завжди повторювати: ", + "Autoplay: ": "Автовідтворення: ", + "Autoplay next video: ": "Автовідтворення наступного відео: ", + "Listen by default: ": "Режим «тільки звук» як усталений: ", + "Proxy videos? ": "Програвати відео через проксі? ", + "Default speed: ": "Усталена швидкість відео: ", + "Preferred video quality: ": "Пріорітетна якість відео: ", + "Player volume: ": "Гучність відео: ", + "Default comments: ": "Джерело коментарів: ", + "Default captions: ": "Основна мова субтитрів: ", + "Fallback captions: ": "Запасна мова субтитрів: ", + "Show related videos? ": "Показувати схожі відео? ", + "Visual preferences": "Налаштування сайту", + "Dark mode: ": "Темне оформлення: ", + "Thin mode: ": "Полегшене оформлення: ", + "Subscription preferences": "Налаштування підписок", + "Redirect homepage to feed: ": "Показувати відео з каналів, на які підписані, як головну сторінку: ", + "Number of videos shown in feed: ": "Кількість відео з каналів, на які підписані, у потоці: ", + "Sort videos by: ": "Сортувати відео: ", + "published": "за датою розміщення", + "published - reverse": "за датою розміщення в зворотному порядку", + "alphabetically": "за абеткою", + "alphabetically - reverse": "за абеткою в зворотному порядку", + "channel name": "за назвою каналу", + "channel name - reverse": "за назвою каналу в зворотному порядку", + "Only show latest video from channel: ": "Показувати тільки останнє відео з каналів: ", + "Only show latest unwatched video from channel: ": "Показувати тільки непереглянуті відео з каналів: ", + "Only show unwatched: ": "Показувати тільки непереглянуті відео: ", + "Only show notifications (if there are any): ": "Показувати лише сповіщення, якщо вони є: ", + "Data preferences": "Налаштування даних", + "Clear watch history": "Очистити історію переглядів", + "Import/Export data": "Імпорт і експорт даних", + "Manage subscriptions": "Керування підписками", + "Watch history": "Історія переглядів", + "Delete account": "Видалити обліківку", + "Administrator preferences": "Адміністраторські налаштування", + "Default homepage: ": "Усталена домашня сторінка: ", + "Feed menu: ": "Меню потоку з відео: ", + "Top enabled? ": "Увімкнути топ відео? ", + "CAPTCHA enabled? ": "Увімкнути капчу? ", + "Login enabled? ": "Увімкнути авторизацію? ", + "Registration enabled? ": "Увімкнути реєстрацію? ", + "Report statistics? ": "Повідомляти статистику? ", + "Save preferences": "Зберегти налаштування", + "Subscription manager": "Менеджер підписок", + "`x` subscriptions": "`x` підписка / підписок / підписки", + "Import/Export": "Імпорт і експорт", + "unsubscribe": "відписатися", + "Subscriptions": "Підписки", + "`x` unseen notifications": "`x` непереглянуте сповіщення / непереглянутих сповіщень / непереглянутих сповіщення", + "search": "пошук", + "Sign out": "Вийти", + "Released under the AGPLv3 by Omar Roth.": "Реалізовано Омаром Ротом за ліцензією AGPLv3.", + "Source available here.": "Програмний код доступний тут.", + "View JavaScript license information.": "Переглянути інформацію щодо ліцензії JavaScript.", + "View privacy policy.": "Переглянути політику приватності.", + "Trending": "У тренді", "Unlisted": "", - "Watch video on Youtube": "", - "Genre: ": "", - "License: ": "", - "Family friendly? ": "", - "Wilson score: ": "", - "Engagement: ": "", - "Whitelisted regions: ": "", - "Blacklisted regions: ": "", - "Shared `x`": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", - "View YouTube comments": "", - "View more comments on Reddit": "", - "View `x` comments": "", - "View Reddit comments": "", - "Hide replies": "", - "Show replies": "", - "Incorrect password": "", - "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", - "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "", - "Invalid CAPTCHA": "", - "CAPTCHA is a required field": "", - "User ID is a required field": "", - "Password is a required field": "", - "Invalid username or password": "", - "Please sign in using 'Sign in with Google'": "", - "Password cannot be empty": "", - "Password cannot be longer than 55 characters": "", - "Please sign in": "", - "Invidious Private Feed for `x`": "", - "channel:`x`": "", - "Deleted or invalid channel": "", - "This channel does not exist.": "", - "Could not get channel info.": "", - "Could not fetch comments": "", - "View `x` replies": "", - "`x` ago": "", - "Load more": "", - "`x` points": "", - "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", - "Playlist does not exist.": "", - "Could not pull trending pages.": "", + "Watch video on Youtube": "Дивитися відео на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Ліцензія: ", + "Family friendly? ": "Перегляд із родиною? ", + "Wilson score: ": "Рейтинг Вілсона: ", + "Engagement: ": "Залученість: ", + "Whitelisted regions: ": "Доступно у регіонах: ", + "Blacklisted regions: ": "Недоступно у регіонах: ", + "Shared `x`": "Розміщено `x`", + "Premieres in `x`": "Прем’єра через `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Схоже, у вас відключений JavaScript. Щоб побачити коментарі, натисніть сюда, але майте на увазі, що вони можуть завантажуватися трохи довше.", + "View YouTube comments": "Переглянути коментарі з YouTube", + "View more comments on Reddit": "Переглянути більше коментарів на Reddit", + "View `x` comments": "Переглянути `x` коментар / коментарів / коментаря", + "View Reddit comments": "Переглянути коментарі з Reddit", + "Hide replies": "Сховати відповіді", + "Show replies": "Показати відповіді", + "Incorrect password": "Неправильний пароль", + "Quota exceeded, try again in a few hours": "Ліміт перевищено, спробуйте знову за декілька годин", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Не вдається увійти. Перевірте, чи не ввімкнена двофакторна аутентифікація (за кодом чи смс).", + "Invalid TFA code": "Неправильний код двофакторної аутентифікації", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не вдається увійти. Це може бути через те, що у вашій обліківці не ввімкнена двофакторна аутентифікація.", + "Invalid answer": "Неправильна відповідь", + "Invalid CAPTCHA": "Неправильна капча", + "CAPTCHA is a required field": "Необхідно пройти капчу", + "User ID is a required field": "Необхідно ввести ID користувача", + "Password is a required field": "Необхідно ввести пароль", + "Invalid username or password": "Неправильний логін чи пароль", + "Please sign in using 'Sign in with Google'": "Будь ласка, натисніть «Увійдіть через Google»", + "Password cannot be empty": "Пароль не може бути порожнім", + "Password cannot be longer than 55 characters": "Пароль не може бути довшим за 55 знаків", + "Please sign in": "Будь ласка, увійдіть", + "Invidious Private Feed for `x`": "Приватний поток відео Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал видалено або не знайдено", + "This channel does not exist.": "Такого каналу не існує.", + "Could not get channel info.": "Не вдається отримати інформацію щодо цього каналу.", + "Could not fetch comments": "Не вдається завантажити коментарі", + "View `x` replies": "Переглянути `x` відповідь / відповідей / відповіді", + "`x` ago": "`x` тому", + "Load more": "Завантажити більше", + "`x` points": "`x` очко / очок / очка", + "Could not create mix.": "Не вдається створити мікс.", + "Playlist is empty": "Плейлист порожній", + "Invalid playlist.": "Недійсний плейлист.", + "Playlist does not exist.": "Плейлист не існує.", + "Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».", "Hidden field \"challenge\" is a required field": "", "Hidden field \"token\" is a required field": "", "Invalid challenge": "", From 0178013fc182915d30691c928daeac25d58b4dd4 Mon Sep 17 00:00:00 2001 From: Tolstovka Date: Sun, 14 Apr 2019 00:03:50 +0000 Subject: [PATCH 060/210] Update Ukrainian translation --- locales/eo.json | 586 ++++++++++++++++++++++---------------------- locales/nb_NO.json | 586 ++++++++++++++++++++++---------------------- locales/ru.json | 590 ++++++++++++++++++++++----------------------- locales/uk.json | 586 ++++++++++++++++++++++---------------------- 4 files changed, 1174 insertions(+), 1174 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 0e122a13..39dc9acd 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,295 +1,295 @@ { - "`x` subscribers": "`x` abonantoj", - "`x` videos": "`x` videoj", - "LIVE": "NUNA", - "Shared `x` ago": "Konigita antaŭ `x`", - "Unsubscribe": "Malaboni", - "Subscribe": "Aboni", - "Login to subscribe to `x`": "Ensaluti por aboni je `x`", - "View channel on YouTube": "Vidi kanalon en YouTube", - "newest": "pli novaj", - "oldest": "pli malnovaj", - "popular": "popularaj", - "last": "lasta", - "Next page": "Sekva paĝo", - "Previous page": "Antaŭa paĝo", - "Clear watch history?": "Ĉu forigi vidohistorion?", - "Yes": "Jes", - "No": "Ne", - "Import and Export Data": "Importi kaj Eksporti Datumojn", - "Import": "Importi", - "Import Invidious data": "Importi datumojn de Invidious", - "Import YouTube subscriptions": "Importi abonojn de YouTube", - "Import FreeTube subscriptions (.db)": "Importi abonojn de FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Importi abonojn de NewPipe (.json)", - "Import NewPipe data (.zip)": "Importi datumojn de NewPipe (.zip)", - "Export": "Eksporti", - "Export subscriptions as OPML": "Eksporti abonojn kiel OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporti abonojn kiel OPML (por NewPipe kaj FreeTube)", - "Export data as JSON": "Eksporti datumojn kiel JSON", - "Delete account?": "Ĉu forigi konton?", - "History": "Historio", - "An alternative front-end to YouTube": "Alternativa fasado al YouTube", - "JavaScript license information": "Ĝavoskripta licenca informo", - "source": "fonto", - "Login": "Ensaluti", - "Login/Register": "Ensaluti/Registriĝi", - "Login to Google": "Ensaluti al Google", - "User ID:": "Uzula identigilo:", - "Password:": "Pasvorto:", - "Time (h:mm:ss):": "Horo (h:mm:ss):", - "Text CAPTCHA": "Teksta CAPTCHA", - "Image CAPTCHA": "Bilda CAPTCHA", - "Sign In": "Ensaluti", - "Register": "Registriĝi", - "Email:": "Retpoŝto:", - "Google verification code:": "Kontrolkodo de Google:", - "Preferences": "Agordoj", - "Player preferences": "Spektilaj agordoj", - "Always loop: ": "Ĉiam ripeti: ", - "Autoplay: ": "Aŭtomate ludi: ", - "Autoplay next video: ": "Aŭtomate ludi sekvan videon: ", - "Listen by default: ": "Aŭskulti defaŭlte: ", - "Proxy videos? ": "Ĉu uzi prokuran servilon por videoj? ", - "Default speed: ": "Defaŭlta rapido: ", - "Preferred video quality: ": "Preferita videkvalito: ", - "Player volume: ": "Ludila sonforteco: ", - "Default comments: ": "Defaŭltaj komentoj: ", - "Default captions: ": "Defaŭltaj subtekstoj: ", - "Fallback captions: ": "Retrodefaŭltaj subtekstoj: ", - "Show related videos? ": "Ĉu montri rilatajn videojn? ", - "Visual preferences": "Vidaj preferoj", - "Dark mode: ": "Malhela reĝimo: ", - "Thin mode: ": "Maldika reĝimo: ", - "Subscription preferences": "Abonaj agordoj", - "Redirect homepage to feed: ": "Alidirekti hejmpâgon al fluo: ", - "Number of videos shown in feed: ": "Nombro da videoj montritaj en fluo: ", - "Sort videos by: ": "Ordi videojn laŭ: ", - "published": "publikigo", - "published - reverse": "publitigo - renverse", - "alphabetically": "alfabete", - "alphabetically - reverse": "alfabete - renverse", - "channel name": "kanala nombro", - "channel name - reverse": "kanala nombro - renverse", - "Only show latest video from channel: ": "Nur montri pli novan videon el kanalo: ", - "Only show latest unwatched video from channel: ": "Nur montri pli novan malviditan videon el kanalo: ", - "Only show unwatched: ": "Nur montri malviditajn: ", - "Only show notifications (if there are any): ": "Nur montri sciigojn (se estas): ", - "Data preferences": "Datumagordoj", - "Clear watch history": "Forigi vidohistorion", - "Import/Export data": "Importi/Eksporti datumojn", - "Manage subscriptions": "Administri abonojn", - "Watch history": "Vidohistorio", - "Delete account": "Forigi konton", - "Administrator preferences": "Agordoj de administranto", - "Default homepage: ": "Defaŭlta hejmpaĝo: ", - "Feed menu: ": "Flua menuo: ", - "Top enabled? ": "Ĉu pli bonaj ŝaltitaj? ", - "CAPTCHA enabled? ": "Ĉu CAPTCHA ŝaltita? ", - "Login enabled? ": "Ĉu ensaluto aktivita? ", - "Registration enabled? ": "Ĉu registriĝo aktivita? ", - "Report statistics? ": "Ĉu raporti statistikojn? ", - "Save preferences": "Konservi agordojn", - "Subscription manager": "Administrilo de abonoj", - "`x` subscriptions": "`x` abonoj", - "Import/Export": "Importi/Eksporti", - "unsubscribe": "malaboni", - "Subscriptions": "Abonoj", - "`x` unseen notifications": "`x` neviditaj sciigoj", - "search": "serĉi", - "Sign out": "Elsaluti", - "Released under the AGPLv3 by Omar Roth.": "Eldonita sub la AGPLv3 de Omar Roth.", - "Source available here.": "Fonto havebla ĉi tie.", - "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", - "View privacy policy.": "Vidi regularon pri privateco.", - "Trending": "", - "Unlisted": "", - "Watch video on Youtube": "Vidi videon en Youtube", - "Genre: ": "Ĝenro: ", - "License: ": "Licenco: ", - "Family friendly? ": "", - "Wilson score: ": "", - "Engagement: ": "", - "Whitelisted regions: ": "", - "Blacklisted regions: ": "", - "Shared `x`": "Konigita `x`", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", - "View YouTube comments": "Vidi komentojn de YouTube", - "View more comments on Reddit": "Vidi pli komentoj en Reddit", - "View `x` comments": "Vidi `x` komentojn", - "View Reddit comments": "Vidi komentojn de Reddit", - "Hide replies": "Kaŝi respondojn", - "Show replies": "Montri respondojn", - "Incorrect password": "Malbona pasvorto", - "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", - "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "Nevalida respondo", - "Invalid CAPTCHA": "Nevalida CAPTCHA", - "CAPTCHA is a required field": "", - "User ID is a required field": "", - "Password is a required field": "", - "Invalid username or password": "Nevalida uzantnomo aŭ pasvorto", - "Please sign in using 'Sign in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", - "Password cannot be empty": "", - "Password cannot be longer than 55 characters": "", - "Please sign in": "Bonvolu ensaluti", - "Invidious Private Feed for `x`": "", - "channel:`x`": "kanalo:`x`", - "Deleted or invalid channel": "Forigita aŭ nevalida kanalo", - "This channel does not exist.": "Ĉi tiu kanalo ne ekzistas.", - "Could not get channel info.": "", - "Could not fetch comments": "", - "View `x` replies": "Vidi `x` respondojn", - "`x` ago": "antaŭ `x`", - "Load more": "Ŝarĝi pli", - "`x` points": "`x` poentoj", - "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", - "Playlist does not exist.": "", - "Could not pull trending pages.": "", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "Nevalida uzanto", - "Token is expired, please try again": "", - "English": "Angla", - "English (auto-generated)": "Angla (aŭtomate generita)", - "Afrikaans": "Afrikansa", - "Albanian": "Albana", - "Amharic": "Amhara", - "Arabic": "Araba", - "Armenian": "Armena", - "Azerbaijani": "Azerbajĝana", - "Bangla": "Bengala", - "Basque": "Eŭska", - "Belarusian": "Belorusa", - "Bosnian": "Bosna", - "Bulgarian": "Bulgara", - "Burmese": "Birma", - "Catalan": "Kataluna", - "Cebuano": "Cebua", - "Chinese (Simplified)": "Ĉina (simpligita)", - "Chinese (Traditional)": "Ĉina (tradicia)", - "Corsican": "Korsika", - "Croatian": "Kroata", - "Czech": "Ĉeĥa", - "Danish": "Dana", - "Dutch": "Nederlanda", - "Esperanto": "Esperanto", - "Estonian": "Estona", - "Filipino": "Filipina", - "Finnish": "Finna", - "French": "Franca", - "Galician": "Galega", - "Georgian": "Kartvela", - "German": "Germana", - "Greek": "Greka", - "Gujarati": "Guĝarata", - "Haitian Creole": "Haitia kreola", - "Hausa": "Haŭsa", - "Hawaiian": "Havaja", - "Hebrew": "Hebrea", - "Hindi": "Hindia", - "Hmong": "Miaa", - "Hungarian": "Hungara", - "Icelandic": "Islanda", - "Igbo": "Igba", - "Indonesian": "Indonezia", - "Irish": "Irlanda", - "Italian": "Itala", - "Japanese": "Japana", - "Javanese": "Java", - "Kannada": "Kanara", - "Kazakh": "Kazaĥa", - "Khmer": "Kmera", - "Korean": "Korea", - "Kurdish": "Kurda", - "Kyrgyz": "Kirgiza", - "Lao": "Laosa", - "Latin": "Latina", - "Latvian": "Latva", - "Lithuanian": "Litova", - "Luxembourgish": "Luksemburga", - "Macedonian": "Makedona", - "Malagasy": "Malagasa", - "Malay": "Malaja", - "Malayalam": "Malajala", - "Maltese": "Malta", - "Maori": "Maoria", - "Marathi": "Marata", - "Mongolian": "Mongola", - "Nepali": "Nepala", - "Norwegian": "Norvega", - "Nyanja": "Njanĝa", - "Pashto": "Paŝtuna", - "Persian": "Persa", - "Polish": "Pola", - "Portuguese": "Portugala", - "Punjabi": "Panĝaba", - "Romanian": "Rumana", - "Russian": "Rusa", - "Samoan": "Samoa", - "Scottish Gaelic": "Skotgaela", - "Serbian": "Serba", - "Shona": "Ŝona", - "Sindhi": "Sinda", - "Sinhala": "Sinhala", - "Slovak": "Slovaka", - "Slovenian": "Slovena", - "Somali": "Somala", - "Southern Sotho": "Sota", - "Spanish": "Hispana", - "Spanish (Latin America)": "Hispana (Latinameriko)", - "Sundanese": "Sunda", - "Swahili": "Svahila", - "Swedish": "Sveda", - "Tajik": "Taĝika", - "Tamil": "Tamila", - "Telugu": "Telugua", - "Thai": "Taja", - "Turkish": "Turka", - "Ukrainian": "Ukraina", - "Urdu": "Urduo", - "Uzbek": "Uzbeka", - "Vietnamese": "Vjetnama", - "Welsh": "Kimra", - "Western Frisian": "Okcidentfrisa", - "Xhosa": "Kosa", - "Yiddish": "Jida", - "Yoruba": "Joruba", - "Zulu": "Zulua", - "`x` years": "`x` jaroj", - "`x` months": "`x` monatoj", - "`x` weeks": "`x` semajnoj", - "`x` days": "`x` tagoj", - "`x` hours": "`x` horoj", - "`x` minutes": "`x` minutoj", - "`x` seconds": "`x` sekundoj", - "Fallback comments: ": "", - "Popular": "Popularaj", - "Top": "Supraj", - "About": "Pri", - "Rating: ": "", - "Language: ": "Lingvo: ", - "Default": "Defaŭlte", - "Music": "Musiko", - "Gaming": "", - "News": "Novaĵoj", - "Movies": "Filmoj", - "Download": "Elŝuti", - "Download as: ": "Elŝuti kiel: ", - "%A %B %-d, %Y": "", - "(edited)": "(redaktita)", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "Videoj", - "Playlists": "", - "Current version: ": "Nuna versio: " + "`x` subscribers": "`x` abonantoj", + "`x` videos": "`x` videoj", + "LIVE": "NUNA", + "Shared `x` ago": "Konigita antaŭ `x`", + "Unsubscribe": "Malaboni", + "Subscribe": "Aboni", + "Login to subscribe to `x`": "Ensaluti por aboni je `x`", + "View channel on YouTube": "Vidi kanalon en YouTube", + "newest": "pli novaj", + "oldest": "pli malnovaj", + "popular": "popularaj", + "last": "lasta", + "Next page": "Sekva paĝo", + "Previous page": "Antaŭa paĝo", + "Clear watch history?": "Ĉu forigi vidohistorion?", + "Yes": "Jes", + "No": "Ne", + "Import and Export Data": "Importi kaj Eksporti Datumojn", + "Import": "Importi", + "Import Invidious data": "Importi datumojn de Invidious", + "Import YouTube subscriptions": "Importi abonojn de YouTube", + "Import FreeTube subscriptions (.db)": "Importi abonojn de FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importi abonojn de NewPipe (.json)", + "Import NewPipe data (.zip)": "Importi datumojn de NewPipe (.zip)", + "Export": "Eksporti", + "Export subscriptions as OPML": "Eksporti abonojn kiel OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporti abonojn kiel OPML (por NewPipe kaj FreeTube)", + "Export data as JSON": "Eksporti datumojn kiel JSON", + "Delete account?": "Ĉu forigi konton?", + "History": "Historio", + "An alternative front-end to YouTube": "Alternativa fasado al YouTube", + "JavaScript license information": "Ĝavoskripta licenca informo", + "source": "fonto", + "Login": "Ensaluti", + "Login/Register": "Ensaluti/Registriĝi", + "Login to Google": "Ensaluti al Google", + "User ID:": "Uzula identigilo:", + "Password:": "Pasvorto:", + "Time (h:mm:ss):": "Horo (h:mm:ss):", + "Text CAPTCHA": "Teksta CAPTCHA", + "Image CAPTCHA": "Bilda CAPTCHA", + "Sign In": "Ensaluti", + "Register": "Registriĝi", + "Email:": "Retpoŝto:", + "Google verification code:": "Kontrolkodo de Google:", + "Preferences": "Agordoj", + "Player preferences": "Spektilaj agordoj", + "Always loop: ": "Ĉiam ripeti: ", + "Autoplay: ": "Aŭtomate ludi: ", + "Autoplay next video: ": "Aŭtomate ludi sekvan videon: ", + "Listen by default: ": "Aŭskulti defaŭlte: ", + "Proxy videos? ": "Ĉu uzi prokuran servilon por videoj? ", + "Default speed: ": "Defaŭlta rapido: ", + "Preferred video quality: ": "Preferita videkvalito: ", + "Player volume: ": "Ludila sonforteco: ", + "Default comments: ": "Defaŭltaj komentoj: ", + "Default captions: ": "Defaŭltaj subtekstoj: ", + "Fallback captions: ": "Retrodefaŭltaj subtekstoj: ", + "Show related videos? ": "Ĉu montri rilatajn videojn? ", + "Visual preferences": "Vidaj preferoj", + "Dark mode: ": "Malhela reĝimo: ", + "Thin mode: ": "Maldika reĝimo: ", + "Subscription preferences": "Abonaj agordoj", + "Redirect homepage to feed: ": "Alidirekti hejmpâgon al fluo: ", + "Number of videos shown in feed: ": "Nombro da videoj montritaj en fluo: ", + "Sort videos by: ": "Ordi videojn laŭ: ", + "published": "publikigo", + "published - reverse": "publitigo - renverse", + "alphabetically": "alfabete", + "alphabetically - reverse": "alfabete - renverse", + "channel name": "kanala nombro", + "channel name - reverse": "kanala nombro - renverse", + "Only show latest video from channel: ": "Nur montri pli novan videon el kanalo: ", + "Only show latest unwatched video from channel: ": "Nur montri pli novan malviditan videon el kanalo: ", + "Only show unwatched: ": "Nur montri malviditajn: ", + "Only show notifications (if there are any): ": "Nur montri sciigojn (se estas): ", + "Data preferences": "Datumagordoj", + "Clear watch history": "Forigi vidohistorion", + "Import/Export data": "Importi/Eksporti datumojn", + "Manage subscriptions": "Administri abonojn", + "Watch history": "Vidohistorio", + "Delete account": "Forigi konton", + "Administrator preferences": "Agordoj de administranto", + "Default homepage: ": "Defaŭlta hejmpaĝo: ", + "Feed menu: ": "Flua menuo: ", + "Top enabled? ": "Ĉu pli bonaj ŝaltitaj? ", + "CAPTCHA enabled? ": "Ĉu CAPTCHA ŝaltita? ", + "Login enabled? ": "Ĉu ensaluto aktivita? ", + "Registration enabled? ": "Ĉu registriĝo aktivita? ", + "Report statistics? ": "Ĉu raporti statistikojn? ", + "Save preferences": "Konservi agordojn", + "Subscription manager": "Administrilo de abonoj", + "`x` subscriptions": "`x` abonoj", + "Import/Export": "Importi/Eksporti", + "unsubscribe": "malaboni", + "Subscriptions": "Abonoj", + "`x` unseen notifications": "`x` neviditaj sciigoj", + "search": "serĉi", + "Sign out": "Elsaluti", + "Released under the AGPLv3 by Omar Roth.": "Eldonita sub la AGPLv3 de Omar Roth.", + "Source available here.": "Fonto havebla ĉi tie.", + "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", + "View privacy policy.": "Vidi regularon pri privateco.", + "Trending": "", + "Unlisted": "", + "Watch video on Youtube": "Vidi videon en Youtube", + "Genre: ": "Ĝenro: ", + "License: ": "Licenco: ", + "Family friendly? ": "", + "Wilson score: ": "", + "Engagement: ": "", + "Whitelisted regions: ": "", + "Blacklisted regions: ": "", + "Shared `x`": "Konigita `x`", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", + "View YouTube comments": "Vidi komentojn de YouTube", + "View more comments on Reddit": "Vidi pli komentoj en Reddit", + "View `x` comments": "Vidi `x` komentojn", + "View Reddit comments": "Vidi komentojn de Reddit", + "Hide replies": "Kaŝi respondojn", + "Show replies": "Montri respondojn", + "Incorrect password": "Malbona pasvorto", + "Quota exceeded, try again in a few hours": "", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", + "Invalid TFA code": "", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "", + "Invalid answer": "Nevalida respondo", + "Invalid CAPTCHA": "Nevalida CAPTCHA", + "CAPTCHA is a required field": "", + "User ID is a required field": "", + "Password is a required field": "", + "Invalid username or password": "Nevalida uzantnomo aŭ pasvorto", + "Please sign in using 'Sign in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", + "Password cannot be empty": "", + "Password cannot be longer than 55 characters": "", + "Please sign in": "Bonvolu ensaluti", + "Invidious Private Feed for `x`": "", + "channel:`x`": "kanalo:`x`", + "Deleted or invalid channel": "Forigita aŭ nevalida kanalo", + "This channel does not exist.": "Ĉi tiu kanalo ne ekzistas.", + "Could not get channel info.": "", + "Could not fetch comments": "", + "View `x` replies": "Vidi `x` respondojn", + "`x` ago": "antaŭ `x`", + "Load more": "Ŝarĝi pli", + "`x` points": "`x` poentoj", + "Could not create mix.": "", + "Playlist is empty": "", + "Invalid playlist.": "", + "Playlist does not exist.": "", + "Could not pull trending pages.": "", + "Hidden field \"challenge\" is a required field": "", + "Hidden field \"token\" is a required field": "", + "Invalid challenge": "", + "Invalid token": "", + "Invalid user": "Nevalida uzanto", + "Token is expired, please try again": "", + "English": "Angla", + "English (auto-generated)": "Angla (aŭtomate generita)", + "Afrikaans": "Afrikansa", + "Albanian": "Albana", + "Amharic": "Amhara", + "Arabic": "Araba", + "Armenian": "Armena", + "Azerbaijani": "Azerbajĝana", + "Bangla": "Bengala", + "Basque": "Eŭska", + "Belarusian": "Belorusa", + "Bosnian": "Bosna", + "Bulgarian": "Bulgara", + "Burmese": "Birma", + "Catalan": "Kataluna", + "Cebuano": "Cebua", + "Chinese (Simplified)": "Ĉina (simpligita)", + "Chinese (Traditional)": "Ĉina (tradicia)", + "Corsican": "Korsika", + "Croatian": "Kroata", + "Czech": "Ĉeĥa", + "Danish": "Dana", + "Dutch": "Nederlanda", + "Esperanto": "Esperanto", + "Estonian": "Estona", + "Filipino": "Filipina", + "Finnish": "Finna", + "French": "Franca", + "Galician": "Galega", + "Georgian": "Kartvela", + "German": "Germana", + "Greek": "Greka", + "Gujarati": "Guĝarata", + "Haitian Creole": "Haitia kreola", + "Hausa": "Haŭsa", + "Hawaiian": "Havaja", + "Hebrew": "Hebrea", + "Hindi": "Hindia", + "Hmong": "Miaa", + "Hungarian": "Hungara", + "Icelandic": "Islanda", + "Igbo": "Igba", + "Indonesian": "Indonezia", + "Irish": "Irlanda", + "Italian": "Itala", + "Japanese": "Japana", + "Javanese": "Java", + "Kannada": "Kanara", + "Kazakh": "Kazaĥa", + "Khmer": "Kmera", + "Korean": "Korea", + "Kurdish": "Kurda", + "Kyrgyz": "Kirgiza", + "Lao": "Laosa", + "Latin": "Latina", + "Latvian": "Latva", + "Lithuanian": "Litova", + "Luxembourgish": "Luksemburga", + "Macedonian": "Makedona", + "Malagasy": "Malagasa", + "Malay": "Malaja", + "Malayalam": "Malajala", + "Maltese": "Malta", + "Maori": "Maoria", + "Marathi": "Marata", + "Mongolian": "Mongola", + "Nepali": "Nepala", + "Norwegian": "Norvega", + "Nyanja": "Njanĝa", + "Pashto": "Paŝtuna", + "Persian": "Persa", + "Polish": "Pola", + "Portuguese": "Portugala", + "Punjabi": "Panĝaba", + "Romanian": "Rumana", + "Russian": "Rusa", + "Samoan": "Samoa", + "Scottish Gaelic": "Skotgaela", + "Serbian": "Serba", + "Shona": "Ŝona", + "Sindhi": "Sinda", + "Sinhala": "Sinhala", + "Slovak": "Slovaka", + "Slovenian": "Slovena", + "Somali": "Somala", + "Southern Sotho": "Sota", + "Spanish": "Hispana", + "Spanish (Latin America)": "Hispana (Latinameriko)", + "Sundanese": "Sunda", + "Swahili": "Svahila", + "Swedish": "Sveda", + "Tajik": "Taĝika", + "Tamil": "Tamila", + "Telugu": "Telugua", + "Thai": "Taja", + "Turkish": "Turka", + "Ukrainian": "Ukraina", + "Urdu": "Urduo", + "Uzbek": "Uzbeka", + "Vietnamese": "Vjetnama", + "Welsh": "Kimra", + "Western Frisian": "Okcidentfrisa", + "Xhosa": "Kosa", + "Yiddish": "Jida", + "Yoruba": "Joruba", + "Zulu": "Zulua", + "`x` years": "`x` jaroj", + "`x` months": "`x` monatoj", + "`x` weeks": "`x` semajnoj", + "`x` days": "`x` tagoj", + "`x` hours": "`x` horoj", + "`x` minutes": "`x` minutoj", + "`x` seconds": "`x` sekundoj", + "Fallback comments: ": "", + "Popular": "Popularaj", + "Top": "Supraj", + "About": "Pri", + "Rating: ": "", + "Language: ": "Lingvo: ", + "Default": "Defaŭlte", + "Music": "Musiko", + "Gaming": "", + "News": "Novaĵoj", + "Movies": "Filmoj", + "Download": "Elŝuti", + "Download as: ": "Elŝuti kiel: ", + "%A %B %-d, %Y": "", + "(edited)": "(redaktita)", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "", + "Videos": "Videoj", + "Playlists": "", + "Current version: ": "Nuna versio: " } diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 1ef2f631..4af499d7 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1,295 +1,295 @@ { - "`x` subscribers": "`x` abonnenter", - "`x` videos": "`x` videoer", - "LIVE": "SANNTIDSVISNING", - "Shared `x` ago": "Delt for `x` siden", - "Unsubscribe": "Opphev abonnement", - "Subscribe": "Abonner", - "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", - "View channel on YouTube": "Vis kanal på YouTube", - "newest": "nyeste", - "oldest": "eldste", - "popular": "populært", - "last": "siste", - "Next page": "Neste side", - "Previous page": "Forrige side", - "Clear watch history?": "Tøm visningshistorikk?", - "Yes": "Ja", - "No": "Nei", - "Import and Export Data": "Importer- og eksporter data", - "Import": "Importer", - "Import Invidious data": "Importer Invidious-data", - "Import YouTube subscriptions": "Importer YouTube-abonnenter", - "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", - "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", - "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", - "Export": "Eksporter", - "Export subscriptions as OPML": "Eksporter abonnenter som OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", - "Export data as JSON": "Eksporter data som JSON", - "Delete account?": "Slett konto?", - "History": "Historikk", - "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", - "JavaScript license information": "JavaScript-lisensinformasjon", - "source": "kilde", - "Login": "Logg inn", - "Login/Register": "Logg inn/registrer", - "Login to Google": "Logg inn med Google", - "User ID:": "Bruker-ID:", - "Password:": "Passord:", - "Time (h:mm:ss):": "Tid (h:mm:ss):", - "Text CAPTCHA": "Tekst-CAPTCHA", - "Image CAPTCHA": "Bilde-CAPTCHA", - "Sign In": "Innlogging", - "Register": "Registrer", - "Email:": "E-post:", - "Google verification code:": "Google-bekreftelseskode:", - "Preferences": "Innstillinger", - "Player preferences": "Avspillerinnstillinger", - "Always loop: ": "Alltid gjenta: ", - "Autoplay: ": "Autoavspilling: ", - "Autoplay next video: ": "Autospill neste video: ", - "Listen by default: ": "Lytt som forvalg: ", - "Proxy videos? ": "Mellomtjen videoer? ", - "Default speed: ": "Forvalgt hastighet: ", - "Preferred video quality: ": "Foretrukket videokvalitet: ", - "Player volume: ": "Avspillerlydstyrke: ", - "Default comments: ": "Forvalgte kommentarer: ", - "Default captions: ": "Forvalgte undertitler: ", - "Fallback captions: ": "Tilbakefallsundertitler: ", - "Show related videos? ": "Vis relaterte videoer? ", - "Visual preferences": "Visuelle innstillinger", - "Dark mode: ": "Mørk drakt: ", - "Thin mode: ": "Tynt modus: ", - "Subscription preferences": "Abonnementsinnstillinger", - "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", - "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", - "Sort videos by: ": "Sorter videoer etter: ", - "published": "publisert", - "published - reverse": "publisert - motsatt", - "alphabetically": "alfabetisk", - "alphabetically - reverse": "alfabetisk - motsatt", - "channel name": "kanalnavn", - "channel name - reverse": "kanalnavn - motsatt", - "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", - "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", - "Only show unwatched: ": "Kun vis usette: ", - "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", - "Data preferences": "Datainnstillinger", - "Clear watch history": "Tøm visningshistorikk", - "Import/Export data": "Importer/eksporter data", - "Manage subscriptions": "Behandle abonnementer", - "Watch history": "Visningshistorikk", - "Delete account": "Slett konto", - "Administrator preferences": "Administratorinnstillinger", - "Default homepage: ": "Forvalgt hjemmeside: ", - "Feed menu: ": "Flyt-meny: ", - "Top enabled? ": "Topp påskrudd? ", - "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", - "Login enabled? ": "Innlogging påskrudd? ", - "Registration enabled? ": "Registrering påskrudd? ", - "Report statistics? ": "Innrapporter statistikk? ", - "Save preferences": "Lagre innstillinger", - "Subscription manager": "Abonnementsbehandler", - "`x` subscriptions": "`x` abonnementer", - "Import/Export": "Importer/eksporter", - "unsubscribe": "opphev abonnement", - "Subscriptions": "Abonnement", - "`x` unseen notifications": "`x` usette merknader", - "search": "søk", - "Sign out": "Logg ut", - "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", - "Source available here.": "Kildekode tilgjengelig her.", - "View JavaScript license information.": "Vis JavaScript-lisensinfo.", - "View privacy policy.": "Vis personvernspraksis.", - "Trending": "Trendsettende", - "Unlisted": "Ulistet", - "Watch video on Youtube": "Vis video på YouTube", - "Genre: ": "Sjanger: ", - "License: ": "Lisens: ", - "Family friendly? ": "Familievennlig? ", - "Wilson score: ": "Wilson-poengsum: ", - "Engagement: ": "Engasjement: ", - "Whitelisted regions: ": "Hvitlistede regioner: ", - "Blacklisted regions: ": "Svartelistede regioner: ", - "Shared `x`": "Delt `x`", - "Premieres in `x`": "Premiere om `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", - "View YouTube comments": "Vis YouTube-kommentarer", - "View more comments on Reddit": "Vis flere kommenterer på Reddit", - "View `x` comments": "Vis `x` kommentarer", - "View Reddit comments": "Vis Reddit-kommentarer", - "Hide replies": "Skjul svar", - "Show replies": "Vis svar", - "Incorrect password": "Feil passord", - "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", - "Invalid TFA code": "Ugyldig tofaktorkode", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", - "Invalid answer": "Ugyldig svar", - "Invalid CAPTCHA": "Ugyldig CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", - "User ID is a required field": "Bruker-ID er et påkrevd felt", - "Password is a required field": "Passord er et påkrevd felt", - "Invalid username or password": "Ugyldig brukernavn eller passord", - "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", - "Password cannot be empty": "Passordet kan ikke være tomt", - "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", - "Please sign in": "Logg inn", - "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", - "channel:`x`": "kanal `x`", - "Deleted or invalid channel": "Slettet eller ugyldig kanal", - "This channel does not exist.": "Denne kanalen finnes ikke.", - "Could not get channel info.": "Kunne ikke innhente kanalinfo.", - "Could not fetch comments": "Kunne ikke hente kommentarer", - "View `x` replies": "Vis `x` svar", - "`x` ago": "`x` siden", - "Load more": "Last inn flere", - "`x` points": "`x` poeng", - "Could not create mix.": "Kunne ikke opprette miks.", - "Playlist is empty": "Spillelisten er tom", - "Invalid playlist.": "Ugyldig spilleliste.", - "Playlist does not exist.": "Spillelisten finnes ikke.", - "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", - "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", - "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", - "Invalid challenge": "Ugyldig utfordring", - "Invalid token": "Ugyldig symbol", - "Invalid user": "Ugyldig bruker", - "Token is expired, please try again": "Symbol utløpt, prøv igjen", - "English": "Engelsk", - "English (auto-generated)": "Engelsk (auto-generert)", - "Afrikaans": "", - "Albanian": "Albansk", - "Amharic": "", - "Arabic": "Arabisk", - "Armenian": "Armensk", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "Hviterussisk", - "Bosnian": "Bosnisk", - "Bulgarian": "Bulgarsk", - "Burmese": "Burmesisk", - "Catalan": "Katalansk", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "Tsjekkisk", - "Danish": "Dansk", - "Dutch": "", - "Esperanto": "Esperanto", - "Estonian": "", - "Filipino": "", - "Finnish": "Finsk", - "French": "Fransk", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "Ungarsk", - "Icelandic": "Islandsk", - "Igbo": "", - "Indonesian": "Indonesisk", - "Irish": "Irsk", - "Italian": "Italiensk", - "Japanese": "Japansk", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "Norsk bokmål", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "Russisk", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "Serbisk", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "Slovakisk", - "Slovenian": "Slovensk", - "Somali": "Somali", - "Southern Sotho": "", - "Spanish": "Spansk", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "Svensk", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "Tyrkisk", - "Ukrainian": "Ukrainsk", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "Vietnamesisk", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` år", - "`x` months": "`x` måneder", - "`x` weeks": "`x` uker", - "`x` days": "`x` dager", - "`x` hours": "`x` timer", - "`x` minutes": "`x` minutter", - "`x` seconds": "`x` sekunder", - "Fallback comments: ": "Tilbakefallskommentarer: ", - "Popular": "Pupulært", - "Top": "Topp", - "About": "Om", - "Rating: ": "Vurdering: ", - "Language: ": "Språk: ", - "Default": "Forvalg", - "Music": "Musikk", - "Gaming": "Spill", - "News": "Nyheter", - "Movies": "Filmer", - "Download": "Last ned", - "Download as: ": "Last ned som: ", - "%A %B %-d, %Y": "", - "(edited)": "(redigert)", - "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", - "`x` marked it with a ❤": "`x` levnet et ❤", - "Audio mode": "Lydmodus", - "Video mode": "Video-modus", - "Videos": "Videoer", - "Playlists": "Spillelister", - "Current version: ": "Nåværende versjon: " + "`x` subscribers": "`x` abonnenter", + "`x` videos": "`x` videoer", + "LIVE": "SANNTIDSVISNING", + "Shared `x` ago": "Delt for `x` siden", + "Unsubscribe": "Opphev abonnement", + "Subscribe": "Abonner", + "Login to subscribe to `x`": "Logg inn for å abonnere på `x`", + "View channel on YouTube": "Vis kanal på YouTube", + "newest": "nyeste", + "oldest": "eldste", + "popular": "populært", + "last": "siste", + "Next page": "Neste side", + "Previous page": "Forrige side", + "Clear watch history?": "Tøm visningshistorikk?", + "Yes": "Ja", + "No": "Nei", + "Import and Export Data": "Importer- og eksporter data", + "Import": "Importer", + "Import Invidious data": "Importer Invidious-data", + "Import YouTube subscriptions": "Importer YouTube-abonnenter", + "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", + "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", + "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", + "Export": "Eksporter", + "Export subscriptions as OPML": "Eksporter abonnenter som OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", + "Export data as JSON": "Eksporter data som JSON", + "Delete account?": "Slett konto?", + "History": "Historikk", + "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", + "JavaScript license information": "JavaScript-lisensinformasjon", + "source": "kilde", + "Login": "Logg inn", + "Login/Register": "Logg inn/registrer", + "Login to Google": "Logg inn med Google", + "User ID:": "Bruker-ID:", + "Password:": "Passord:", + "Time (h:mm:ss):": "Tid (h:mm:ss):", + "Text CAPTCHA": "Tekst-CAPTCHA", + "Image CAPTCHA": "Bilde-CAPTCHA", + "Sign In": "Innlogging", + "Register": "Registrer", + "Email:": "E-post:", + "Google verification code:": "Google-bekreftelseskode:", + "Preferences": "Innstillinger", + "Player preferences": "Avspillerinnstillinger", + "Always loop: ": "Alltid gjenta: ", + "Autoplay: ": "Autoavspilling: ", + "Autoplay next video: ": "Autospill neste video: ", + "Listen by default: ": "Lytt som forvalg: ", + "Proxy videos? ": "Mellomtjen videoer? ", + "Default speed: ": "Forvalgt hastighet: ", + "Preferred video quality: ": "Foretrukket videokvalitet: ", + "Player volume: ": "Avspillerlydstyrke: ", + "Default comments: ": "Forvalgte kommentarer: ", + "Default captions: ": "Forvalgte undertitler: ", + "Fallback captions: ": "Tilbakefallsundertitler: ", + "Show related videos? ": "Vis relaterte videoer? ", + "Visual preferences": "Visuelle innstillinger", + "Dark mode: ": "Mørk drakt: ", + "Thin mode: ": "Tynt modus: ", + "Subscription preferences": "Abonnementsinnstillinger", + "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", + "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", + "Sort videos by: ": "Sorter videoer etter: ", + "published": "publisert", + "published - reverse": "publisert - motsatt", + "alphabetically": "alfabetisk", + "alphabetically - reverse": "alfabetisk - motsatt", + "channel name": "kanalnavn", + "channel name - reverse": "kanalnavn - motsatt", + "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", + "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", + "Only show unwatched: ": "Kun vis usette: ", + "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", + "Data preferences": "Datainnstillinger", + "Clear watch history": "Tøm visningshistorikk", + "Import/Export data": "Importer/eksporter data", + "Manage subscriptions": "Behandle abonnementer", + "Watch history": "Visningshistorikk", + "Delete account": "Slett konto", + "Administrator preferences": "Administratorinnstillinger", + "Default homepage: ": "Forvalgt hjemmeside: ", + "Feed menu: ": "Flyt-meny: ", + "Top enabled? ": "Topp påskrudd? ", + "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", + "Login enabled? ": "Innlogging påskrudd? ", + "Registration enabled? ": "Registrering påskrudd? ", + "Report statistics? ": "Innrapporter statistikk? ", + "Save preferences": "Lagre innstillinger", + "Subscription manager": "Abonnementsbehandler", + "`x` subscriptions": "`x` abonnementer", + "Import/Export": "Importer/eksporter", + "unsubscribe": "opphev abonnement", + "Subscriptions": "Abonnement", + "`x` unseen notifications": "`x` usette merknader", + "search": "søk", + "Sign out": "Logg ut", + "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", + "Source available here.": "Kildekode tilgjengelig her.", + "View JavaScript license information.": "Vis JavaScript-lisensinfo.", + "View privacy policy.": "Vis personvernspraksis.", + "Trending": "Trendsettende", + "Unlisted": "Ulistet", + "Watch video on Youtube": "Vis video på YouTube", + "Genre: ": "Sjanger: ", + "License: ": "Lisens: ", + "Family friendly? ": "Familievennlig? ", + "Wilson score: ": "Wilson-poengsum: ", + "Engagement: ": "Engasjement: ", + "Whitelisted regions: ": "Hvitlistede regioner: ", + "Blacklisted regions: ": "Svartelistede regioner: ", + "Shared `x`": "Delt `x`", + "Premieres in `x`": "Premiere om `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", + "View YouTube comments": "Vis YouTube-kommentarer", + "View more comments on Reddit": "Vis flere kommenterer på Reddit", + "View `x` comments": "Vis `x` kommentarer", + "View Reddit comments": "Vis Reddit-kommentarer", + "Hide replies": "Skjul svar", + "Show replies": "Vis svar", + "Incorrect password": "Feil passord", + "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", + "Invalid TFA code": "Ugyldig tofaktorkode", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", + "Invalid answer": "Ugyldig svar", + "Invalid CAPTCHA": "Ugyldig CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", + "User ID is a required field": "Bruker-ID er et påkrevd felt", + "Password is a required field": "Passord er et påkrevd felt", + "Invalid username or password": "Ugyldig brukernavn eller passord", + "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", + "Password cannot be empty": "Passordet kan ikke være tomt", + "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", + "Please sign in": "Logg inn", + "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", + "channel:`x`": "kanal `x`", + "Deleted or invalid channel": "Slettet eller ugyldig kanal", + "This channel does not exist.": "Denne kanalen finnes ikke.", + "Could not get channel info.": "Kunne ikke innhente kanalinfo.", + "Could not fetch comments": "Kunne ikke hente kommentarer", + "View `x` replies": "Vis `x` svar", + "`x` ago": "`x` siden", + "Load more": "Last inn flere", + "`x` points": "`x` poeng", + "Could not create mix.": "Kunne ikke opprette miks.", + "Playlist is empty": "Spillelisten er tom", + "Invalid playlist.": "Ugyldig spilleliste.", + "Playlist does not exist.": "Spillelisten finnes ikke.", + "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", + "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", + "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", + "Invalid challenge": "Ugyldig utfordring", + "Invalid token": "Ugyldig symbol", + "Invalid user": "Ugyldig bruker", + "Token is expired, please try again": "Symbol utløpt, prøv igjen", + "English": "Engelsk", + "English (auto-generated)": "Engelsk (auto-generert)", + "Afrikaans": "", + "Albanian": "Albansk", + "Amharic": "", + "Arabic": "Arabisk", + "Armenian": "Armensk", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "Hviterussisk", + "Bosnian": "Bosnisk", + "Bulgarian": "Bulgarsk", + "Burmese": "Burmesisk", + "Catalan": "Katalansk", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "Tsjekkisk", + "Danish": "Dansk", + "Dutch": "", + "Esperanto": "Esperanto", + "Estonian": "", + "Filipino": "", + "Finnish": "Finsk", + "French": "Fransk", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "Ungarsk", + "Icelandic": "Islandsk", + "Igbo": "", + "Indonesian": "Indonesisk", + "Irish": "Irsk", + "Italian": "Italiensk", + "Japanese": "Japansk", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "Norsk bokmål", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "Russisk", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "Serbisk", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "Slovakisk", + "Slovenian": "Slovensk", + "Somali": "Somali", + "Southern Sotho": "", + "Spanish": "Spansk", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "Svensk", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "Tyrkisk", + "Ukrainian": "Ukrainsk", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "Vietnamesisk", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` år", + "`x` months": "`x` måneder", + "`x` weeks": "`x` uker", + "`x` days": "`x` dager", + "`x` hours": "`x` timer", + "`x` minutes": "`x` minutter", + "`x` seconds": "`x` sekunder", + "Fallback comments: ": "Tilbakefallskommentarer: ", + "Popular": "Pupulært", + "Top": "Topp", + "About": "Om", + "Rating: ": "Vurdering: ", + "Language: ": "Språk: ", + "Default": "Forvalg", + "Music": "Musikk", + "Gaming": "Spill", + "News": "Nyheter", + "Movies": "Filmer", + "Download": "Last ned", + "Download as: ": "Last ned som: ", + "%A %B %-d, %Y": "", + "(edited)": "(redigert)", + "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", + "`x` marked it with a ❤": "`x` levnet et ❤", + "Audio mode": "Lydmodus", + "Video mode": "Video-modus", + "Videos": "Videoer", + "Playlists": "Spillelister", + "Current version: ": "Nåværende versjon: " } diff --git a/locales/ru.json b/locales/ru.json index 76ec4f8e..25ef0ee2 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,297 +1,297 @@ { - "`x` subscribers": "`x` подписчиков", - "`x` videos": "`x` видео", - "LIVE": "ПРЯМОЙ ЭФИР", - "Shared `x` ago": "Опубликовано `x` назад", - "Unsubscribe": "Отписаться", - "Subscribe": "Подписаться", - "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", - "View channel on YouTube": "Канал на YouTube", - "newest": "новые", - "oldest": "старые", - "popular": "популярные", - "last": "недавно обновленные", - "Next page": "Следующая страница", - "Previous page": "Предыдущая страница", - "Clear watch history?": "Очистить историю просмотров?", - "Yes": "Да", - "No": "Нет", - "Import and Export Data": "Импорт и экспорт данных", - "Import": "Импорт", - "Import Invidious data": "Импортировать данные Invidious", - "Import YouTube subscriptions": "Импортировать YouTube подписки", - "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", - "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", - "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", - "Export": "Экспорт", - "Export subscriptions as OPML": "Экспортировать подписки в OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", - "Export data as JSON": "Экспортировать данные в JSON", - "Delete account?": "Удалить аккаунт?", - "History": "История", - "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", - "JavaScript license information": "Лицензии JavaScript", - "source": "источник", - "Login": "Войти", - "Login/Register": "Войти/Регистрация", - "Login to Google": "Войти через Google", - "User ID:": "ID пользователя:", - "Password:": "Пароль:", - "Time (h:mm:ss):": "Время (ч:мм:сс):", - "Text CAPTCHA": "Текст капчи", - "Image CAPTCHA": "Изображение капчи", - "Sign In": "Войти", - "Register": "Регистрация", - "Email:": "Эл. почта:", - "Google verification code:": "Код подтверждения Google:", - "Preferences": "Настройки", - "Player preferences": "Настройки проигрывателя", - "Always loop: ": "Всегда повторять: ", - "Autoplay: ": "Автовоспроизведение: ", - "Autoplay next video: ": "Автовоспроизведение следующего видео: ", - "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", - "Proxy videos? ": "Проксировать видео? ", - "Default speed: ": "Скорость по умолчанию: ", - "Preferred video quality: ": "Предпочтительное качество видео: ", - "Player volume: ": "Громкость воспроизведения: ", - "Default comments: ": "Источник комментариев: ", - "youtube": "YouTube", - "reddit": "Reddit", - "Default captions: ": "Субтитры по умолчанию: ", - "Fallback captions: ": "Резервные субтитры: ", - "Show related videos? ": "Показывать похожие видео? ", - "Visual preferences": "Визуальные настройки", - "Dark mode: ": "Темная тема: ", - "Thin mode: ": "Облегченный режим: ", - "Subscription preferences": "Настройки подписок", - "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", - "Number of videos shown in feed: ": "Число видео в ленте: ", - "Sort videos by: ": "Сортировать видео по: ", - "published": "дате публикации", - "published - reverse": "дате - обратный порядок", - "alphabetically": "алфавиту", - "alphabetically - reverse": "алфавиту - обратный порядок", - "channel name": "имени канала", - "channel name - reverse": "имени канала - обратный порядок", - "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", - "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", - "Only show unwatched: ": "Отображать только непросмотренные видео: ", - "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", - "Data preferences": "Настройки данных", - "Clear watch history": "Очистить историю просмотра", - "Import/Export data": "Импорт/Экспорт данных", - "Manage subscriptions": "Управление подписками", - "Watch history": "История просмотров", - "Delete account": "Удалить аккаунт", - "Administrator preferences": "Настройки администратора", - "Default homepage: ": "Главная страница по умолчанию: ", - "Feed menu: ": "Меню ленты: ", - "Top enabled? ": "Включить топ? ", - "CAPTCHA enabled? ": "Включить капчу? ", - "Login enabled? ": "Включить логин? ", - "Registration enabled? ": "Включить регистрацию? ", - "Report statistics? ": "Отображать статистику? ", - "Save preferences": "Сохранить настройки", - "Subscription manager": "Менеджер подписок", - "`x` subscriptions": "`x` подписок", - "Import/Export": "Импорт/Экспорт", - "unsubscribe": "отписаться", - "Subscriptions": "Подписки", - "`x` unseen notifications": "`x` новых оповещений", - "search": "поиск", - "Sign out": "Выйти", - "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", - "Source available here.": "Исходный код доступен здесь.", - "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", - "View privacy policy.": "См. политику конфиденциальности.", - "Trending": "В тренде", - "Unlisted": "Доступно по ссылке", - "Watch video on Youtube": "Смотреть на YouTube", - "Genre: ": "Жанр: ", - "License: ": "Лицензия: ", - "Family friendly? ": "Семейный просмотр: ", - "Wilson score: ": "Рейтинг Уилсона: ", - "Engagement: ": "Вовлеченность: ", - "Whitelisted regions: ": "Доступно для: ", - "Blacklisted regions: ": "Недоступно для: ", - "Shared `x`": "Опубликовано `x`", - "Premieres in `x`": "Премьера через `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", - "View YouTube comments": "Смотреть комментарии с YouTube", - "View more comments on Reddit": "Больше комментариев на Reddit", - "View `x` comments": "Показать `x` комментариев", - "View Reddit comments": "Смотреть комментарии с Reddit", - "Hide replies": "Скрыть ответы", - "Show replies": "Показать ответы", - "Incorrect password": "Неправильный пароль", - "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", - "Invalid TFA code": "Неправильный TFA код", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", - "Invalid answer": "Неверный ответ", - "Invalid CAPTCHA": "Неверная капча", - "CAPTCHA is a required field": "Необходимо ввести капчу", - "User ID is a required field": "Необходимо ввести идентификатор пользователя", - "Password is a required field": "Необходимо ввести пароль", - "Invalid username or password": "Недопустимый пароль или имя пользователя", - "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", - "Password cannot be empty": "Пароль не может быть пустым", - "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", - "Please sign in": "Пожалуйста, войдите", - "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", - "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удален или не найден", - "This channel does not exist.": "Такой канал не существует.", - "Could not get channel info.": "Невозможно получить информацию о канале.", - "Could not fetch comments": "Невозможно получить комментарии", - "View `x` replies": "Показать `x` ответов", - "`x` ago": "`x` назад", - "Load more": "Загрузить больше", - "`x` points": "`x` очков", - "Could not create mix.": "Невозможно создать \"микс\".", - "Playlist is empty": "Плейлист пуст", - "Invalid playlist.": "Некорректный плейлист.", - "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", - "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", - "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", - "Invalid challenge": "Неправильный ответ в \"challenge\"", - "Invalid token": "Неправильный токен", - "Invalid user": "Недопустимое имя пользователя", - "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", - "English": "Английский", - "English (auto-generated)": "Английский (созданы автоматически)", - "Afrikaans": "Африкаанс", - "Albanian": "Албанский", - "Amharic": "Амхарский", - "Arabic": "Арабский", - "Armenian": "Армянский", - "Azerbaijani": "Азербайджанский", - "Bangla": "Бенгальский", - "Basque": "Баскский", - "Belarusian": "Белорусский", - "Bosnian": "Боснийский", - "Bulgarian": "Болгарский", - "Burmese": "Бирманский", - "Catalan": "Каталонский", - "Cebuano": "Себуанский", - "Chinese (Simplified)": "Китайский (упрощенный)", - "Chinese (Traditional)": "Китайский (традиционный)", - "Corsican": "Корсиканский", - "Croatian": "Хорватский", - "Czech": "Чешский", - "Danish": "Датский", - "Dutch": "Нидерландский", - "Esperanto": "Эсперанто", - "Estonian": "Эстонский", - "Filipino": "Филиппинский", - "Finnish": "Финский", - "French": "Французский", - "Galician": "Галисийский", - "Georgian": "Грузинский", - "German": "Немецкий", - "Greek": "Греческий", - "Gujarati": "Гуджаратский", - "Haitian Creole": "Гаит. креольский", - "Hausa": "Хауса", - "Hawaiian": "Гавайский", - "Hebrew": "Иврит", - "Hindi": "Хинди", - "Hmong": "Хмонг (мяо)", - "Hungarian": "Венгерский", - "Icelandic": "Исландский", - "Igbo": "Игбо", - "Indonesian": "Индонезийский", - "Irish": "Ирландский", - "Italian": "Итальянский", - "Japanese": "Японский", - "Javanese": "Яванский", - "Kannada": "Каннада", - "Kazakh": "Казахский", - "Khmer": "Кхмерский", - "Korean": "Корейский", - "Kurdish": "Курдский", - "Kyrgyz": "Киргизский", - "Lao": "Лаосский", - "Latin": "Латинский", - "Latvian": "Латышский", - "Lithuanian": "Литовский", - "Luxembourgish": "Люксембургский", - "Macedonian": "Македонский", - "Malagasy": "Малагасийский", - "Malay": "Малайский", - "Malayalam": "Малаялам", - "Maltese": "Мальтийский", - "Maori": "Маори", - "Marathi": "Маратхи", - "Mongolian": "Монгольская", - "Nepali": "Непальский", - "Norwegian": "Норвежский", - "Nyanja": "Ньянджа", - "Pashto": "Пушту", - "Persian": "Персидский", - "Polish": "Польский", - "Portuguese": "Португальский", - "Punjabi": "Панджаби", - "Romanian": "Румынский", - "Russian": "Русский", - "Samoan": "Самоанский", - "Scottish Gaelic": "Шотландский (гэльский)", - "Serbian": "Сербский", - "Shona": "Шона", - "Sindhi": "Синдхи", - "Sinhala": "Сингальский", - "Slovak": "Словацкий", - "Slovenian": "Словенский", - "Somali": "Сомалийский", - "Southern Sotho": "Сесото (южный сото)", - "Spanish": "Испанский", - "Spanish (Latin America)": "Испанский (Латинская Америка)", - "Sundanese": "Сунданский", - "Swahili": "Суахили", - "Swedish": "Шведский", - "Tajik": "Таджикский", - "Tamil": "Тамильский", - "Telugu": "Телугу", - "Thai": "Тайский", - "Turkish": "Турецкий", - "Ukrainian": "Украинский", - "Urdu": "Урду", - "Uzbek": "Узбекский", - "Vietnamese": "Вьетнамский", - "Welsh": "Валлийский", - "Western Frisian": "Западнофризский", - "Xhosa": "Коса", - "Yiddish": "Идиш", - "Yoruba": "Йоруба", - "Zulu": "Зулусский", - "`x` years": "`x` лет", - "`x` months": "`x` месяцев", - "`x` weeks": "`x` недель", - "`x` days": "`x` дней", - "`x` hours": "`x` часов", - "`x` minutes": "`x` минут", - "`x` seconds": "`x` секунд", - "Fallback comments: ": "Резервные комментарии: ", - "Popular": "Популярное", - "Top": "Топ", - "About": "О сайте", - "Rating: ": "Рейтинг: ", - "Language: ": "Язык: ", - "Default": "По-умолчанию", - "Music": "Музыка", - "Gaming": "Игры", - "News": "Новости", - "Movies": "Фильмы", - "Download": "Скачать", - "Download as: ": "Скачать как: ", - "%A %B %-d, %Y": "%-d %B %Y, %A", - "(edited)": "(изменено)", - "Youtube permalink of the comment": "Прямая ссылка на YouTube", - "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", - "Audio mode": "Аудио режим", - "Video mode": "Видео режим", - "Videos": "Видео", - "Playlists": "Плейлисты", - "Current version: ": "Текущая версия: " + "`x` subscribers": "`x` подписчиков", + "`x` videos": "`x` видео", + "LIVE": "ПРЯМОЙ ЭФИР", + "Shared `x` ago": "Опубликовано `x` назад", + "Unsubscribe": "Отписаться", + "Subscribe": "Подписаться", + "Login to subscribe to `x`": "Войти, чтобы подписаться на `x`", + "View channel on YouTube": "Канал на YouTube", + "newest": "новые", + "oldest": "старые", + "popular": "популярные", + "last": "недавно обновленные", + "Next page": "Следующая страница", + "Previous page": "Предыдущая страница", + "Clear watch history?": "Очистить историю просмотров?", + "Yes": "Да", + "No": "Нет", + "Import and Export Data": "Импорт и экспорт данных", + "Import": "Импорт", + "Import Invidious data": "Импортировать данные Invidious", + "Import YouTube subscriptions": "Импортировать YouTube подписки", + "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", + "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", + "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", + "Export": "Экспорт", + "Export subscriptions as OPML": "Экспортировать подписки в OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", + "Export data as JSON": "Экспортировать данные в JSON", + "Delete account?": "Удалить аккаунт?", + "History": "История", + "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", + "JavaScript license information": "Лицензии JavaScript", + "source": "источник", + "Login": "Войти", + "Login/Register": "Войти/Регистрация", + "Login to Google": "Войти через Google", + "User ID:": "ID пользователя:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Время (ч:мм:сс):", + "Text CAPTCHA": "Текст капчи", + "Image CAPTCHA": "Изображение капчи", + "Sign In": "Войти", + "Register": "Регистрация", + "Email:": "Эл. почта:", + "Google verification code:": "Код подтверждения Google:", + "Preferences": "Настройки", + "Player preferences": "Настройки проигрывателя", + "Always loop: ": "Всегда повторять: ", + "Autoplay: ": "Автовоспроизведение: ", + "Autoplay next video: ": "Автовоспроизведение следующего видео: ", + "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", + "Proxy videos? ": "Проксировать видео? ", + "Default speed: ": "Скорость по умолчанию: ", + "Preferred video quality: ": "Предпочтительное качество видео: ", + "Player volume: ": "Громкость воспроизведения: ", + "Default comments: ": "Источник комментариев: ", + "youtube": "YouTube", + "reddit": "Reddit", + "Default captions: ": "Субтитры по умолчанию: ", + "Fallback captions: ": "Резервные субтитры: ", + "Show related videos? ": "Показывать похожие видео? ", + "Visual preferences": "Визуальные настройки", + "Dark mode: ": "Темная тема: ", + "Thin mode: ": "Облегченный режим: ", + "Subscription preferences": "Настройки подписок", + "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", + "Number of videos shown in feed: ": "Число видео в ленте: ", + "Sort videos by: ": "Сортировать видео по: ", + "published": "дате публикации", + "published - reverse": "дате - обратный порядок", + "alphabetically": "алфавиту", + "alphabetically - reverse": "алфавиту - обратный порядок", + "channel name": "имени канала", + "channel name - reverse": "имени канала - обратный порядок", + "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", + "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", + "Only show unwatched: ": "Отображать только непросмотренные видео: ", + "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", + "Data preferences": "Настройки данных", + "Clear watch history": "Очистить историю просмотра", + "Import/Export data": "Импорт/Экспорт данных", + "Manage subscriptions": "Управление подписками", + "Watch history": "История просмотров", + "Delete account": "Удалить аккаунт", + "Administrator preferences": "Настройки администратора", + "Default homepage: ": "Главная страница по умолчанию: ", + "Feed menu: ": "Меню ленты: ", + "Top enabled? ": "Включить топ? ", + "CAPTCHA enabled? ": "Включить капчу? ", + "Login enabled? ": "Включить логин? ", + "Registration enabled? ": "Включить регистрацию? ", + "Report statistics? ": "Отображать статистику? ", + "Save preferences": "Сохранить настройки", + "Subscription manager": "Менеджер подписок", + "`x` subscriptions": "`x` подписок", + "Import/Export": "Импорт/Экспорт", + "unsubscribe": "отписаться", + "Subscriptions": "Подписки", + "`x` unseen notifications": "`x` новых оповещений", + "search": "поиск", + "Sign out": "Выйти", + "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", + "Source available here.": "Исходный код доступен здесь.", + "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", + "View privacy policy.": "См. политику конфиденциальности.", + "Trending": "В тренде", + "Unlisted": "Доступно по ссылке", + "Watch video on Youtube": "Смотреть на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Лицензия: ", + "Family friendly? ": "Семейный просмотр: ", + "Wilson score: ": "Рейтинг Уилсона: ", + "Engagement: ": "Вовлеченность: ", + "Whitelisted regions: ": "Доступно для: ", + "Blacklisted regions: ": "Недоступно для: ", + "Shared `x`": "Опубликовано `x`", + "Premieres in `x`": "Премьера через `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", + "View YouTube comments": "Смотреть комментарии с YouTube", + "View more comments on Reddit": "Больше комментариев на Reddit", + "View `x` comments": "Показать `x` комментариев", + "View Reddit comments": "Смотреть комментарии с Reddit", + "Hide replies": "Скрыть ответы", + "Show replies": "Показать ответы", + "Incorrect password": "Неправильный пароль", + "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", + "Invalid TFA code": "Неправильный TFA код", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", + "Invalid answer": "Неверный ответ", + "Invalid CAPTCHA": "Неверная капча", + "CAPTCHA is a required field": "Необходимо ввести капчу", + "User ID is a required field": "Необходимо ввести идентификатор пользователя", + "Password is a required field": "Необходимо ввести пароль", + "Invalid username or password": "Недопустимый пароль или имя пользователя", + "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", + "Password cannot be empty": "Пароль не может быть пустым", + "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", + "Please sign in": "Пожалуйста, войдите", + "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал удален или не найден", + "This channel does not exist.": "Такой канал не существует.", + "Could not get channel info.": "Невозможно получить информацию о канале.", + "Could not fetch comments": "Невозможно получить комментарии", + "View `x` replies": "Показать `x` ответов", + "`x` ago": "`x` назад", + "Load more": "Загрузить больше", + "`x` points": "`x` очков", + "Could not create mix.": "Невозможно создать \"микс\".", + "Playlist is empty": "Плейлист пуст", + "Invalid playlist.": "Некорректный плейлист.", + "Playlist does not exist.": "Плейлист не существует.", + "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", + "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", + "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", + "Invalid challenge": "Неправильный ответ в \"challenge\"", + "Invalid token": "Неправильный токен", + "Invalid user": "Недопустимое имя пользователя", + "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", + "English": "Английский", + "English (auto-generated)": "Английский (созданы автоматически)", + "Afrikaans": "Африкаанс", + "Albanian": "Албанский", + "Amharic": "Амхарский", + "Arabic": "Арабский", + "Armenian": "Армянский", + "Azerbaijani": "Азербайджанский", + "Bangla": "Бенгальский", + "Basque": "Баскский", + "Belarusian": "Белорусский", + "Bosnian": "Боснийский", + "Bulgarian": "Болгарский", + "Burmese": "Бирманский", + "Catalan": "Каталонский", + "Cebuano": "Себуанский", + "Chinese (Simplified)": "Китайский (упрощенный)", + "Chinese (Traditional)": "Китайский (традиционный)", + "Corsican": "Корсиканский", + "Croatian": "Хорватский", + "Czech": "Чешский", + "Danish": "Датский", + "Dutch": "Нидерландский", + "Esperanto": "Эсперанто", + "Estonian": "Эстонский", + "Filipino": "Филиппинский", + "Finnish": "Финский", + "French": "Французский", + "Galician": "Галисийский", + "Georgian": "Грузинский", + "German": "Немецкий", + "Greek": "Греческий", + "Gujarati": "Гуджаратский", + "Haitian Creole": "Гаит. креольский", + "Hausa": "Хауса", + "Hawaiian": "Гавайский", + "Hebrew": "Иврит", + "Hindi": "Хинди", + "Hmong": "Хмонг (мяо)", + "Hungarian": "Венгерский", + "Icelandic": "Исландский", + "Igbo": "Игбо", + "Indonesian": "Индонезийский", + "Irish": "Ирландский", + "Italian": "Итальянский", + "Japanese": "Японский", + "Javanese": "Яванский", + "Kannada": "Каннада", + "Kazakh": "Казахский", + "Khmer": "Кхмерский", + "Korean": "Корейский", + "Kurdish": "Курдский", + "Kyrgyz": "Киргизский", + "Lao": "Лаосский", + "Latin": "Латинский", + "Latvian": "Латышский", + "Lithuanian": "Литовский", + "Luxembourgish": "Люксембургский", + "Macedonian": "Македонский", + "Malagasy": "Малагасийский", + "Malay": "Малайский", + "Malayalam": "Малаялам", + "Maltese": "Мальтийский", + "Maori": "Маори", + "Marathi": "Маратхи", + "Mongolian": "Монгольская", + "Nepali": "Непальский", + "Norwegian": "Норвежский", + "Nyanja": "Ньянджа", + "Pashto": "Пушту", + "Persian": "Персидский", + "Polish": "Польский", + "Portuguese": "Португальский", + "Punjabi": "Панджаби", + "Romanian": "Румынский", + "Russian": "Русский", + "Samoan": "Самоанский", + "Scottish Gaelic": "Шотландский (гэльский)", + "Serbian": "Сербский", + "Shona": "Шона", + "Sindhi": "Синдхи", + "Sinhala": "Сингальский", + "Slovak": "Словацкий", + "Slovenian": "Словенский", + "Somali": "Сомалийский", + "Southern Sotho": "Сесото (южный сото)", + "Spanish": "Испанский", + "Spanish (Latin America)": "Испанский (Латинская Америка)", + "Sundanese": "Сунданский", + "Swahili": "Суахили", + "Swedish": "Шведский", + "Tajik": "Таджикский", + "Tamil": "Тамильский", + "Telugu": "Телугу", + "Thai": "Тайский", + "Turkish": "Турецкий", + "Ukrainian": "Украинский", + "Urdu": "Урду", + "Uzbek": "Узбекский", + "Vietnamese": "Вьетнамский", + "Welsh": "Валлийский", + "Western Frisian": "Западнофризский", + "Xhosa": "Коса", + "Yiddish": "Идиш", + "Yoruba": "Йоруба", + "Zulu": "Зулусский", + "`x` years": "`x` лет", + "`x` months": "`x` месяцев", + "`x` weeks": "`x` недель", + "`x` days": "`x` дней", + "`x` hours": "`x` часов", + "`x` minutes": "`x` минут", + "`x` seconds": "`x` секунд", + "Fallback comments: ": "Резервные комментарии: ", + "Popular": "Популярное", + "Top": "Топ", + "About": "О сайте", + "Rating: ": "Рейтинг: ", + "Language: ": "Язык: ", + "Default": "По-умолчанию", + "Music": "Музыка", + "Gaming": "Игры", + "News": "Новости", + "Movies": "Фильмы", + "Download": "Скачать", + "Download as: ": "Скачать как: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "(edited)": "(изменено)", + "Youtube permalink of the comment": "Прямая ссылка на YouTube", + "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", + "Audio mode": "Аудио режим", + "Video mode": "Видео режим", + "Videos": "Видео", + "Playlists": "Плейлисты", + "Current version: ": "Текущая версия: " } diff --git a/locales/uk.json b/locales/uk.json index b1d81ca4..22c0fb86 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -1,295 +1,295 @@ { - "`x` subscribers": "", - "`x` videos": "", - "LIVE": "", - "Shared `x` ago": "", - "Unsubscribe": "", - "Subscribe": "", - "Login to subscribe to `x`": "", - "View channel on YouTube": "", - "newest": "", - "oldest": "", - "popular": "", - "last": "", - "Next page": "", - "Previous page": "", - "Clear watch history?": "", - "Yes": "", - "No": "", - "Import and Export Data": "", - "Import": "", - "Import Invidious data": "", - "Import YouTube subscriptions": "", - "Import FreeTube subscriptions (.db)": "", - "Import NewPipe subscriptions (.json)": "", - "Import NewPipe data (.zip)": "", - "Export": "", - "Export subscriptions as OPML": "", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "", - "Export data as JSON": "", - "Delete account?": "", - "History": "", - "An alternative front-end to YouTube": "", - "JavaScript license information": "", - "source": "", - "Login": "", - "Login/Register": "", - "Login to Google": "", - "User ID:": "", - "Password:": "", - "Time (h:mm:ss):": "", - "Text CAPTCHA": "", - "Image CAPTCHA": "", - "Sign In": "", - "Register": "", - "Email:": "", - "Google verification code:": "", - "Preferences": "", - "Player preferences": "", - "Always loop: ": "", - "Autoplay: ": "", - "Autoplay next video: ": "", - "Listen by default: ": "", - "Proxy videos? ": "", - "Default speed: ": "", - "Preferred video quality: ": "", - "Player volume: ": "", - "Default comments: ": "", - "Default captions: ": "", - "Fallback captions: ": "", - "Show related videos? ": "", - "Visual preferences": "", - "Dark mode: ": "", - "Thin mode: ": "", - "Subscription preferences": "", - "Redirect homepage to feed: ": "", - "Number of videos shown in feed: ": "", - "Sort videos by: ": "", - "published": "", - "published - reverse": "", - "alphabetically": "", - "alphabetically - reverse": "", - "channel name": "", - "channel name - reverse": "", - "Only show latest video from channel: ": "", - "Only show latest unwatched video from channel: ": "", - "Only show unwatched: ": "", - "Only show notifications (if there are any): ": "", - "Data preferences": "", - "Clear watch history": "", - "Import/Export data": "", - "Manage subscriptions": "", - "Watch history": "", - "Delete account": "", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", - "Save preferences": "", - "Subscription manager": "", - "`x` subscriptions": "", - "Import/Export": "", - "unsubscribe": "", - "Subscriptions": "", - "`x` unseen notifications": "", - "search": "", - "Sign out": "", - "Released under the AGPLv3 by Omar Roth.": "", - "Source available here.": "", - "View JavaScript license information.": "", - "View privacy policy.": "", - "Trending": "", - "Unlisted": "", - "Watch video on Youtube": "", - "Genre: ": "", - "License: ": "", - "Family friendly? ": "", - "Wilson score: ": "", - "Engagement: ": "", - "Whitelisted regions: ": "", - "Blacklisted regions: ": "", - "Shared `x`": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", - "View YouTube comments": "", - "View more comments on Reddit": "", - "View `x` comments": "", - "View Reddit comments": "", - "Hide replies": "", - "Show replies": "", - "Incorrect password": "", - "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", - "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "", - "Invalid CAPTCHA": "", - "CAPTCHA is a required field": "", - "User ID is a required field": "", - "Password is a required field": "", - "Invalid username or password": "", - "Please sign in using 'Sign in with Google'": "", - "Password cannot be empty": "", - "Password cannot be longer than 55 characters": "", - "Please sign in": "", - "Invidious Private Feed for `x`": "", - "channel:`x`": "", - "Deleted or invalid channel": "", - "This channel does not exist.": "", - "Could not get channel info.": "", - "Could not fetch comments": "", - "View `x` replies": "", - "`x` ago": "", - "Load more": "", - "`x` points": "", - "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", - "Playlist does not exist.": "", - "Could not pull trending pages.": "", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "", - "Token is expired, please try again": "", - "English": "", - "English (auto-generated)": "", - "Afrikaans": "", - "Albanian": "", - "Amharic": "", - "Arabic": "", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "", - "Bosnian": "", - "Bulgarian": "", - "Burmese": "", - "Catalan": "", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "", - "Danish": "", - "Dutch": "", - "Esperanto": "", - "Estonian": "", - "Filipino": "", - "Finnish": "", - "French": "", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "", - "Icelandic": "", - "Igbo": "", - "Indonesian": "", - "Irish": "", - "Italian": "", - "Japanese": "", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "", - "Slovenian": "", - "Somali": "", - "Southern Sotho": "", - "Spanish": "", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "", - "Ukrainian": "", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "", - "`x` months": "", - "`x` weeks": "", - "`x` days": "", - "`x` hours": "", - "`x` minutes": "", - "`x` seconds": "", - "Fallback comments: ": "", - "Popular": "", - "Top": "", - "About": "", - "Rating: ": "", - "Language: ": "", - "Default": "", - "Music": "", - "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", - "%A %B %-d, %Y": "", - "(edited)": "", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "", - "Playlists": "", - "Current version: ": "" + "`x` subscribers": "`x` підписник / підписників / підписника", + "`x` videos": "`x` відео", + "LIVE": "ПРЯМИЙ ЕФІР", + "Shared `x` ago": "Розміщено `x` назад", + "Unsubscribe": "Відписатися", + "Subscribe": "Підписатися", + "Login to subscribe to `x`": "Увійдіть, щоб підписатися на `x`", + "View channel on YouTube": "Подивитися канал на YouTube", + "newest": "найновіше", + "oldest": "найстаріше", + "popular": "популярне", + "last": "останнє", + "Next page": "Наступна сторінка", + "Previous page": "Попередня сторінка", + "Clear watch history?": "Очистити історію переглядів?", + "Yes": "Так", + "No": "Ні", + "Import and Export Data": "Імпорт і експорт даних", + "Import": "Імпорт", + "Import Invidious data": "Імпортувати дані Invidious", + "Import YouTube subscriptions": "Імпортувати підписки з YouTube", + "Import FreeTube subscriptions (.db)": "Імпортувати підписки з FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Імпортувати підписки з NewPipe (.json)", + "Import NewPipe data (.zip)": "Імпортувати дані з NewPipe (.zip)", + "Export": "Експорт", + "Export subscriptions as OPML": "Експортувати підписки у форматі OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Експортувати підписки у форматі OPML (для NewPipe та FreeTube)", + "Export data as JSON": "Експортувати дані у форматі JSON", + "Delete account?": "Видалити обліківку?", + "History": "Історія", + "An alternative front-end to YouTube": "Альтернативний фронтенд до YouTube", + "JavaScript license information": "Інформація щодо ліцензій JavaScript", + "source": "джерело", + "Login": "Увійти", + "Login/Register": "Увійти або зареєструватися", + "Login to Google": "Увійти через Google", + "User ID:": "ID користувача:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Час (г:мм:сс):", + "Text CAPTCHA": "Текст капчі", + "Image CAPTCHA": "Зображення капчі", + "Sign In": "Увійти", + "Register": "Зареєструватися", + "Email:": "Електронна пошта:", + "Google verification code:": "Код підтвердження Google:", + "Preferences": "Налаштування", + "Player preferences": "Налаштування програвача", + "Always loop: ": "Завжди повторювати: ", + "Autoplay: ": "Автовідтворення: ", + "Autoplay next video: ": "Автовідтворення наступного відео: ", + "Listen by default: ": "Режим «тільки звук» як усталений: ", + "Proxy videos? ": "Програвати відео через проксі? ", + "Default speed: ": "Усталена швидкість відео: ", + "Preferred video quality: ": "Пріорітетна якість відео: ", + "Player volume: ": "Гучність відео: ", + "Default comments: ": "Джерело коментарів: ", + "Default captions: ": "Основна мова субтитрів: ", + "Fallback captions: ": "Запасна мова субтитрів: ", + "Show related videos? ": "Показувати схожі відео? ", + "Visual preferences": "Налаштування сайту", + "Dark mode: ": "Темне оформлення: ", + "Thin mode: ": "Полегшене оформлення: ", + "Subscription preferences": "Налаштування підписок", + "Redirect homepage to feed: ": "Показувати відео з каналів, на які підписані, як головну сторінку: ", + "Number of videos shown in feed: ": "Кількість відео з каналів, на які підписані, у потоці: ", + "Sort videos by: ": "Сортувати відео: ", + "published": "за датою розміщення", + "published - reverse": "за датою розміщення в зворотному порядку", + "alphabetically": "за абеткою", + "alphabetically - reverse": "за абеткою в зворотному порядку", + "channel name": "за назвою каналу", + "channel name - reverse": "за назвою каналу в зворотному порядку", + "Only show latest video from channel: ": "Показувати тільки останнє відео з каналів: ", + "Only show latest unwatched video from channel: ": "Показувати тільки непереглянуті відео з каналів: ", + "Only show unwatched: ": "Показувати тільки непереглянуті відео: ", + "Only show notifications (if there are any): ": "Показувати лише сповіщення, якщо вони є: ", + "Data preferences": "Налаштування даних", + "Clear watch history": "Очистити історію переглядів", + "Import/Export data": "Імпорт і експорт даних", + "Manage subscriptions": "Керування підписками", + "Watch history": "Історія переглядів", + "Delete account": "Видалити обліківку", + "Administrator preferences": "Адміністраторські налаштування", + "Default homepage: ": "Усталена домашня сторінка: ", + "Feed menu: ": "Меню потоку з відео: ", + "Top enabled? ": "Увімкнути топ відео? ", + "CAPTCHA enabled? ": "Увімкнути капчу? ", + "Login enabled? ": "Увімкнути авторизацію? ", + "Registration enabled? ": "Увімкнути реєстрацію? ", + "Report statistics? ": "Повідомляти статистику? ", + "Save preferences": "Зберегти налаштування", + "Subscription manager": "Менеджер підписок", + "`x` subscriptions": "`x` підписка / підписок / підписки", + "Import/Export": "Імпорт і експорт", + "unsubscribe": "відписатися", + "Subscriptions": "Підписки", + "`x` unseen notifications": "`x` непереглянуте сповіщення / непереглянутих сповіщень / непереглянутих сповіщення", + "search": "пошук", + "Sign out": "Вийти", + "Released under the AGPLv3 by Omar Roth.": "Реалізовано Омаром Ротом за ліцензією AGPLv3.", + "Source available here.": "Програмний код доступний тут.", + "View JavaScript license information.": "Переглянути інформацію щодо ліцензії JavaScript.", + "View privacy policy.": "Переглянути політику приватності.", + "Trending": "У тренді", + "Unlisted": "", + "Watch video on Youtube": "Дивитися відео на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Ліцензія: ", + "Family friendly? ": "Перегляд із родиною? ", + "Wilson score: ": "Рейтинг Вілсона: ", + "Engagement: ": "Залученість: ", + "Whitelisted regions: ": "Доступно у регіонах: ", + "Blacklisted regions: ": "Недоступно у регіонах: ", + "Shared `x`": "Розміщено `x`", + "Premieres in `x`": "Прем’єра через `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Схоже, у вас відключений JavaScript. Щоб побачити коментарі, натисніть сюда, але майте на увазі, що вони можуть завантажуватися трохи довше.", + "View YouTube comments": "Переглянути коментарі з YouTube", + "View more comments on Reddit": "Переглянути більше коментарів на Reddit", + "View `x` comments": "Переглянути `x` коментар / коментарів / коментаря", + "View Reddit comments": "Переглянути коментарі з Reddit", + "Hide replies": "Сховати відповіді", + "Show replies": "Показати відповіді", + "Incorrect password": "Неправильний пароль", + "Quota exceeded, try again in a few hours": "Ліміт перевищено, спробуйте знову за декілька годин", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Не вдається увійти. Перевірте, чи не ввімкнена двофакторна аутентифікація (за кодом чи смс).", + "Invalid TFA code": "Неправильний код двофакторної аутентифікації", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не вдається увійти. Це може бути через те, що у вашій обліківці не ввімкнена двофакторна аутентифікація.", + "Invalid answer": "Неправильна відповідь", + "Invalid CAPTCHA": "Неправильна капча", + "CAPTCHA is a required field": "Необхідно пройти капчу", + "User ID is a required field": "Необхідно ввести ID користувача", + "Password is a required field": "Необхідно ввести пароль", + "Invalid username or password": "Неправильний логін чи пароль", + "Please sign in using 'Sign in with Google'": "Будь ласка, натисніть «Увійдіть через Google»", + "Password cannot be empty": "Пароль не може бути порожнім", + "Password cannot be longer than 55 characters": "Пароль не може бути довшим за 55 знаків", + "Please sign in": "Будь ласка, увійдіть", + "Invidious Private Feed for `x`": "Приватний поток відео Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал видалено або не знайдено", + "This channel does not exist.": "Такого каналу не існує.", + "Could not get channel info.": "Не вдається отримати інформацію щодо цього каналу.", + "Could not fetch comments": "Не вдається завантажити коментарі", + "View `x` replies": "Переглянути `x` відповідь / відповідей / відповіді", + "`x` ago": "`x` тому", + "Load more": "Завантажити більше", + "`x` points": "`x` очко / очок / очка", + "Could not create mix.": "Не вдається створити мікс.", + "Playlist is empty": "Плейлист порожній", + "Invalid playlist.": "Недійсний плейлист.", + "Playlist does not exist.": "Плейлист не існує.", + "Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».", + "Hidden field \"challenge\" is a required field": "", + "Hidden field \"token\" is a required field": "", + "Invalid challenge": "", + "Invalid token": "", + "Invalid user": "", + "Token is expired, please try again": "", + "English": "", + "English (auto-generated)": "", + "Afrikaans": "", + "Albanian": "", + "Amharic": "", + "Arabic": "", + "Armenian": "", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "", + "Bosnian": "", + "Bulgarian": "", + "Burmese": "", + "Catalan": "", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "", + "Danish": "", + "Dutch": "", + "Esperanto": "", + "Estonian": "", + "Filipino": "", + "Finnish": "", + "French": "", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "", + "Icelandic": "", + "Igbo": "", + "Indonesian": "", + "Irish": "", + "Italian": "", + "Japanese": "", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "", + "Slovenian": "", + "Somali": "", + "Southern Sotho": "", + "Spanish": "", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "", + "Ukrainian": "", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "", + "`x` months": "", + "`x` weeks": "", + "`x` days": "", + "`x` hours": "", + "`x` minutes": "", + "`x` seconds": "", + "Fallback comments: ": "", + "Popular": "", + "Top": "", + "About": "", + "Rating: ": "", + "Language: ": "", + "Default": "", + "Music": "", + "Gaming": "", + "News": "", + "Movies": "", + "Download": "", + "Download as: ": "", + "%A %B %-d, %Y": "", + "(edited)": "", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "", + "Videos": "", + "Playlists": "", + "Current version: ": "" } From 677a4656301bfbb8f2606212b9c6f43b61275573 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sun, 14 Apr 2019 19:48:21 -0500 Subject: [PATCH 061/210] Fix file formatting for locales --- locales/ar.json | 592 ++++++++++++++++++++++----------------------- locales/de.json | 592 ++++++++++++++++++++++----------------------- locales/en-US.json | 588 ++++++++++++++++++++++---------------------- locales/eo.json | 586 ++++++++++++++++++++++---------------------- locales/es.json | 588 ++++++++++++++++++++++---------------------- locales/eu.json | 588 ++++++++++++++++++++++---------------------- locales/fr.json | 588 ++++++++++++++++++++++---------------------- locales/it.json | 588 ++++++++++++++++++++++---------------------- locales/nb_NO.json | 588 ++++++++++++++++++++++---------------------- locales/nl.json | 588 ++++++++++++++++++++++---------------------- locales/pl.json | 588 ++++++++++++++++++++++---------------------- locales/ru.json | 592 ++++++++++++++++++++++----------------------- locales/uk.json | 586 ++++++++++++++++++++++---------------------- 13 files changed, 3826 insertions(+), 3826 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 15f582fb..6221862e 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -1,298 +1,298 @@ { - "`x` subscribers": "`x` المشتركين", - "`x` videos": "`x` الفيديوهات", - "LIVE": "مباشر", - "Shared `x` ago": "تم رفع الفيديو منذ `x`", - "Unsubscribe": "إلغاء الإشتراك", - "Subscribe": "إشتراك", - "View channel on YouTube": "زيارة القناة على موقع يوتيوب", - "newest": "الأجدد", - "oldest": "الأقدم", - "popular": "الاكثر شعبية", - "last": "اخر الفيديوهات المعدلة", - "Next page": "الصفحة الثانية", - "Previous page": "الصفحة السابقة", - "Clear watch history?": "مسح السجل ؟", - "Yes": "نعم", - "No": "لا", - "Import and Export Data": "استخراج و إضافة البيانات", - "Import": "إضافة", - "Import Invidious data": "إضافة بيانات Invidious", - "Import YouTube subscriptions": "إضافةالإشتراكات من موقع يوتيوب", - "Import FreeTube subscriptions (.db)": "إضافةالمشتركين من FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "إضافة المشتركين من NewPipe (.json)", - "Import NewPipe data (.zip)": "إضافة بيانات NewPipe (.zip)", - "Export": "استخراج", - "Export subscriptions as OPML": "استخراج المشتركين كـ OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "استخراج المشتركين كـ OPML (لـ NewPipe و FreeTube)", - "Export data as JSON": "استخراج البيانات كـ JSON", - "Delete account?": "حذف الحساب ؟", - "History": "السجل", - "An alternative front-end to YouTube": "البديل الكامل لموقع يوتيوب", - "JavaScript license information": "معلومات ترخيص JavaScript", - "source": "المصدر", - "Login": "تسجيل الدخول", - "Login/Register": "تسجيل الدخول\\إنشاء حساب", - "Login to Google": "تسجيل الدخول بإستخدام جوجل", - "User ID": "إسم المستخدم", - "Password": "الرقم السرى", - "Time (h:mm:ss):": "(يجب ان يكتب مثل هذا التنسيق) الوقت (h(ساعات):mm(دقائق):ss(ثوانى)):", - "Text CAPTCHA": "CAPTCHA كلامية", - "Image CAPTCHA": "CAPTCHA صورية", - "Sign In": "تسجيل الدخول", - "Register": "انشاء الحساب", - "Email": "الإيميل", - "Google verification code": "رمز تحقق جوجل", - "Preferences": "التفضيلات", - "Player preferences": "التفضيلات المشغل", - "Always loop: ": "كرر الفيديو دائما: ", - "Autoplay: ": "تشغيل تلقائى: ", - "Autoplay next video: ": "شغل الفيديو التالى تلقائى: ", - "Listen by default: ": "تشغيل النسخة السمعية تلقائى: ", - "Proxy videos? ": "عرض الفيديوهات عن طريق الوكيل(proxy) ؟", - "Default speed: ": "السرعة الإفتراضية: ", - "Preferred video quality: ": "الجودة المفضلة للفيديوهات: ", - "Player volume: ": "صوت المشغل: ", - "Default comments: ": "إضهار التعليقات الإفتراضية لـ: ", - "youtube": "يوتيوب", - "reddit": "Reddit", - "Default captions: ": "الترجمات الإفتراضية: ", - "Fallback captions: ": "الترجمات المصاحبة: ", - "Show related videos? ": "عرض مقاطع الفيديو ذات الصلة؟", - "Visual preferences": "التفضيلات المرئية", - "Dark mode: ": "الوضع الليلى: ", - "Thin mode: ": "الوضع الخفيف: ", - "Subscription preferences": "تفضيلات الإشتراك", - "Redirect homepage to feed: ": "إعادة التوجية من الصفحة الرئيسية لصفحة المشتركين (لرؤية اخر فيديوهات المشتركين): ", - "Number of videos shown in feed: ": "عدد الفيديوهات التى ستظهر فى صفحة المشتركين: ", - "Sort videos by: ": "ترتيب الفيديو بـ: ", - "published": "احدث فيديو", - "published - reverse": "احدث فيديو - عكسى", - "alphabetically": "ترتيب ابجدى", - "alphabetically - reverse": "ابجدى - عكسى", - "channel name": "بإسم القناة", - "channel name - reverse": "بإسم القناة - عكسى", - "Only show latest video from channel: ": "فقط إظهر اخر فيديو من القناة: ", - "Only show latest unwatched video from channel: ": "فقط اظهر اخر فيديو لم يتم رؤيتة من القناة: ", - "Only show unwatched: ": "فقط اظهر الذى لم يتم رؤيتة: ", - "Only show notifications (if there are any): ": "إظهار الإشعارات فقط (إذا كان هناك أي): ", - "Data preferences": "إعدادات التفضيلات", - "Clear watch history": "حذف سجل المشاهدة", - "Import/Export data": "إضافة\\إستخراج البيانات", - "Manage subscriptions": "إدارة المشتركين", - "Watch history": "سجل المشاهدة", - "Delete account": "حذف الحساب", - "Administrator preferences": "إعدادات المدير", - "Default homepage: ": "الصفحة الرئيسية الافتراضية ", - "Feed menu: ": "قائمة التغذية", - "Top enabled? ": "تفعيل 'الأفضل' ؟ ", - "CAPTCHA enabled? ": "تفعيل الكابتشا ؟", - "Login enabled? ": "تفعيل تسجيل الدخول ؟", - "Registration enabled? ": "تفعيل التسجيل ؟", - "Report statistics? ": "إبلاغ الإحصائيات", - "Save preferences": "حفظ التفضيلات", - "Subscription manager": "مدير الإشتراكات", - "`x` subscriptions": "`x` مشتركين", - "Import/Export": "إضافة\\إستخراج", - "unsubscribe": "إلغاء الإشتراك", - "Subscriptions": "الإشتراكات", - "`x` unseen notifications": "`x` إشعارات لم تشاهدها بعد ", - "search": "بحث", - "Sign out": "تسجيل الخروج", - "Released under the AGPLv3 by Omar Roth.": "تم الإنشاء تحت AGPLv3 بواسطة عمر روث.", - "Source available here.": "الأكواد متوفرة هنا.", - "View JavaScript license information.": "مشاهدة معلومات حول تراخيص الجافاسكريبت.", - "View privacy policy.": "عرض سياسة الخصوصية", - "Trending": "الشائع", - "Unlisted": "غير مصنف", - "Watch video on Youtube": "مشاهدة الفيديو على اليوتيوب", - "Genre: ": "النوع: ", - "License: ": "التراخيص: ", - "Family friendly? ": "محتوى عائلى? ", - "Wilson score: ": "درجة ويلسون: ", - "Engagement: ": "نسبة المشاركة (عدد المشاهدات\\عدد الإعجابات): ", - "Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ", - "Blacklisted regions: ": "الدول الحظور فيها هذا الفيديو: ", - "Shared `x`": "شارك منذ `x`", - "`x` views": "", - "Premieres in `x`": "يعرض فى 'x'", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.", - "View YouTube comments": "عرض تعليقات اليوتيوب", - "View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع Reddit", - "View `x` comments": "عرض `x` تعليقات", - "View Reddit comments": "عرض تعليقات ريدإت Reddit", - "Hide replies": "إخفاء الردود", - "Show replies": "عرض الردود", - "Incorrect password": "الرقم السرى غير صحيح", - "Quota exceeded, try again in a few hours": "تم تجاوز عدد المرات المسموح بها, حاول مرة اخرى بعد عدة ساعات", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "غير قادر على تسجيل الدخول, تأكد من تشغيل المصادقة الثنائية 2FA.", - "Invalid TFA code": "كود مصادقة ثنائية 2FA غير صحيح", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "لم يتم تسجيل الدخول. هذا ربما بسبب ان المصادقة الثنائية 2FA معطلة فى حسابك.", - "Invalid answer": "إجابة خاطئة", - "Invalid CAPTCHA": "الكابتشا CAPTCHA غير صاحلة", - "CAPTCHA is a required field": "مكان الكابتشا CAPTCHA مطلوب", - "User ID is a required field": "مكان إسم المستخدم مطلوب", - "Password is a required field": "مكان الرقم السرى مطلوب", - "Invalid username or password": "إسم المستخدم او الرقم السرى غير صحيح", - "Please sign in using 'Sign in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'", - "Password cannot be empty": "الرقم السرى لايمكن ان يكون فارغ", - "Password cannot be longer than 55 characters": "الرقم السرى لا يتعدى 55 حرف", - "Please sign in": "الرجاء تسجيل الدخول", - "Invidious Private Feed for `x`": "صفحة Invidious للمشتركين الخاصة\\مخفية لـ `x`", - "channel:`x`": "قناة:`x`", - "Deleted or invalid channel": "قناة ممسوحة او غير صالحة", - "This channel does not exist.": "القناة غير موجودة.", - "Could not get channel info.": "لم يستطع الحصول على معلومات القناة.", - "Could not fetch comments": "لم يتمكن من إحضار التعليقات", - "View `x` replies": "عرض `x` ردود", - "`x` ago": "`x` منذ", - "Load more": "عرض المزيد", - "`x` points": "`x` نقاط", - "Could not create mix.": "لم يستطع عمل خلط.", - "Playlist is empty": "قائمة التشغيل فارغة", - "Invalid playlist.": "قائمة التشغيل غير صالحة.", - "Playlist does not exist.": "قائمة التشغيل غير موجودة.", - "Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.", - "Hidden field \"challenge\" is a required field": "مكان مخفى \"تحدى\" مكان مطلوب", - "Hidden field \"token\" is a required field": "مكان مخفى \"رمز\" مكان مطلوب", - "Invalid challenge": "تحدى غير صالح", - "Invalid token": "روز غير صالح", - "Invalid user": "مستخدم غير صالح", - "Token is expired, please try again": "الرمز منتهى الصلاحية , الرجاء المحاولة مرة اخرى", - "English": "إنجليزى", - "English (auto-generated)": "إنجليزى (تم إنشائة تلقائى)", - "Afrikaans": "الأفريكانية", - "Albanian": "الألبانية", - "Amharic": "الأمهرية", - "Arabic": "العربية", - "Armenian": "الأرميني", - "Azerbaijani": "أذربيجان", - "Bangla": "البنغالية", - "Basque": "الباسكي", - "Belarusian": "البيلاروسية", - "Bosnian": "البوسنية", - "Bulgarian": "البلغارية", - "Burmese": "البورمية", - "Catalan": "الكاتالونية", - "Cebuano": "السيبيونو", - "Chinese (Simplified)": "الصينية (المبسطة)", - "Chinese (Traditional)": "الصينية (التقليدية)", - "Corsican": "الكورسيكية", - "Croatian": "الكرواتية", - "Czech": "تشيكي", - "Danish": "دانماركي", - "Dutch": "هولندي", - "Esperanto": "الاسبرانتو", - "Estonian": "الإستونية", - "Filipino": "الفلبينية", - "Finnish": "الفنلندية", - "French": "الفرنسية", - "Galician": "الجاليكية", - "Georgian": "الجورجية", - "German": "ألمانية", - "Greek": "الإغريقي", - "Gujarati": "الغوجاراتية", - "Haitian Creole": "الكاثوليكية الهايتية", - "Hausa": "الهوسا", - "Hawaiian": "هاواي", - "Hebrew": "العبرية", - "Hindi": "الهندية", - "Hmong": "همونغ", - "Hungarian": "الهنغارية", - "Icelandic": "أيسلندي", - "Igbo": "الإيبو", - "Indonesian": "الأندونيسية", - "Irish": "الأيرلندية", - "Italian": "الإيطالي", - "Japanese": "اليابانية", - "Javanese": "جاوي", - "Kannada": "الكانادا", - "Kazakh": "الكازاخية", - "Khmer": "الخمير", - "Korean": "الكورية", - "Kurdish": "كردي", - "Kyrgyz": "قيرغيزستان", - "Lao": "لاو", - "Latin": "لاتينية", - "Latvian": "اللاتفية", - "Lithuanian": "اللتوانية", - "Luxembourgish": "اللوكسمبرجية", - "Macedonian": "المقدونية", - "Malagasy": "مدجشقر\\مدغشقر", - "Malay": "الملايو", - "Malayalam": "المالايالامية", - "Maltese": "المالطية", - "Maori": "الماوري", - "Marathi": "المهاراتية", - "Mongolian": "المنغولية", - "Nepali": "النيبالية", - "Norwegian": "النرويجية", - "Nyanja": "نيانجا", - "Pashto": "الباشتو", - "Persian": "الفارسية", - "Polish": "البولندي", - "Portuguese": "البرتغالية", - "Punjabi": "البنجابية", - "Romanian": "روماني", - "Russian": "الروسية", - "Samoan": "ساموا", - "Scottish Gaelic": "الغيلية الاسكتلندية", - "Serbian": "صربي", - "Shona": "شونا", - "Sindhi": "السندية", - "Sinhala": "السنهالية", - "Slovak": "السلوفاكية", - "Slovenian": "سلوفيني", - "Somali": "الصومالية", - "Southern Sotho": "جنوب سوثو", - "Spanish": "الأسبانية", - "Spanish (Latin America)": "الأسبانية (أمريكا اللاتينية)", - "Sundanese": "السودانية", - "Swahili": "السواحلية", - "Swedish": "السويدية", - "Tajik": "الطاجيكية", - "Tamil": "التاميل", - "Telugu": "التيلجو", - "Thai": "التايلاندية", - "Turkish": "التركية", - "Ukrainian": "الأوكراني", - "Urdu": "الأردية", - "Uzbek": "الأوزبكي", - "Vietnamese": "الفيتنامية", - "Welsh": "الولزية", - "Western Frisian": "الفريزية الغربية", - "Xhosa": "زوسا", - "Yiddish": "اليديشية", - "Yoruba": "اليوروبا", - "Zulu": "الزولو", - "`x` years": "`x` سنوات", - "`x` months": "`x` شهور", - "`x` weeks": "`x` اسابيع", - "`x` days": "`x` ايام", - "`x` hours": "`x` ساعات", - "`x` minutes": "`x` دقائق", - "`x` seconds": "`x` ثوانى", - "Fallback comments: ": "التعليقات المصاحبة", - "Popular": "لاكثر شعبية", - "Top": "الأفضل", - "About": "حول", - "Rating: ": "التقييم", - "Language: ": "اللغة", - "View as playlist": "", - "Default": "الكل", - "Music": "الاغانى", - "Gaming": "الألعاب", - "News": "الأخبار", - "Movies": "الأفلام", - "Download as: ": "تحميل كـ", - "Download": "تحميل", - "%A %B %-d, %Y": "", - "(edited)": "(تم تعديلة)", - "Youtube permalink of the comment": "رابط التعليق على اليوتيوب", - "`x` marked it with a ❤": "'x' اعجب بهذا", - "Audio mode": "الوضع الصوتى", - "Video mode": "وضع الفيديو", - "Videos": "الفيديوهات", - "Playlists": "قوائم التشغيل", - "Current version: ": "الإصدار الحالى" + "`x` subscribers": "`x` المشتركين", + "`x` videos": "`x` الفيديوهات", + "LIVE": "مباشر", + "Shared `x` ago": "تم رفع الفيديو منذ `x`", + "Unsubscribe": "إلغاء الإشتراك", + "Subscribe": "إشتراك", + "View channel on YouTube": "زيارة القناة على موقع يوتيوب", + "newest": "الأجدد", + "oldest": "الأقدم", + "popular": "الاكثر شعبية", + "last": "اخر الفيديوهات المعدلة", + "Next page": "الصفحة الثانية", + "Previous page": "الصفحة السابقة", + "Clear watch history?": "مسح السجل ؟", + "Yes": "نعم", + "No": "لا", + "Import and Export Data": "استخراج و إضافة البيانات", + "Import": "إضافة", + "Import Invidious data": "إضافة بيانات Invidious", + "Import YouTube subscriptions": "إضافةالإشتراكات من موقع يوتيوب", + "Import FreeTube subscriptions (.db)": "إضافةالمشتركين من FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "إضافة المشتركين من NewPipe (.json)", + "Import NewPipe data (.zip)": "إضافة بيانات NewPipe (.zip)", + "Export": "استخراج", + "Export subscriptions as OPML": "استخراج المشتركين كـ OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "استخراج المشتركين كـ OPML (لـ NewPipe و FreeTube)", + "Export data as JSON": "استخراج البيانات كـ JSON", + "Delete account?": "حذف الحساب ؟", + "History": "السجل", + "An alternative front-end to YouTube": "البديل الكامل لموقع يوتيوب", + "JavaScript license information": "معلومات ترخيص JavaScript", + "source": "المصدر", + "Login": "تسجيل الدخول", + "Login/Register": "تسجيل الدخول\\إنشاء حساب", + "Login to Google": "تسجيل الدخول بإستخدام جوجل", + "User ID": "إسم المستخدم", + "Password": "الرقم السرى", + "Time (h:mm:ss):": "(يجب ان يكتب مثل هذا التنسيق) الوقت (h(ساعات):mm(دقائق):ss(ثوانى)):", + "Text CAPTCHA": "CAPTCHA كلامية", + "Image CAPTCHA": "CAPTCHA صورية", + "Sign In": "تسجيل الدخول", + "Register": "انشاء الحساب", + "Email": "الإيميل", + "Google verification code": "رمز تحقق جوجل", + "Preferences": "التفضيلات", + "Player preferences": "التفضيلات المشغل", + "Always loop: ": "كرر الفيديو دائما: ", + "Autoplay: ": "تشغيل تلقائى: ", + "Autoplay next video: ": "شغل الفيديو التالى تلقائى: ", + "Listen by default: ": "تشغيل النسخة السمعية تلقائى: ", + "Proxy videos? ": "عرض الفيديوهات عن طريق الوكيل(proxy) ؟", + "Default speed: ": "السرعة الإفتراضية: ", + "Preferred video quality: ": "الجودة المفضلة للفيديوهات: ", + "Player volume: ": "صوت المشغل: ", + "Default comments: ": "إضهار التعليقات الإفتراضية لـ: ", + "youtube": "يوتيوب", + "reddit": "Reddit", + "Default captions: ": "الترجمات الإفتراضية: ", + "Fallback captions: ": "الترجمات المصاحبة: ", + "Show related videos? ": "عرض مقاطع الفيديو ذات الصلة؟", + "Visual preferences": "التفضيلات المرئية", + "Dark mode: ": "الوضع الليلى: ", + "Thin mode: ": "الوضع الخفيف: ", + "Subscription preferences": "تفضيلات الإشتراك", + "Redirect homepage to feed: ": "إعادة التوجية من الصفحة الرئيسية لصفحة المشتركين (لرؤية اخر فيديوهات المشتركين): ", + "Number of videos shown in feed: ": "عدد الفيديوهات التى ستظهر فى صفحة المشتركين: ", + "Sort videos by: ": "ترتيب الفيديو بـ: ", + "published": "احدث فيديو", + "published - reverse": "احدث فيديو - عكسى", + "alphabetically": "ترتيب ابجدى", + "alphabetically - reverse": "ابجدى - عكسى", + "channel name": "بإسم القناة", + "channel name - reverse": "بإسم القناة - عكسى", + "Only show latest video from channel: ": "فقط إظهر اخر فيديو من القناة: ", + "Only show latest unwatched video from channel: ": "فقط اظهر اخر فيديو لم يتم رؤيتة من القناة: ", + "Only show unwatched: ": "فقط اظهر الذى لم يتم رؤيتة: ", + "Only show notifications (if there are any): ": "إظهار الإشعارات فقط (إذا كان هناك أي): ", + "Data preferences": "إعدادات التفضيلات", + "Clear watch history": "حذف سجل المشاهدة", + "Import/Export data": "إضافة\\إستخراج البيانات", + "Manage subscriptions": "إدارة المشتركين", + "Watch history": "سجل المشاهدة", + "Delete account": "حذف الحساب", + "Administrator preferences": "إعدادات المدير", + "Default homepage: ": "الصفحة الرئيسية الافتراضية ", + "Feed menu: ": "قائمة التغذية", + "Top enabled? ": "تفعيل 'الأفضل' ؟ ", + "CAPTCHA enabled? ": "تفعيل الكابتشا ؟", + "Login enabled? ": "تفعيل تسجيل الدخول ؟", + "Registration enabled? ": "تفعيل التسجيل ؟", + "Report statistics? ": "إبلاغ الإحصائيات", + "Save preferences": "حفظ التفضيلات", + "Subscription manager": "مدير الإشتراكات", + "`x` subscriptions": "`x` مشتركين", + "Import/Export": "إضافة\\إستخراج", + "unsubscribe": "إلغاء الإشتراك", + "Subscriptions": "الإشتراكات", + "`x` unseen notifications": "`x` إشعارات لم تشاهدها بعد ", + "search": "بحث", + "Sign out": "تسجيل الخروج", + "Released under the AGPLv3 by Omar Roth.": "تم الإنشاء تحت AGPLv3 بواسطة عمر روث.", + "Source available here.": "الأكواد متوفرة هنا.", + "View JavaScript license information.": "مشاهدة معلومات حول تراخيص الجافاسكريبت.", + "View privacy policy.": "عرض سياسة الخصوصية", + "Trending": "الشائع", + "Unlisted": "غير مصنف", + "Watch video on Youtube": "مشاهدة الفيديو على اليوتيوب", + "Genre: ": "النوع: ", + "License: ": "التراخيص: ", + "Family friendly? ": "محتوى عائلى? ", + "Wilson score: ": "درجة ويلسون: ", + "Engagement: ": "نسبة المشاركة (عدد المشاهدات\\عدد الإعجابات): ", + "Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ", + "Blacklisted regions: ": "الدول الحظور فيها هذا الفيديو: ", + "Shared `x`": "شارك منذ `x`", + "`x` views": "", + "Premieres in `x`": "يعرض فى 'x'", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.", + "View YouTube comments": "عرض تعليقات اليوتيوب", + "View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع Reddit", + "View `x` comments": "عرض `x` تعليقات", + "View Reddit comments": "عرض تعليقات ريدإت Reddit", + "Hide replies": "إخفاء الردود", + "Show replies": "عرض الردود", + "Incorrect password": "الرقم السرى غير صحيح", + "Quota exceeded, try again in a few hours": "تم تجاوز عدد المرات المسموح بها, حاول مرة اخرى بعد عدة ساعات", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "غير قادر على تسجيل الدخول, تأكد من تشغيل المصادقة الثنائية 2FA.", + "Invalid TFA code": "كود مصادقة ثنائية 2FA غير صحيح", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "لم يتم تسجيل الدخول. هذا ربما بسبب ان المصادقة الثنائية 2FA معطلة فى حسابك.", + "Invalid answer": "إجابة خاطئة", + "Invalid CAPTCHA": "الكابتشا CAPTCHA غير صاحلة", + "CAPTCHA is a required field": "مكان الكابتشا CAPTCHA مطلوب", + "User ID is a required field": "مكان إسم المستخدم مطلوب", + "Password is a required field": "مكان الرقم السرى مطلوب", + "Invalid username or password": "إسم المستخدم او الرقم السرى غير صحيح", + "Please sign in using 'Sign in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'", + "Password cannot be empty": "الرقم السرى لايمكن ان يكون فارغ", + "Password cannot be longer than 55 characters": "الرقم السرى لا يتعدى 55 حرف", + "Please sign in": "الرجاء تسجيل الدخول", + "Invidious Private Feed for `x`": "صفحة Invidious للمشتركين الخاصة\\مخفية لـ `x`", + "channel:`x`": "قناة:`x`", + "Deleted or invalid channel": "قناة ممسوحة او غير صالحة", + "This channel does not exist.": "القناة غير موجودة.", + "Could not get channel info.": "لم يستطع الحصول على معلومات القناة.", + "Could not fetch comments": "لم يتمكن من إحضار التعليقات", + "View `x` replies": "عرض `x` ردود", + "`x` ago": "`x` منذ", + "Load more": "عرض المزيد", + "`x` points": "`x` نقاط", + "Could not create mix.": "لم يستطع عمل خلط.", + "Playlist is empty": "قائمة التشغيل فارغة", + "Invalid playlist.": "قائمة التشغيل غير صالحة.", + "Playlist does not exist.": "قائمة التشغيل غير موجودة.", + "Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.", + "Hidden field \"challenge\" is a required field": "مكان مخفى \"تحدى\" مكان مطلوب", + "Hidden field \"token\" is a required field": "مكان مخفى \"رمز\" مكان مطلوب", + "Invalid challenge": "تحدى غير صالح", + "Invalid token": "روز غير صالح", + "Invalid user": "مستخدم غير صالح", + "Token is expired, please try again": "الرمز منتهى الصلاحية , الرجاء المحاولة مرة اخرى", + "English": "إنجليزى", + "English (auto-generated)": "إنجليزى (تم إنشائة تلقائى)", + "Afrikaans": "الأفريكانية", + "Albanian": "الألبانية", + "Amharic": "الأمهرية", + "Arabic": "العربية", + "Armenian": "الأرميني", + "Azerbaijani": "أذربيجان", + "Bangla": "البنغالية", + "Basque": "الباسكي", + "Belarusian": "البيلاروسية", + "Bosnian": "البوسنية", + "Bulgarian": "البلغارية", + "Burmese": "البورمية", + "Catalan": "الكاتالونية", + "Cebuano": "السيبيونو", + "Chinese (Simplified)": "الصينية (المبسطة)", + "Chinese (Traditional)": "الصينية (التقليدية)", + "Corsican": "الكورسيكية", + "Croatian": "الكرواتية", + "Czech": "تشيكي", + "Danish": "دانماركي", + "Dutch": "هولندي", + "Esperanto": "الاسبرانتو", + "Estonian": "الإستونية", + "Filipino": "الفلبينية", + "Finnish": "الفنلندية", + "French": "الفرنسية", + "Galician": "الجاليكية", + "Georgian": "الجورجية", + "German": "ألمانية", + "Greek": "الإغريقي", + "Gujarati": "الغوجاراتية", + "Haitian Creole": "الكاثوليكية الهايتية", + "Hausa": "الهوسا", + "Hawaiian": "هاواي", + "Hebrew": "العبرية", + "Hindi": "الهندية", + "Hmong": "همونغ", + "Hungarian": "الهنغارية", + "Icelandic": "أيسلندي", + "Igbo": "الإيبو", + "Indonesian": "الأندونيسية", + "Irish": "الأيرلندية", + "Italian": "الإيطالي", + "Japanese": "اليابانية", + "Javanese": "جاوي", + "Kannada": "الكانادا", + "Kazakh": "الكازاخية", + "Khmer": "الخمير", + "Korean": "الكورية", + "Kurdish": "كردي", + "Kyrgyz": "قيرغيزستان", + "Lao": "لاو", + "Latin": "لاتينية", + "Latvian": "اللاتفية", + "Lithuanian": "اللتوانية", + "Luxembourgish": "اللوكسمبرجية", + "Macedonian": "المقدونية", + "Malagasy": "مدجشقر\\مدغشقر", + "Malay": "الملايو", + "Malayalam": "المالايالامية", + "Maltese": "المالطية", + "Maori": "الماوري", + "Marathi": "المهاراتية", + "Mongolian": "المنغولية", + "Nepali": "النيبالية", + "Norwegian": "النرويجية", + "Nyanja": "نيانجا", + "Pashto": "الباشتو", + "Persian": "الفارسية", + "Polish": "البولندي", + "Portuguese": "البرتغالية", + "Punjabi": "البنجابية", + "Romanian": "روماني", + "Russian": "الروسية", + "Samoan": "ساموا", + "Scottish Gaelic": "الغيلية الاسكتلندية", + "Serbian": "صربي", + "Shona": "شونا", + "Sindhi": "السندية", + "Sinhala": "السنهالية", + "Slovak": "السلوفاكية", + "Slovenian": "سلوفيني", + "Somali": "الصومالية", + "Southern Sotho": "جنوب سوثو", + "Spanish": "الأسبانية", + "Spanish (Latin America)": "الأسبانية (أمريكا اللاتينية)", + "Sundanese": "السودانية", + "Swahili": "السواحلية", + "Swedish": "السويدية", + "Tajik": "الطاجيكية", + "Tamil": "التاميل", + "Telugu": "التيلجو", + "Thai": "التايلاندية", + "Turkish": "التركية", + "Ukrainian": "الأوكراني", + "Urdu": "الأردية", + "Uzbek": "الأوزبكي", + "Vietnamese": "الفيتنامية", + "Welsh": "الولزية", + "Western Frisian": "الفريزية الغربية", + "Xhosa": "زوسا", + "Yiddish": "اليديشية", + "Yoruba": "اليوروبا", + "Zulu": "الزولو", + "`x` years": "`x` سنوات", + "`x` months": "`x` شهور", + "`x` weeks": "`x` اسابيع", + "`x` days": "`x` ايام", + "`x` hours": "`x` ساعات", + "`x` minutes": "`x` دقائق", + "`x` seconds": "`x` ثوانى", + "Fallback comments: ": "التعليقات المصاحبة", + "Popular": "لاكثر شعبية", + "Top": "الأفضل", + "About": "حول", + "Rating: ": "التقييم", + "Language: ": "اللغة", + "View as playlist": "", + "Default": "الكل", + "Music": "الاغانى", + "Gaming": "الألعاب", + "News": "الأخبار", + "Movies": "الأفلام", + "Download as: ": "تحميل كـ", + "Download": "تحميل", + "%A %B %-d, %Y": "", + "(edited)": "(تم تعديلة)", + "Youtube permalink of the comment": "رابط التعليق على اليوتيوب", + "`x` marked it with a ❤": "'x' اعجب بهذا", + "Audio mode": "الوضع الصوتى", + "Video mode": "وضع الفيديو", + "Videos": "الفيديوهات", + "Playlists": "قوائم التشغيل", + "Current version: ": "الإصدار الحالى" } diff --git a/locales/de.json b/locales/de.json index 473cf7f2..fd8c40f6 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1,298 +1,298 @@ { - "`x` subscribers": "`x` Abonnenten", - "`x` videos": "`x` Videos", - "LIVE": "LIVE", - "Shared `x` ago": "Vor `x` geteilt", - "Unsubscribe": "Abbestellen", - "Subscribe": "Abonnieren", - "View channel on YouTube": "Kanal auf YouTube anzeigen", - "newest": "neueste", - "oldest": "älteste", - "popular": "beliebt", - "last": "", - "Next page": "Nächste Seite", - "Previous page": "Vorherige Seite", - "Clear watch history?": "Verlauf löschen?", - "Yes": "Ja", - "No": "Nein", - "Import and Export Data": "Import und Export Daten", - "Import": "Importieren", - "Import Invidious data": "Invidious Daten importieren", - "Import YouTube subscriptions": "YouTube Abonnements importieren", - "Import FreeTube subscriptions (.db)": "FreeTube Abonnements importieren (.db)", - "Import NewPipe subscriptions (.json)": "NewPipe Abonnements importieren (.json)", - "Import NewPipe data (.zip)": "NewPipe Daten importieren (.zip)", - "Export": "Exportieren", - "Export subscriptions as OPML": "Abonnements als OPML exportieren", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Abonnements als OPML exportieren (für NewPipe & FreeTube)", - "Export data as JSON": "Daten als JSON exportieren", - "Delete account?": "Account löschen?", - "History": "Verlauf", - "An alternative front-end to YouTube": "Eine alternative Oberfläche für YouTube", - "JavaScript license information": "JavaScript Lizenzinformationen", - "source": "Quelle", - "Login": "Einloggen", - "Login/Register": "Einloggen/Registrieren", - "Login to Google": "In Google einloggen", - "User ID": "Benutzer ID", - "Password": "Passwort", - "Time (h:mm:ss):": "Zeit (h:mm:ss):", - "Text CAPTCHA": "Text CAPTCHA", - "Image CAPTCHA": "Image CAPTCHA", - "Sign In": "Einloggen", - "Register": "Registrieren", - "Email": "Email", - "Google verification code": "Google Bestätigungscode", - "Preferences": "Einstellungen", - "Player preferences": "Playereinstellungen", - "Always loop: ": "Immer wiederholen: ", - "Autoplay: ": "Automatisch abspielen: ", - "Autoplay next video: ": "nächstes Video automatisch abspielen: ", - "Listen by default: ": "Nur Ton als Standard: ", - "Proxy videos? ": "", - "Default speed: ": "Standardgeschwindigkeit: ", - "Preferred video quality: ": "Bevorzugte Videoqualität: ", - "Player volume: ": "Playerlautstärke: ", - "Default comments: ": "Standardkommentare: ", - "youtube": "youtube", - "reddit": "reddit", - "Default captions: ": "Standarduntertitel: ", - "Fallback captions: ": "Ersatzuntertitel: ", - "Show related videos? ": "Ähnliche Videos anzeigen? ", - "Visual preferences": "Anzeigeeinstellungen", - "Dark mode: ": "Nachtmodus: ", - "Thin mode: ": "Schlanker Modus: ", - "Subscription preferences": "Abonnementeinstellungen", - "Redirect homepage to feed: ": "Startseite zu Feed umleiten: ", - "Number of videos shown in feed: ": "Anzahl von Videos die im Feed angezeigt werden: ", - "Sort videos by: ": "Videos sortieren nach: ", - "published": "veröffentlicht", - "published - reverse": "veröffentlicht - invertiert", - "alphabetically": "alphabetisch", - "alphabetically - reverse": "alphabetisch - invertiert", - "channel name": "Kanalname", - "channel name - reverse": "Kanalname - invertiert", - "Only show latest video from channel: ": "Nur neueste Videos des Kanals anzeigen: ", - "Only show latest unwatched video from channel: ": "Nur neueste ungesehene Videos des Kanals anzeigen: ", - "Only show unwatched: ": "Nur ungesehene anzeigen: ", - "Only show notifications (if there are any): ": "Nur Benachrichtigungen anzeigen (wenn es welche gibt): ", - "Data preferences": "Dateneinstellungen", - "Clear watch history": "Verlauf löschen", - "Import/Export data": "Daten im- exportieren", - "Manage subscriptions": "Abonnements verwalten", - "Watch history": "Verlauf", - "Delete account": "Account löschen", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", - "Save preferences": "Einstellungen speichern", - "Subscription manager": "Abonnementverwaltung", - "`x` subscriptions": "`x` Abonnements", - "Import/Export": "Importieren/Exportieren", - "unsubscribe": "abbestellen", - "Subscriptions": "Abonnements", - "`x` unseen notifications": "`x` ungesehene Benachrichtigungen", - "search": "Suchen", - "Sign out": "Abmelden", - "Released under the AGPLv3 by Omar Roth.": "Veröffentlicht unter AGPLv3 von Omar Roth.", - "Source available here.": "Quellcode verfügbar hier.", - "View JavaScript license information.": "Javascript Lizenzinformationen anzeigen.", - "View privacy policy.": "", - "Trending": "Trending", - "Unlisted": "", - "Watch video on Youtube": "Video auf YouTube ansehen", - "Genre: ": "Genre: ", - "License: ": "Lizenz: ", - "Family friendly? ": "Familienfreundlich? ", - "Wilson score: ": "Wilson-Score: ", - "Engagement: ": "Engagement: ", - "Whitelisted regions: ": "Erlaubte Regionen: ", - "Blacklisted regions: ": "Unerlaubte Regionen: ", - "Shared `x`": "Geteilt `x`", - "`x` views": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.", - "View YouTube comments": "YouTube Kommentare anzeigen", - "View more comments on Reddit": "Mehr Kommentare auf Reddit anzeigen", - "View `x` comments": "`x` Kommentare anzeigen", - "View Reddit comments": "Reddit Kommentare anzeigen", - "Hide replies": "Antworten verstecken", - "Show replies": "Antworten anzeigen", - "Incorrect password": "Falsches Passwort", - "Quota exceeded, try again in a few hours": "Kontingent überschritten, versuche es in ein paar Stunden erneut", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Login nicht möglich, stellen Sie sicher dass two-factor Authentifikation (Authentifizierung oder SMS) aktiviert ist.", - "Invalid TFA code": "Ungültiger TFA Code", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login fehlgeschlagen. Das kann daran liegen dass two-factor Authentifizierung in ihrem Account nicht aktiviert ist.", - "Invalid answer": "Ungültige Antwort", - "Invalid CAPTCHA": "Ungültiges CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA ist eine erforderliche Eingabe", - "User ID is a required field": "Benutzer ID ist eine erforderliche Eingabe", - "Password is a required field": "Passwort ist eine erforderliche Eingabe", - "Invalid username or password": "Ungültiger Benutzername oder Passwort", - "Please sign in using 'Sign in with Google'": "Bitte melden sie sich mit 'Mit Google anmelden' an", - "Password cannot be empty": "Passwort darf nicht leer sein", - "Password cannot be longer than 55 characters": "Passwort darf nicht länger als 55 Zeichen sein", - "Please sign in": "Bitte anmelden", - "Invidious Private Feed for `x`": "Invidious Persönlicher Feed für `x`", - "channel:`x`": "Kanal:`x`", - "Deleted or invalid channel": "Gelöschter oder ungültiger Kanal", - "This channel does not exist.": "Dieser Kanal existiert nicht.", - "Could not get channel info.": "Kanalinformationen konnten nicht geladen werden.", - "Could not fetch comments": "Kommentare konnten nicht geladen werden", - "View `x` replies": "Zeige `x` Antworten", - "`x` ago": "vor `x`", - "Load more": "Mehr laden", - "`x` points": "`x` Punkte", - "Could not create mix.": "Mix konnte nicht erstellt werden.", - "Playlist is empty": "Playlist ist leer", - "Invalid playlist.": "Ungültige Playlist.", - "Playlist does not exist.": "Playlist existiert nicht.", - "Could not pull trending pages.": "Trending Seiten konnten nicht geladen werden.", - "Hidden field \"challenge\" is a required field": "Verstecktes Feld \"challenge\" ist eine erforderliche Eingabe", - "Hidden field \"token\" is a required field": "Verstecktes Feld \"token\" ist eine erforderliche Eingabe", - "Invalid challenge": "Ungültiger Test", - "Invalid token": "Ungöltige Marke", - "Invalid user": "Ungültiger Benutzer", - "Token is expired, please try again": "Marke ist abgelaufen, bitte erneut versuchen", - "English": "Englisch", - "English (auto-generated)": "Englisch (automatisch erzeugt)", - "Afrikaans": "Afrikaans", - "Albanian": "Albanisch", - "Amharic": "Amharisch", - "Arabic": "Arabisch", - "Armenian": "Armenisch", - "Azerbaijani": "Aserbaidschanisch", - "Bangla": "Bengalisch", - "Basque": "Baskisch", - "Belarusian": "Weißrussisch", - "Bosnian": "Bosnisch", - "Bulgarian": "Bulgarisch", - "Burmese": "Burmesisch", - "Catalan": "Katalanisch", - "Cebuano": "Cebuano", - "Chinese (Simplified)": "Chinesisch (vereinfacht)", - "Chinese (Traditional)": "Chinesisch (traditionell)", - "Corsican": "Korsisch", - "Croatian": "Kroatisch", - "Czech": "Tschechisch", - "Danish": "Dänisch", - "Dutch": "Niederländisch", - "Esperanto": "Esperanto", - "Estonian": "Estnisch", - "Filipino": "Philippinisch", - "Finnish": "Finnisch", - "French": "Französisch", - "Galician": "Galizisch", - "Georgian": "Georgisch", - "German": "Deutsch", - "Greek": "Griechisch", - "Gujarati": "Gujarati", - "Haitian Creole": "Haitianisches Kreolisch", - "Hausa": "Hausa", - "Hawaiian": "Hawaiianisch", - "Hebrew": "Hebräisch", - "Hindi": "Hindi", - "Hmong": "Hmong", - "Hungarian": "Ungarisch", - "Icelandic": "Isländisch", - "Igbo": "Igbo", - "Indonesian": "Indonesisch", - "Irish": "Irisch", - "Italian": "Italienisch", - "Japanese": "Japanisch", - "Javanese": "Javanisch", - "Kannada": "Kannada", - "Kazakh": "Kasachisch", - "Khmer": "Khmer", - "Korean": "Koreanisch", - "Kurdish": "Kurdisch", - "Kyrgyz": "Kirgisisch", - "Lao": "Laotisch", - "Latin": "Lateinisch", - "Latvian": "Lettisch", - "Lithuanian": "Litauisch", - "Luxembourgish": "Luxemburgisch", - "Macedonian": "Mazedonisch", - "Malagasy": "Madagassisch", - "Malay": "Malaiisch", - "Malayalam": "Malayalam", - "Maltese": "Maltesisch", - "Maori": "Maori", - "Marathi": "Marathi", - "Mongolian": "Mongolisch", - "Nepali": "Nepalesisch", - "Norwegian": "Norwegisch", - "Nyanja": "Nyanja", - "Pashto": "Paschtunisch", - "Persian": "Persisch", - "Polish": "Polnisch", - "Portuguese": "Portugiesisch", - "Punjabi": "Pandschabi", - "Romanian": "Rumänisch", - "Russian": "Russisch", - "Samoan": "Samoanisch", - "Scottish Gaelic": "Schottisches Gälisch", - "Serbian": "Serbisch", - "Shona": "Schona", - "Sindhi": "Sindhi", - "Sinhala": "Singhalesisch", - "Slovak": "Slowakisch", - "Slovenian": "Slowenisch", - "Somali": "Somali", - "Southern Sotho": "Südliches Sotho", - "Spanish": "Spanisch", - "Spanish (Latin America)": "Spanisch (Lateinamerika)", - "Sundanese": "Sundanesisch", - "Swahili": "Suaheli", - "Swedish": "Schwedisch", - "Tajik": "Tadschikisch", - "Tamil": "Tamilisch", - "Telugu": "Telugu", - "Thai": "Thailändisch", - "Turkish": "Türkisch", - "Ukrainian": "Ukrainisch", - "Urdu": "Urdu", - "Uzbek": "Usbekisch", - "Vietnamese": "Vietnamesisch", - "Welsh": "Walisisch", - "Western Frisian": "Westfriesisch", - "Xhosa": "Xhosa", - "Yiddish": "Jiddisch", - "Yoruba": "Joruba", - "Zulu": "Zulu", - "`x` years": "`x` Jahre", - "`x` months": "`x` Monate", - "`x` weeks": "`x` Wochen", - "`x` days": "`x` Tage", - "`x` hours": "`x` Stunden", - "`x` minutes": "`x` Minuten", - "`x` seconds": "`x` Sekunden", - "Fallback comments: ": "Alternative Kommentare: ", - "Popular": "Populär", - "Top": "Top", - "About": "Über", - "Rating: ": "Bewertung: ", - "Language: ": "Sprache: ", - "View as playlist": "", - "Default": "", - "Music": "", - "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", - "%A %B %-d, %Y": "", - "(edited)": "", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "", - "Playlists": "", - "Current version: ": "" + "`x` subscribers": "`x` Abonnenten", + "`x` videos": "`x` Videos", + "LIVE": "LIVE", + "Shared `x` ago": "Vor `x` geteilt", + "Unsubscribe": "Abbestellen", + "Subscribe": "Abonnieren", + "View channel on YouTube": "Kanal auf YouTube anzeigen", + "newest": "neueste", + "oldest": "älteste", + "popular": "beliebt", + "last": "", + "Next page": "Nächste Seite", + "Previous page": "Vorherige Seite", + "Clear watch history?": "Verlauf löschen?", + "Yes": "Ja", + "No": "Nein", + "Import and Export Data": "Import und Export Daten", + "Import": "Importieren", + "Import Invidious data": "Invidious Daten importieren", + "Import YouTube subscriptions": "YouTube Abonnements importieren", + "Import FreeTube subscriptions (.db)": "FreeTube Abonnements importieren (.db)", + "Import NewPipe subscriptions (.json)": "NewPipe Abonnements importieren (.json)", + "Import NewPipe data (.zip)": "NewPipe Daten importieren (.zip)", + "Export": "Exportieren", + "Export subscriptions as OPML": "Abonnements als OPML exportieren", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Abonnements als OPML exportieren (für NewPipe & FreeTube)", + "Export data as JSON": "Daten als JSON exportieren", + "Delete account?": "Account löschen?", + "History": "Verlauf", + "An alternative front-end to YouTube": "Eine alternative Oberfläche für YouTube", + "JavaScript license information": "JavaScript Lizenzinformationen", + "source": "Quelle", + "Login": "Einloggen", + "Login/Register": "Einloggen/Registrieren", + "Login to Google": "In Google einloggen", + "User ID": "Benutzer ID", + "Password": "Passwort", + "Time (h:mm:ss):": "Zeit (h:mm:ss):", + "Text CAPTCHA": "Text CAPTCHA", + "Image CAPTCHA": "Image CAPTCHA", + "Sign In": "Einloggen", + "Register": "Registrieren", + "Email": "Email", + "Google verification code": "Google Bestätigungscode", + "Preferences": "Einstellungen", + "Player preferences": "Playereinstellungen", + "Always loop: ": "Immer wiederholen: ", + "Autoplay: ": "Automatisch abspielen: ", + "Autoplay next video: ": "nächstes Video automatisch abspielen: ", + "Listen by default: ": "Nur Ton als Standard: ", + "Proxy videos? ": "", + "Default speed: ": "Standardgeschwindigkeit: ", + "Preferred video quality: ": "Bevorzugte Videoqualität: ", + "Player volume: ": "Playerlautstärke: ", + "Default comments: ": "Standardkommentare: ", + "youtube": "youtube", + "reddit": "reddit", + "Default captions: ": "Standarduntertitel: ", + "Fallback captions: ": "Ersatzuntertitel: ", + "Show related videos? ": "Ähnliche Videos anzeigen? ", + "Visual preferences": "Anzeigeeinstellungen", + "Dark mode: ": "Nachtmodus: ", + "Thin mode: ": "Schlanker Modus: ", + "Subscription preferences": "Abonnementeinstellungen", + "Redirect homepage to feed: ": "Startseite zu Feed umleiten: ", + "Number of videos shown in feed: ": "Anzahl von Videos die im Feed angezeigt werden: ", + "Sort videos by: ": "Videos sortieren nach: ", + "published": "veröffentlicht", + "published - reverse": "veröffentlicht - invertiert", + "alphabetically": "alphabetisch", + "alphabetically - reverse": "alphabetisch - invertiert", + "channel name": "Kanalname", + "channel name - reverse": "Kanalname - invertiert", + "Only show latest video from channel: ": "Nur neueste Videos des Kanals anzeigen: ", + "Only show latest unwatched video from channel: ": "Nur neueste ungesehene Videos des Kanals anzeigen: ", + "Only show unwatched: ": "Nur ungesehene anzeigen: ", + "Only show notifications (if there are any): ": "Nur Benachrichtigungen anzeigen (wenn es welche gibt): ", + "Data preferences": "Dateneinstellungen", + "Clear watch history": "Verlauf löschen", + "Import/Export data": "Daten im- exportieren", + "Manage subscriptions": "Abonnements verwalten", + "Watch history": "Verlauf", + "Delete account": "Account löschen", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Report statistics? ": "", + "Save preferences": "Einstellungen speichern", + "Subscription manager": "Abonnementverwaltung", + "`x` subscriptions": "`x` Abonnements", + "Import/Export": "Importieren/Exportieren", + "unsubscribe": "abbestellen", + "Subscriptions": "Abonnements", + "`x` unseen notifications": "`x` ungesehene Benachrichtigungen", + "search": "Suchen", + "Sign out": "Abmelden", + "Released under the AGPLv3 by Omar Roth.": "Veröffentlicht unter AGPLv3 von Omar Roth.", + "Source available here.": "Quellcode verfügbar hier.", + "View JavaScript license information.": "Javascript Lizenzinformationen anzeigen.", + "View privacy policy.": "", + "Trending": "Trending", + "Unlisted": "", + "Watch video on Youtube": "Video auf YouTube ansehen", + "Genre: ": "Genre: ", + "License: ": "Lizenz: ", + "Family friendly? ": "Familienfreundlich? ", + "Wilson score: ": "Wilson-Score: ", + "Engagement: ": "Engagement: ", + "Whitelisted regions: ": "Erlaubte Regionen: ", + "Blacklisted regions: ": "Unerlaubte Regionen: ", + "Shared `x`": "Geteilt `x`", + "`x` views": "", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.", + "View YouTube comments": "YouTube Kommentare anzeigen", + "View more comments on Reddit": "Mehr Kommentare auf Reddit anzeigen", + "View `x` comments": "`x` Kommentare anzeigen", + "View Reddit comments": "Reddit Kommentare anzeigen", + "Hide replies": "Antworten verstecken", + "Show replies": "Antworten anzeigen", + "Incorrect password": "Falsches Passwort", + "Quota exceeded, try again in a few hours": "Kontingent überschritten, versuche es in ein paar Stunden erneut", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Login nicht möglich, stellen Sie sicher dass two-factor Authentifikation (Authentifizierung oder SMS) aktiviert ist.", + "Invalid TFA code": "Ungültiger TFA Code", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login fehlgeschlagen. Das kann daran liegen dass two-factor Authentifizierung in ihrem Account nicht aktiviert ist.", + "Invalid answer": "Ungültige Antwort", + "Invalid CAPTCHA": "Ungültiges CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA ist eine erforderliche Eingabe", + "User ID is a required field": "Benutzer ID ist eine erforderliche Eingabe", + "Password is a required field": "Passwort ist eine erforderliche Eingabe", + "Invalid username or password": "Ungültiger Benutzername oder Passwort", + "Please sign in using 'Sign in with Google'": "Bitte melden sie sich mit 'Mit Google anmelden' an", + "Password cannot be empty": "Passwort darf nicht leer sein", + "Password cannot be longer than 55 characters": "Passwort darf nicht länger als 55 Zeichen sein", + "Please sign in": "Bitte anmelden", + "Invidious Private Feed for `x`": "Invidious Persönlicher Feed für `x`", + "channel:`x`": "Kanal:`x`", + "Deleted or invalid channel": "Gelöschter oder ungültiger Kanal", + "This channel does not exist.": "Dieser Kanal existiert nicht.", + "Could not get channel info.": "Kanalinformationen konnten nicht geladen werden.", + "Could not fetch comments": "Kommentare konnten nicht geladen werden", + "View `x` replies": "Zeige `x` Antworten", + "`x` ago": "vor `x`", + "Load more": "Mehr laden", + "`x` points": "`x` Punkte", + "Could not create mix.": "Mix konnte nicht erstellt werden.", + "Playlist is empty": "Playlist ist leer", + "Invalid playlist.": "Ungültige Playlist.", + "Playlist does not exist.": "Playlist existiert nicht.", + "Could not pull trending pages.": "Trending Seiten konnten nicht geladen werden.", + "Hidden field \"challenge\" is a required field": "Verstecktes Feld \"challenge\" ist eine erforderliche Eingabe", + "Hidden field \"token\" is a required field": "Verstecktes Feld \"token\" ist eine erforderliche Eingabe", + "Invalid challenge": "Ungültiger Test", + "Invalid token": "Ungöltige Marke", + "Invalid user": "Ungültiger Benutzer", + "Token is expired, please try again": "Marke ist abgelaufen, bitte erneut versuchen", + "English": "Englisch", + "English (auto-generated)": "Englisch (automatisch erzeugt)", + "Afrikaans": "Afrikaans", + "Albanian": "Albanisch", + "Amharic": "Amharisch", + "Arabic": "Arabisch", + "Armenian": "Armenisch", + "Azerbaijani": "Aserbaidschanisch", + "Bangla": "Bengalisch", + "Basque": "Baskisch", + "Belarusian": "Weißrussisch", + "Bosnian": "Bosnisch", + "Bulgarian": "Bulgarisch", + "Burmese": "Burmesisch", + "Catalan": "Katalanisch", + "Cebuano": "Cebuano", + "Chinese (Simplified)": "Chinesisch (vereinfacht)", + "Chinese (Traditional)": "Chinesisch (traditionell)", + "Corsican": "Korsisch", + "Croatian": "Kroatisch", + "Czech": "Tschechisch", + "Danish": "Dänisch", + "Dutch": "Niederländisch", + "Esperanto": "Esperanto", + "Estonian": "Estnisch", + "Filipino": "Philippinisch", + "Finnish": "Finnisch", + "French": "Französisch", + "Galician": "Galizisch", + "Georgian": "Georgisch", + "German": "Deutsch", + "Greek": "Griechisch", + "Gujarati": "Gujarati", + "Haitian Creole": "Haitianisches Kreolisch", + "Hausa": "Hausa", + "Hawaiian": "Hawaiianisch", + "Hebrew": "Hebräisch", + "Hindi": "Hindi", + "Hmong": "Hmong", + "Hungarian": "Ungarisch", + "Icelandic": "Isländisch", + "Igbo": "Igbo", + "Indonesian": "Indonesisch", + "Irish": "Irisch", + "Italian": "Italienisch", + "Japanese": "Japanisch", + "Javanese": "Javanisch", + "Kannada": "Kannada", + "Kazakh": "Kasachisch", + "Khmer": "Khmer", + "Korean": "Koreanisch", + "Kurdish": "Kurdisch", + "Kyrgyz": "Kirgisisch", + "Lao": "Laotisch", + "Latin": "Lateinisch", + "Latvian": "Lettisch", + "Lithuanian": "Litauisch", + "Luxembourgish": "Luxemburgisch", + "Macedonian": "Mazedonisch", + "Malagasy": "Madagassisch", + "Malay": "Malaiisch", + "Malayalam": "Malayalam", + "Maltese": "Maltesisch", + "Maori": "Maori", + "Marathi": "Marathi", + "Mongolian": "Mongolisch", + "Nepali": "Nepalesisch", + "Norwegian": "Norwegisch", + "Nyanja": "Nyanja", + "Pashto": "Paschtunisch", + "Persian": "Persisch", + "Polish": "Polnisch", + "Portuguese": "Portugiesisch", + "Punjabi": "Pandschabi", + "Romanian": "Rumänisch", + "Russian": "Russisch", + "Samoan": "Samoanisch", + "Scottish Gaelic": "Schottisches Gälisch", + "Serbian": "Serbisch", + "Shona": "Schona", + "Sindhi": "Sindhi", + "Sinhala": "Singhalesisch", + "Slovak": "Slowakisch", + "Slovenian": "Slowenisch", + "Somali": "Somali", + "Southern Sotho": "Südliches Sotho", + "Spanish": "Spanisch", + "Spanish (Latin America)": "Spanisch (Lateinamerika)", + "Sundanese": "Sundanesisch", + "Swahili": "Suaheli", + "Swedish": "Schwedisch", + "Tajik": "Tadschikisch", + "Tamil": "Tamilisch", + "Telugu": "Telugu", + "Thai": "Thailändisch", + "Turkish": "Türkisch", + "Ukrainian": "Ukrainisch", + "Urdu": "Urdu", + "Uzbek": "Usbekisch", + "Vietnamese": "Vietnamesisch", + "Welsh": "Walisisch", + "Western Frisian": "Westfriesisch", + "Xhosa": "Xhosa", + "Yiddish": "Jiddisch", + "Yoruba": "Joruba", + "Zulu": "Zulu", + "`x` years": "`x` Jahre", + "`x` months": "`x` Monate", + "`x` weeks": "`x` Wochen", + "`x` days": "`x` Tage", + "`x` hours": "`x` Stunden", + "`x` minutes": "`x` Minuten", + "`x` seconds": "`x` Sekunden", + "Fallback comments: ": "Alternative Kommentare: ", + "Popular": "Populär", + "Top": "Top", + "About": "Über", + "Rating: ": "Bewertung: ", + "Language: ": "Sprache: ", + "View as playlist": "", + "Default": "", + "Music": "", + "Gaming": "", + "News": "", + "Movies": "", + "Download": "", + "Download as: ": "", + "%A %B %-d, %Y": "", + "(edited)": "", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "", + "Videos": "", + "Playlists": "", + "Current version: ": "" } diff --git a/locales/en-US.json b/locales/en-US.json index 2b817dd7..4fbdbcc9 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` subscribers", - "`x` videos": "`x` videos", - "LIVE": "LIVE", - "Shared `x` ago": "Shared `x` ago", - "Unsubscribe": "Unsubscribe", - "Subscribe": "Subscribe", - "View channel on YouTube": "View channel on YouTube", - "newest": "newest", - "oldest": "oldest", - "popular": "popular", - "last": "last", - "Next page": "Next page", - "Previous page": "Previous page", - "Clear watch history?": "Clear watch history?", - "Yes": "Yes", - "No": "No", - "Import and Export Data": "Import and Export Data", - "Import": "Import", - "Import Invidious data": "Import Invidious data", - "Import YouTube subscriptions": "Import YouTube subscriptions", - "Import FreeTube subscriptions (.db)": "Import FreeTube subscriptions (.db)", - "Import NewPipe subscriptions (.json)": "Import NewPipe subscriptions (.json)", - "Import NewPipe data (.zip)": "Import NewPipe data (.zip)", - "Export": "Export", - "Export subscriptions as OPML": "Export subscriptions as OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Export subscriptions as OPML (for NewPipe & FreeTube)", - "Export data as JSON": "Export data as JSON", - "Delete account?": "Delete account?", - "History": "History", - "An alternative front-end to YouTube": "An alternative front-end to YouTube", - "JavaScript license information": "JavaScript license information", - "source": "source", - "Login": "Login", - "Login/Register": "Login/Register", - "Login to Google": "Login to Google", - "User ID": "User ID", - "Password": "Password", - "Time (h:mm:ss):": "Time (h:mm:ss):", - "Text CAPTCHA": "Text CAPTCHA", - "Image CAPTCHA": "Image CAPTCHA", - "Sign In": "Sign In", - "Register": "Register", - "Email": "Email", - "Google verification code": "Google verification code", - "Preferences": "Preferences", - "Player preferences": "Player preferences", - "Always loop: ": "Always loop: ", - "Autoplay: ": "Autoplay: ", - "Autoplay next video: ": "Autoplay next video: ", - "Listen by default: ": "Listen by default: ", - "Proxy videos? ": "Proxy videos? ", - "Default speed: ": "Default speed: ", - "Preferred video quality: ": "Preferred video quality: ", - "Player volume: ": "Player volume: ", - "Default comments: ": "Default comments: ", - "Default captions: ": "Default captions: ", - "Fallback captions: ": "Fallback captions: ", - "Show related videos? ": "Show related videos? ", - "Visual preferences": "Visual preferences", - "Dark mode: ": "Dark mode: ", - "Thin mode: ": "Thin mode: ", - "Subscription preferences": "Subscription preferences", - "Redirect homepage to feed: ": "Redirect homepage to feed: ", - "Number of videos shown in feed: ": "Number of videos shown in feed: ", - "Sort videos by: ": "Sort videos by: ", - "published": "published", - "published - reverse": "published - reverse", - "alphabetically": "alphabetically", - "alphabetically - reverse": "alphabetically - reverse", - "channel name": "channel name", - "channel name - reverse": "channel name - reverse", - "Only show latest video from channel: ": "Only show latest video from channel: ", - "Only show latest unwatched video from channel: ": "Only show latest unwatched video from channel: ", - "Only show unwatched: ": "Only show unwatched: ", - "Only show notifications (if there are any): ": "Only show notifications (if there are any): ", - "Data preferences": "Data preferences", - "Clear watch history": "Clear watch history", - "Import/Export data": "Import/Export data", - "Manage subscriptions": "Manage subscriptions", - "Watch history": "Watch history", - "Delete account": "Delete account", - "Administrator preferences": "Administrator preferences", - "Default homepage: ": "Default homepage: ", - "Feed menu: ": "Feed menu: ", - "Top enabled? ": "Top enabled? ", - "CAPTCHA enabled? ": "CAPTCHA enabled? ", - "Login enabled? ": "Login enabled? ", - "Registration enabled? ": "Registration enabled? ", - "Report statistics? ": "Report statistics? ", - "Save preferences": "Save preferences", - "Subscription manager": "Subscription manager", - "`x` subscriptions": "`x` subscriptions", - "Import/Export": "Import/Export", - "unsubscribe": "unsubscribe", - "Subscriptions": "Subscriptions", - "`x` unseen notifications": "`x` unseen notifications", - "search": "search", - "Sign out": "Sign out", - "Released under the AGPLv3 by Omar Roth.": "Released under the AGPLv3 by Omar Roth.", - "Source available here.": "Source available here.", - "View JavaScript license information.": "View JavaScript license information.", - "View privacy policy.": "View privacy policy.", - "Trending": "Trending", - "Unlisted": "Unlisted", - "Watch video on Youtube": "Watch video on Youtube", - "Genre: ": "Genre: ", - "License: ": "License: ", - "Family friendly? ": "Family friendly? ", - "Wilson score: ": "Wilson score: ", - "Engagement: ": "Engagement: ", - "Whitelisted regions: ": "Whitelisted regions: ", - "Blacklisted regions: ": "Blacklisted regions: ", - "Shared `x`": "Shared `x`", - "`x` views": "`x` views", - "Premieres in `x`": "Premieres in `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.", - "View YouTube comments": "View YouTube comments", - "View more comments on Reddit": "View more comments on Reddit", - "View `x` comments": "View `x` comments", - "View Reddit comments": "View Reddit comments", - "Hide replies": "Hide replies", - "Show replies": "Show replies", - "Incorrect password": "Incorrect password", - "Quota exceeded, try again in a few hours": "Quota exceeded, try again in a few hours", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.", - "Invalid TFA code": "Invalid TFA code", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login failed. This may be because two-factor authentication is not enabled on your account.", - "Invalid answer": "Invalid answer", - "Invalid CAPTCHA": "Invalid CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA is a required field", - "User ID is a required field": "User ID is a required field", - "Password is a required field": "Password is a required field", - "Invalid username or password": "Invalid username or password", - "Please sign in using 'Sign in with Google'": "Please sign in using 'Sign in with Google'", - "Password cannot be empty": "Password cannot be empty", - "Password cannot be longer than 55 characters": "Password cannot be longer than 55 characters", - "Please sign in": "Please sign in", - "Invidious Private Feed for `x`": "Invidious Private Feed for `x`", - "channel:`x`": "channel:`x`", - "Deleted or invalid channel": "Deleted or invalid channel", - "This channel does not exist.": "This channel does not exist.", - "Could not get channel info.": "Could not get channel info.", - "Could not fetch comments": "Could not fetch comments", - "View `x` replies": "View `x` replies", - "`x` ago": "`x` ago", - "Load more": "Load more", - "`x` points": "`x` points", - "Could not create mix.": "Could not create mix.", - "Playlist is empty": "Playlist is empty", - "Invalid playlist.": "Invalid playlist.", - "Playlist does not exist.": "Playlist does not exist.", - "Could not pull trending pages.": "Could not pull trending pages.", - "Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field", - "Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field", - "Invalid challenge": "Invalid challenge", - "Invalid token": "Invalid token", - "Invalid user": "Invalid user", - "Token is expired, please try again": "Token is expired, please try again", - "English": "English", - "English (auto-generated)": "English (auto-generated)", - "Afrikaans": "Afrikaans", - "Albanian": "Albanian", - "Amharic": "Amharic", - "Arabic": "Arabic", - "Armenian": "Armenian", - "Azerbaijani": "Azerbaijani", - "Bangla": "Bangla", - "Basque": "Basque", - "Belarusian": "Belarusian", - "Bosnian": "Bosnian", - "Bulgarian": "Bulgarian", - "Burmese": "Burmese", - "Catalan": "Catalan", - "Cebuano": "Cebuano", - "Chinese (Simplified)": "Chinese (Simplified)", - "Chinese (Traditional)": "Chinese (Traditional)", - "Corsican": "Corsican", - "Croatian": "Croatian", - "Czech": "Czech", - "Danish": "Danish", - "Dutch": "Dutch", - "Esperanto": "Esperanto", - "Estonian": "Estonian", - "Filipino": "Filipino", - "Finnish": "Finnish", - "French": "French", - "Galician": "Galician", - "Georgian": "Georgian", - "German": "German", - "Greek": "Greek", - "Gujarati": "Gujarati", - "Haitian Creole": "Haitian Creole", - "Hausa": "Hausa", - "Hawaiian": "Hawaiian", - "Hebrew": "Hebrew", - "Hindi": "Hindi", - "Hmong": "Hmong", - "Hungarian": "Hungarian", - "Icelandic": "Icelandic", - "Igbo": "Igbo", - "Indonesian": "Indonesian", - "Irish": "Irish", - "Italian": "Italian", - "Japanese": "Japanese", - "Javanese": "Javanese", - "Kannada": "Kannada", - "Kazakh": "Kazakh", - "Khmer": "Khmer", - "Korean": "Korean", - "Kurdish": "Kurdish", - "Kyrgyz": "Kyrgyz", - "Lao": "Lao", - "Latin": "Latin", - "Latvian": "Latvian", - "Lithuanian": "Lithuanian", - "Luxembourgish": "Luxembourgish", - "Macedonian": "Macedonian", - "Malagasy": "Malagasy", - "Malay": "Malay", - "Malayalam": "Malayalam", - "Maltese": "Maltese", - "Maori": "Maori", - "Marathi": "Marathi", - "Mongolian": "Mongolian", - "Nepali": "Nepali", - "Norwegian": "Norwegian", - "Nyanja": "Nyanja", - "Pashto": "Pashto", - "Persian": "Persian", - "Polish": "Polish", - "Portuguese": "Portuguese", - "Punjabi": "Punjabi", - "Romanian": "Romanian", - "Russian": "Russian", - "Samoan": "Samoan", - "Scottish Gaelic": "Scottish Gaelic", - "Serbian": "Serbian", - "Shona": "Shona", - "Sindhi": "Sindhi", - "Sinhala": "Sinhala", - "Slovak": "Slovak", - "Slovenian": "Slovenian", - "Somali": "Somali", - "Southern Sotho": "Southern Sotho", - "Spanish": "Spanish", - "Spanish (Latin America)": "Spanish (Latin America)", - "Sundanese": "Sundanese", - "Swahili": "Swahili", - "Swedish": "Swedish", - "Tajik": "Tajik", - "Tamil": "Tamil", - "Telugu": "Telugu", - "Thai": "Thai", - "Turkish": "Turkish", - "Ukrainian": "Ukrainian", - "Urdu": "Urdu", - "Uzbek": "Uzbek", - "Vietnamese": "Vietnamese", - "Welsh": "Welsh", - "Western Frisian": "Western Frisian", - "Xhosa": "Xhosa", - "Yiddish": "Yiddish", - "Yoruba": "Yoruba", - "Zulu": "Zulu", - "`x` years": "`x` years", - "`x` months": "`x` months", - "`x` weeks": "`x` weeks", - "`x` days": "`x` days", - "`x` hours": "`x` hours", - "`x` minutes": "`x` minutes", - "`x` seconds": "`x` seconds", - "Fallback comments: ": "Fallback comments: ", - "Popular": "Popular", - "Top": "Top", - "About": "About", - "Rating: ": "Rating: ", - "Language: ": "Language: ", - "View as playlist": "View as playlist", - "Default": "Default", - "Music": "Music", - "Gaming": "Gaming", - "News": "News", - "Movies": "Movies", - "Download": "Download", - "Download as: ": "Download as: ", - "%A %B %-d, %Y": "%A %B %-d, %Y", - "(edited)": "(edited)", - "Youtube permalink of the comment": "Youtube permalink of the comment", - "`x` marked it with a ❤": "`x` marked it with a ❤", - "Audio mode": "Audio mode", - "Video mode": "Video mode", - "Videos": "Videos", - "Playlists": "Playlists", - "Current version: ": "Current version: " + "`x` subscribers": "`x` subscribers", + "`x` videos": "`x` videos", + "LIVE": "LIVE", + "Shared `x` ago": "Shared `x` ago", + "Unsubscribe": "Unsubscribe", + "Subscribe": "Subscribe", + "View channel on YouTube": "View channel on YouTube", + "newest": "newest", + "oldest": "oldest", + "popular": "popular", + "last": "last", + "Next page": "Next page", + "Previous page": "Previous page", + "Clear watch history?": "Clear watch history?", + "Yes": "Yes", + "No": "No", + "Import and Export Data": "Import and Export Data", + "Import": "Import", + "Import Invidious data": "Import Invidious data", + "Import YouTube subscriptions": "Import YouTube subscriptions", + "Import FreeTube subscriptions (.db)": "Import FreeTube subscriptions (.db)", + "Import NewPipe subscriptions (.json)": "Import NewPipe subscriptions (.json)", + "Import NewPipe data (.zip)": "Import NewPipe data (.zip)", + "Export": "Export", + "Export subscriptions as OPML": "Export subscriptions as OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Export subscriptions as OPML (for NewPipe & FreeTube)", + "Export data as JSON": "Export data as JSON", + "Delete account?": "Delete account?", + "History": "History", + "An alternative front-end to YouTube": "An alternative front-end to YouTube", + "JavaScript license information": "JavaScript license information", + "source": "source", + "Login": "Login", + "Login/Register": "Login/Register", + "Login to Google": "Login to Google", + "User ID": "User ID", + "Password": "Password", + "Time (h:mm:ss):": "Time (h:mm:ss):", + "Text CAPTCHA": "Text CAPTCHA", + "Image CAPTCHA": "Image CAPTCHA", + "Sign In": "Sign In", + "Register": "Register", + "Email": "Email", + "Google verification code": "Google verification code", + "Preferences": "Preferences", + "Player preferences": "Player preferences", + "Always loop: ": "Always loop: ", + "Autoplay: ": "Autoplay: ", + "Autoplay next video: ": "Autoplay next video: ", + "Listen by default: ": "Listen by default: ", + "Proxy videos? ": "Proxy videos? ", + "Default speed: ": "Default speed: ", + "Preferred video quality: ": "Preferred video quality: ", + "Player volume: ": "Player volume: ", + "Default comments: ": "Default comments: ", + "Default captions: ": "Default captions: ", + "Fallback captions: ": "Fallback captions: ", + "Show related videos? ": "Show related videos? ", + "Visual preferences": "Visual preferences", + "Dark mode: ": "Dark mode: ", + "Thin mode: ": "Thin mode: ", + "Subscription preferences": "Subscription preferences", + "Redirect homepage to feed: ": "Redirect homepage to feed: ", + "Number of videos shown in feed: ": "Number of videos shown in feed: ", + "Sort videos by: ": "Sort videos by: ", + "published": "published", + "published - reverse": "published - reverse", + "alphabetically": "alphabetically", + "alphabetically - reverse": "alphabetically - reverse", + "channel name": "channel name", + "channel name - reverse": "channel name - reverse", + "Only show latest video from channel: ": "Only show latest video from channel: ", + "Only show latest unwatched video from channel: ": "Only show latest unwatched video from channel: ", + "Only show unwatched: ": "Only show unwatched: ", + "Only show notifications (if there are any): ": "Only show notifications (if there are any): ", + "Data preferences": "Data preferences", + "Clear watch history": "Clear watch history", + "Import/Export data": "Import/Export data", + "Manage subscriptions": "Manage subscriptions", + "Watch history": "Watch history", + "Delete account": "Delete account", + "Administrator preferences": "Administrator preferences", + "Default homepage: ": "Default homepage: ", + "Feed menu: ": "Feed menu: ", + "Top enabled? ": "Top enabled? ", + "CAPTCHA enabled? ": "CAPTCHA enabled? ", + "Login enabled? ": "Login enabled? ", + "Registration enabled? ": "Registration enabled? ", + "Report statistics? ": "Report statistics? ", + "Save preferences": "Save preferences", + "Subscription manager": "Subscription manager", + "`x` subscriptions": "`x` subscriptions", + "Import/Export": "Import/Export", + "unsubscribe": "unsubscribe", + "Subscriptions": "Subscriptions", + "`x` unseen notifications": "`x` unseen notifications", + "search": "search", + "Sign out": "Sign out", + "Released under the AGPLv3 by Omar Roth.": "Released under the AGPLv3 by Omar Roth.", + "Source available here.": "Source available here.", + "View JavaScript license information.": "View JavaScript license information.", + "View privacy policy.": "View privacy policy.", + "Trending": "Trending", + "Unlisted": "Unlisted", + "Watch video on Youtube": "Watch video on Youtube", + "Genre: ": "Genre: ", + "License: ": "License: ", + "Family friendly? ": "Family friendly? ", + "Wilson score: ": "Wilson score: ", + "Engagement: ": "Engagement: ", + "Whitelisted regions: ": "Whitelisted regions: ", + "Blacklisted regions: ": "Blacklisted regions: ", + "Shared `x`": "Shared `x`", + "`x` views": "`x` views", + "Premieres in `x`": "Premieres in `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.", + "View YouTube comments": "View YouTube comments", + "View more comments on Reddit": "View more comments on Reddit", + "View `x` comments": "View `x` comments", + "View Reddit comments": "View Reddit comments", + "Hide replies": "Hide replies", + "Show replies": "Show replies", + "Incorrect password": "Incorrect password", + "Quota exceeded, try again in a few hours": "Quota exceeded, try again in a few hours", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.", + "Invalid TFA code": "Invalid TFA code", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login failed. This may be because two-factor authentication is not enabled on your account.", + "Invalid answer": "Invalid answer", + "Invalid CAPTCHA": "Invalid CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA is a required field", + "User ID is a required field": "User ID is a required field", + "Password is a required field": "Password is a required field", + "Invalid username or password": "Invalid username or password", + "Please sign in using 'Sign in with Google'": "Please sign in using 'Sign in with Google'", + "Password cannot be empty": "Password cannot be empty", + "Password cannot be longer than 55 characters": "Password cannot be longer than 55 characters", + "Please sign in": "Please sign in", + "Invidious Private Feed for `x`": "Invidious Private Feed for `x`", + "channel:`x`": "channel:`x`", + "Deleted or invalid channel": "Deleted or invalid channel", + "This channel does not exist.": "This channel does not exist.", + "Could not get channel info.": "Could not get channel info.", + "Could not fetch comments": "Could not fetch comments", + "View `x` replies": "View `x` replies", + "`x` ago": "`x` ago", + "Load more": "Load more", + "`x` points": "`x` points", + "Could not create mix.": "Could not create mix.", + "Playlist is empty": "Playlist is empty", + "Invalid playlist.": "Invalid playlist.", + "Playlist does not exist.": "Playlist does not exist.", + "Could not pull trending pages.": "Could not pull trending pages.", + "Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field", + "Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field", + "Invalid challenge": "Invalid challenge", + "Invalid token": "Invalid token", + "Invalid user": "Invalid user", + "Token is expired, please try again": "Token is expired, please try again", + "English": "English", + "English (auto-generated)": "English (auto-generated)", + "Afrikaans": "Afrikaans", + "Albanian": "Albanian", + "Amharic": "Amharic", + "Arabic": "Arabic", + "Armenian": "Armenian", + "Azerbaijani": "Azerbaijani", + "Bangla": "Bangla", + "Basque": "Basque", + "Belarusian": "Belarusian", + "Bosnian": "Bosnian", + "Bulgarian": "Bulgarian", + "Burmese": "Burmese", + "Catalan": "Catalan", + "Cebuano": "Cebuano", + "Chinese (Simplified)": "Chinese (Simplified)", + "Chinese (Traditional)": "Chinese (Traditional)", + "Corsican": "Corsican", + "Croatian": "Croatian", + "Czech": "Czech", + "Danish": "Danish", + "Dutch": "Dutch", + "Esperanto": "Esperanto", + "Estonian": "Estonian", + "Filipino": "Filipino", + "Finnish": "Finnish", + "French": "French", + "Galician": "Galician", + "Georgian": "Georgian", + "German": "German", + "Greek": "Greek", + "Gujarati": "Gujarati", + "Haitian Creole": "Haitian Creole", + "Hausa": "Hausa", + "Hawaiian": "Hawaiian", + "Hebrew": "Hebrew", + "Hindi": "Hindi", + "Hmong": "Hmong", + "Hungarian": "Hungarian", + "Icelandic": "Icelandic", + "Igbo": "Igbo", + "Indonesian": "Indonesian", + "Irish": "Irish", + "Italian": "Italian", + "Japanese": "Japanese", + "Javanese": "Javanese", + "Kannada": "Kannada", + "Kazakh": "Kazakh", + "Khmer": "Khmer", + "Korean": "Korean", + "Kurdish": "Kurdish", + "Kyrgyz": "Kyrgyz", + "Lao": "Lao", + "Latin": "Latin", + "Latvian": "Latvian", + "Lithuanian": "Lithuanian", + "Luxembourgish": "Luxembourgish", + "Macedonian": "Macedonian", + "Malagasy": "Malagasy", + "Malay": "Malay", + "Malayalam": "Malayalam", + "Maltese": "Maltese", + "Maori": "Maori", + "Marathi": "Marathi", + "Mongolian": "Mongolian", + "Nepali": "Nepali", + "Norwegian": "Norwegian", + "Nyanja": "Nyanja", + "Pashto": "Pashto", + "Persian": "Persian", + "Polish": "Polish", + "Portuguese": "Portuguese", + "Punjabi": "Punjabi", + "Romanian": "Romanian", + "Russian": "Russian", + "Samoan": "Samoan", + "Scottish Gaelic": "Scottish Gaelic", + "Serbian": "Serbian", + "Shona": "Shona", + "Sindhi": "Sindhi", + "Sinhala": "Sinhala", + "Slovak": "Slovak", + "Slovenian": "Slovenian", + "Somali": "Somali", + "Southern Sotho": "Southern Sotho", + "Spanish": "Spanish", + "Spanish (Latin America)": "Spanish (Latin America)", + "Sundanese": "Sundanese", + "Swahili": "Swahili", + "Swedish": "Swedish", + "Tajik": "Tajik", + "Tamil": "Tamil", + "Telugu": "Telugu", + "Thai": "Thai", + "Turkish": "Turkish", + "Ukrainian": "Ukrainian", + "Urdu": "Urdu", + "Uzbek": "Uzbek", + "Vietnamese": "Vietnamese", + "Welsh": "Welsh", + "Western Frisian": "Western Frisian", + "Xhosa": "Xhosa", + "Yiddish": "Yiddish", + "Yoruba": "Yoruba", + "Zulu": "Zulu", + "`x` years": "`x` years", + "`x` months": "`x` months", + "`x` weeks": "`x` weeks", + "`x` days": "`x` days", + "`x` hours": "`x` hours", + "`x` minutes": "`x` minutes", + "`x` seconds": "`x` seconds", + "Fallback comments: ": "Fallback comments: ", + "Popular": "Popular", + "Top": "Top", + "About": "About", + "Rating: ": "Rating: ", + "Language: ": "Language: ", + "View as playlist": "View as playlist", + "Default": "Default", + "Music": "Music", + "Gaming": "Gaming", + "News": "News", + "Movies": "Movies", + "Download": "Download", + "Download as: ": "Download as: ", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "(edited)": "(edited)", + "Youtube permalink of the comment": "Youtube permalink of the comment", + "`x` marked it with a ❤": "`x` marked it with a ❤", + "Audio mode": "Audio mode", + "Video mode": "Video mode", + "Videos": "Videos", + "Playlists": "Playlists", + "Current version: ": "Current version: " } diff --git a/locales/eo.json b/locales/eo.json index 39dc9acd..0e122a13 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -1,295 +1,295 @@ { - "`x` subscribers": "`x` abonantoj", - "`x` videos": "`x` videoj", - "LIVE": "NUNA", - "Shared `x` ago": "Konigita antaŭ `x`", - "Unsubscribe": "Malaboni", - "Subscribe": "Aboni", - "Login to subscribe to `x`": "Ensaluti por aboni je `x`", - "View channel on YouTube": "Vidi kanalon en YouTube", - "newest": "pli novaj", - "oldest": "pli malnovaj", - "popular": "popularaj", - "last": "lasta", - "Next page": "Sekva paĝo", - "Previous page": "Antaŭa paĝo", - "Clear watch history?": "Ĉu forigi vidohistorion?", - "Yes": "Jes", - "No": "Ne", - "Import and Export Data": "Importi kaj Eksporti Datumojn", - "Import": "Importi", - "Import Invidious data": "Importi datumojn de Invidious", - "Import YouTube subscriptions": "Importi abonojn de YouTube", - "Import FreeTube subscriptions (.db)": "Importi abonojn de FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Importi abonojn de NewPipe (.json)", - "Import NewPipe data (.zip)": "Importi datumojn de NewPipe (.zip)", - "Export": "Eksporti", - "Export subscriptions as OPML": "Eksporti abonojn kiel OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporti abonojn kiel OPML (por NewPipe kaj FreeTube)", - "Export data as JSON": "Eksporti datumojn kiel JSON", - "Delete account?": "Ĉu forigi konton?", - "History": "Historio", - "An alternative front-end to YouTube": "Alternativa fasado al YouTube", - "JavaScript license information": "Ĝavoskripta licenca informo", - "source": "fonto", - "Login": "Ensaluti", - "Login/Register": "Ensaluti/Registriĝi", - "Login to Google": "Ensaluti al Google", - "User ID:": "Uzula identigilo:", - "Password:": "Pasvorto:", - "Time (h:mm:ss):": "Horo (h:mm:ss):", - "Text CAPTCHA": "Teksta CAPTCHA", - "Image CAPTCHA": "Bilda CAPTCHA", - "Sign In": "Ensaluti", - "Register": "Registriĝi", - "Email:": "Retpoŝto:", - "Google verification code:": "Kontrolkodo de Google:", - "Preferences": "Agordoj", - "Player preferences": "Spektilaj agordoj", - "Always loop: ": "Ĉiam ripeti: ", - "Autoplay: ": "Aŭtomate ludi: ", - "Autoplay next video: ": "Aŭtomate ludi sekvan videon: ", - "Listen by default: ": "Aŭskulti defaŭlte: ", - "Proxy videos? ": "Ĉu uzi prokuran servilon por videoj? ", - "Default speed: ": "Defaŭlta rapido: ", - "Preferred video quality: ": "Preferita videkvalito: ", - "Player volume: ": "Ludila sonforteco: ", - "Default comments: ": "Defaŭltaj komentoj: ", - "Default captions: ": "Defaŭltaj subtekstoj: ", - "Fallback captions: ": "Retrodefaŭltaj subtekstoj: ", - "Show related videos? ": "Ĉu montri rilatajn videojn? ", - "Visual preferences": "Vidaj preferoj", - "Dark mode: ": "Malhela reĝimo: ", - "Thin mode: ": "Maldika reĝimo: ", - "Subscription preferences": "Abonaj agordoj", - "Redirect homepage to feed: ": "Alidirekti hejmpâgon al fluo: ", - "Number of videos shown in feed: ": "Nombro da videoj montritaj en fluo: ", - "Sort videos by: ": "Ordi videojn laŭ: ", - "published": "publikigo", - "published - reverse": "publitigo - renverse", - "alphabetically": "alfabete", - "alphabetically - reverse": "alfabete - renverse", - "channel name": "kanala nombro", - "channel name - reverse": "kanala nombro - renverse", - "Only show latest video from channel: ": "Nur montri pli novan videon el kanalo: ", - "Only show latest unwatched video from channel: ": "Nur montri pli novan malviditan videon el kanalo: ", - "Only show unwatched: ": "Nur montri malviditajn: ", - "Only show notifications (if there are any): ": "Nur montri sciigojn (se estas): ", - "Data preferences": "Datumagordoj", - "Clear watch history": "Forigi vidohistorion", - "Import/Export data": "Importi/Eksporti datumojn", - "Manage subscriptions": "Administri abonojn", - "Watch history": "Vidohistorio", - "Delete account": "Forigi konton", - "Administrator preferences": "Agordoj de administranto", - "Default homepage: ": "Defaŭlta hejmpaĝo: ", - "Feed menu: ": "Flua menuo: ", - "Top enabled? ": "Ĉu pli bonaj ŝaltitaj? ", - "CAPTCHA enabled? ": "Ĉu CAPTCHA ŝaltita? ", - "Login enabled? ": "Ĉu ensaluto aktivita? ", - "Registration enabled? ": "Ĉu registriĝo aktivita? ", - "Report statistics? ": "Ĉu raporti statistikojn? ", - "Save preferences": "Konservi agordojn", - "Subscription manager": "Administrilo de abonoj", - "`x` subscriptions": "`x` abonoj", - "Import/Export": "Importi/Eksporti", - "unsubscribe": "malaboni", - "Subscriptions": "Abonoj", - "`x` unseen notifications": "`x` neviditaj sciigoj", - "search": "serĉi", - "Sign out": "Elsaluti", - "Released under the AGPLv3 by Omar Roth.": "Eldonita sub la AGPLv3 de Omar Roth.", - "Source available here.": "Fonto havebla ĉi tie.", - "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", - "View privacy policy.": "Vidi regularon pri privateco.", - "Trending": "", - "Unlisted": "", - "Watch video on Youtube": "Vidi videon en Youtube", - "Genre: ": "Ĝenro: ", - "License: ": "Licenco: ", - "Family friendly? ": "", - "Wilson score: ": "", - "Engagement: ": "", - "Whitelisted regions: ": "", - "Blacklisted regions: ": "", - "Shared `x`": "Konigita `x`", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", - "View YouTube comments": "Vidi komentojn de YouTube", - "View more comments on Reddit": "Vidi pli komentoj en Reddit", - "View `x` comments": "Vidi `x` komentojn", - "View Reddit comments": "Vidi komentojn de Reddit", - "Hide replies": "Kaŝi respondojn", - "Show replies": "Montri respondojn", - "Incorrect password": "Malbona pasvorto", - "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", - "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "Nevalida respondo", - "Invalid CAPTCHA": "Nevalida CAPTCHA", - "CAPTCHA is a required field": "", - "User ID is a required field": "", - "Password is a required field": "", - "Invalid username or password": "Nevalida uzantnomo aŭ pasvorto", - "Please sign in using 'Sign in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", - "Password cannot be empty": "", - "Password cannot be longer than 55 characters": "", - "Please sign in": "Bonvolu ensaluti", - "Invidious Private Feed for `x`": "", - "channel:`x`": "kanalo:`x`", - "Deleted or invalid channel": "Forigita aŭ nevalida kanalo", - "This channel does not exist.": "Ĉi tiu kanalo ne ekzistas.", - "Could not get channel info.": "", - "Could not fetch comments": "", - "View `x` replies": "Vidi `x` respondojn", - "`x` ago": "antaŭ `x`", - "Load more": "Ŝarĝi pli", - "`x` points": "`x` poentoj", - "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", - "Playlist does not exist.": "", - "Could not pull trending pages.": "", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "Nevalida uzanto", - "Token is expired, please try again": "", - "English": "Angla", - "English (auto-generated)": "Angla (aŭtomate generita)", - "Afrikaans": "Afrikansa", - "Albanian": "Albana", - "Amharic": "Amhara", - "Arabic": "Araba", - "Armenian": "Armena", - "Azerbaijani": "Azerbajĝana", - "Bangla": "Bengala", - "Basque": "Eŭska", - "Belarusian": "Belorusa", - "Bosnian": "Bosna", - "Bulgarian": "Bulgara", - "Burmese": "Birma", - "Catalan": "Kataluna", - "Cebuano": "Cebua", - "Chinese (Simplified)": "Ĉina (simpligita)", - "Chinese (Traditional)": "Ĉina (tradicia)", - "Corsican": "Korsika", - "Croatian": "Kroata", - "Czech": "Ĉeĥa", - "Danish": "Dana", - "Dutch": "Nederlanda", - "Esperanto": "Esperanto", - "Estonian": "Estona", - "Filipino": "Filipina", - "Finnish": "Finna", - "French": "Franca", - "Galician": "Galega", - "Georgian": "Kartvela", - "German": "Germana", - "Greek": "Greka", - "Gujarati": "Guĝarata", - "Haitian Creole": "Haitia kreola", - "Hausa": "Haŭsa", - "Hawaiian": "Havaja", - "Hebrew": "Hebrea", - "Hindi": "Hindia", - "Hmong": "Miaa", - "Hungarian": "Hungara", - "Icelandic": "Islanda", - "Igbo": "Igba", - "Indonesian": "Indonezia", - "Irish": "Irlanda", - "Italian": "Itala", - "Japanese": "Japana", - "Javanese": "Java", - "Kannada": "Kanara", - "Kazakh": "Kazaĥa", - "Khmer": "Kmera", - "Korean": "Korea", - "Kurdish": "Kurda", - "Kyrgyz": "Kirgiza", - "Lao": "Laosa", - "Latin": "Latina", - "Latvian": "Latva", - "Lithuanian": "Litova", - "Luxembourgish": "Luksemburga", - "Macedonian": "Makedona", - "Malagasy": "Malagasa", - "Malay": "Malaja", - "Malayalam": "Malajala", - "Maltese": "Malta", - "Maori": "Maoria", - "Marathi": "Marata", - "Mongolian": "Mongola", - "Nepali": "Nepala", - "Norwegian": "Norvega", - "Nyanja": "Njanĝa", - "Pashto": "Paŝtuna", - "Persian": "Persa", - "Polish": "Pola", - "Portuguese": "Portugala", - "Punjabi": "Panĝaba", - "Romanian": "Rumana", - "Russian": "Rusa", - "Samoan": "Samoa", - "Scottish Gaelic": "Skotgaela", - "Serbian": "Serba", - "Shona": "Ŝona", - "Sindhi": "Sinda", - "Sinhala": "Sinhala", - "Slovak": "Slovaka", - "Slovenian": "Slovena", - "Somali": "Somala", - "Southern Sotho": "Sota", - "Spanish": "Hispana", - "Spanish (Latin America)": "Hispana (Latinameriko)", - "Sundanese": "Sunda", - "Swahili": "Svahila", - "Swedish": "Sveda", - "Tajik": "Taĝika", - "Tamil": "Tamila", - "Telugu": "Telugua", - "Thai": "Taja", - "Turkish": "Turka", - "Ukrainian": "Ukraina", - "Urdu": "Urduo", - "Uzbek": "Uzbeka", - "Vietnamese": "Vjetnama", - "Welsh": "Kimra", - "Western Frisian": "Okcidentfrisa", - "Xhosa": "Kosa", - "Yiddish": "Jida", - "Yoruba": "Joruba", - "Zulu": "Zulua", - "`x` years": "`x` jaroj", - "`x` months": "`x` monatoj", - "`x` weeks": "`x` semajnoj", - "`x` days": "`x` tagoj", - "`x` hours": "`x` horoj", - "`x` minutes": "`x` minutoj", - "`x` seconds": "`x` sekundoj", - "Fallback comments: ": "", - "Popular": "Popularaj", - "Top": "Supraj", - "About": "Pri", - "Rating: ": "", - "Language: ": "Lingvo: ", - "Default": "Defaŭlte", - "Music": "Musiko", - "Gaming": "", - "News": "Novaĵoj", - "Movies": "Filmoj", - "Download": "Elŝuti", - "Download as: ": "Elŝuti kiel: ", - "%A %B %-d, %Y": "", - "(edited)": "(redaktita)", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "Videoj", - "Playlists": "", - "Current version: ": "Nuna versio: " + "`x` subscribers": "`x` abonantoj", + "`x` videos": "`x` videoj", + "LIVE": "NUNA", + "Shared `x` ago": "Konigita antaŭ `x`", + "Unsubscribe": "Malaboni", + "Subscribe": "Aboni", + "Login to subscribe to `x`": "Ensaluti por aboni je `x`", + "View channel on YouTube": "Vidi kanalon en YouTube", + "newest": "pli novaj", + "oldest": "pli malnovaj", + "popular": "popularaj", + "last": "lasta", + "Next page": "Sekva paĝo", + "Previous page": "Antaŭa paĝo", + "Clear watch history?": "Ĉu forigi vidohistorion?", + "Yes": "Jes", + "No": "Ne", + "Import and Export Data": "Importi kaj Eksporti Datumojn", + "Import": "Importi", + "Import Invidious data": "Importi datumojn de Invidious", + "Import YouTube subscriptions": "Importi abonojn de YouTube", + "Import FreeTube subscriptions (.db)": "Importi abonojn de FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importi abonojn de NewPipe (.json)", + "Import NewPipe data (.zip)": "Importi datumojn de NewPipe (.zip)", + "Export": "Eksporti", + "Export subscriptions as OPML": "Eksporti abonojn kiel OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporti abonojn kiel OPML (por NewPipe kaj FreeTube)", + "Export data as JSON": "Eksporti datumojn kiel JSON", + "Delete account?": "Ĉu forigi konton?", + "History": "Historio", + "An alternative front-end to YouTube": "Alternativa fasado al YouTube", + "JavaScript license information": "Ĝavoskripta licenca informo", + "source": "fonto", + "Login": "Ensaluti", + "Login/Register": "Ensaluti/Registriĝi", + "Login to Google": "Ensaluti al Google", + "User ID:": "Uzula identigilo:", + "Password:": "Pasvorto:", + "Time (h:mm:ss):": "Horo (h:mm:ss):", + "Text CAPTCHA": "Teksta CAPTCHA", + "Image CAPTCHA": "Bilda CAPTCHA", + "Sign In": "Ensaluti", + "Register": "Registriĝi", + "Email:": "Retpoŝto:", + "Google verification code:": "Kontrolkodo de Google:", + "Preferences": "Agordoj", + "Player preferences": "Spektilaj agordoj", + "Always loop: ": "Ĉiam ripeti: ", + "Autoplay: ": "Aŭtomate ludi: ", + "Autoplay next video: ": "Aŭtomate ludi sekvan videon: ", + "Listen by default: ": "Aŭskulti defaŭlte: ", + "Proxy videos? ": "Ĉu uzi prokuran servilon por videoj? ", + "Default speed: ": "Defaŭlta rapido: ", + "Preferred video quality: ": "Preferita videkvalito: ", + "Player volume: ": "Ludila sonforteco: ", + "Default comments: ": "Defaŭltaj komentoj: ", + "Default captions: ": "Defaŭltaj subtekstoj: ", + "Fallback captions: ": "Retrodefaŭltaj subtekstoj: ", + "Show related videos? ": "Ĉu montri rilatajn videojn? ", + "Visual preferences": "Vidaj preferoj", + "Dark mode: ": "Malhela reĝimo: ", + "Thin mode: ": "Maldika reĝimo: ", + "Subscription preferences": "Abonaj agordoj", + "Redirect homepage to feed: ": "Alidirekti hejmpâgon al fluo: ", + "Number of videos shown in feed: ": "Nombro da videoj montritaj en fluo: ", + "Sort videos by: ": "Ordi videojn laŭ: ", + "published": "publikigo", + "published - reverse": "publitigo - renverse", + "alphabetically": "alfabete", + "alphabetically - reverse": "alfabete - renverse", + "channel name": "kanala nombro", + "channel name - reverse": "kanala nombro - renverse", + "Only show latest video from channel: ": "Nur montri pli novan videon el kanalo: ", + "Only show latest unwatched video from channel: ": "Nur montri pli novan malviditan videon el kanalo: ", + "Only show unwatched: ": "Nur montri malviditajn: ", + "Only show notifications (if there are any): ": "Nur montri sciigojn (se estas): ", + "Data preferences": "Datumagordoj", + "Clear watch history": "Forigi vidohistorion", + "Import/Export data": "Importi/Eksporti datumojn", + "Manage subscriptions": "Administri abonojn", + "Watch history": "Vidohistorio", + "Delete account": "Forigi konton", + "Administrator preferences": "Agordoj de administranto", + "Default homepage: ": "Defaŭlta hejmpaĝo: ", + "Feed menu: ": "Flua menuo: ", + "Top enabled? ": "Ĉu pli bonaj ŝaltitaj? ", + "CAPTCHA enabled? ": "Ĉu CAPTCHA ŝaltita? ", + "Login enabled? ": "Ĉu ensaluto aktivita? ", + "Registration enabled? ": "Ĉu registriĝo aktivita? ", + "Report statistics? ": "Ĉu raporti statistikojn? ", + "Save preferences": "Konservi agordojn", + "Subscription manager": "Administrilo de abonoj", + "`x` subscriptions": "`x` abonoj", + "Import/Export": "Importi/Eksporti", + "unsubscribe": "malaboni", + "Subscriptions": "Abonoj", + "`x` unseen notifications": "`x` neviditaj sciigoj", + "search": "serĉi", + "Sign out": "Elsaluti", + "Released under the AGPLv3 by Omar Roth.": "Eldonita sub la AGPLv3 de Omar Roth.", + "Source available here.": "Fonto havebla ĉi tie.", + "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", + "View privacy policy.": "Vidi regularon pri privateco.", + "Trending": "", + "Unlisted": "", + "Watch video on Youtube": "Vidi videon en Youtube", + "Genre: ": "Ĝenro: ", + "License: ": "Licenco: ", + "Family friendly? ": "", + "Wilson score: ": "", + "Engagement: ": "", + "Whitelisted regions: ": "", + "Blacklisted regions: ": "", + "Shared `x`": "Konigita `x`", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", + "View YouTube comments": "Vidi komentojn de YouTube", + "View more comments on Reddit": "Vidi pli komentoj en Reddit", + "View `x` comments": "Vidi `x` komentojn", + "View Reddit comments": "Vidi komentojn de Reddit", + "Hide replies": "Kaŝi respondojn", + "Show replies": "Montri respondojn", + "Incorrect password": "Malbona pasvorto", + "Quota exceeded, try again in a few hours": "", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", + "Invalid TFA code": "", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "", + "Invalid answer": "Nevalida respondo", + "Invalid CAPTCHA": "Nevalida CAPTCHA", + "CAPTCHA is a required field": "", + "User ID is a required field": "", + "Password is a required field": "", + "Invalid username or password": "Nevalida uzantnomo aŭ pasvorto", + "Please sign in using 'Sign in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", + "Password cannot be empty": "", + "Password cannot be longer than 55 characters": "", + "Please sign in": "Bonvolu ensaluti", + "Invidious Private Feed for `x`": "", + "channel:`x`": "kanalo:`x`", + "Deleted or invalid channel": "Forigita aŭ nevalida kanalo", + "This channel does not exist.": "Ĉi tiu kanalo ne ekzistas.", + "Could not get channel info.": "", + "Could not fetch comments": "", + "View `x` replies": "Vidi `x` respondojn", + "`x` ago": "antaŭ `x`", + "Load more": "Ŝarĝi pli", + "`x` points": "`x` poentoj", + "Could not create mix.": "", + "Playlist is empty": "", + "Invalid playlist.": "", + "Playlist does not exist.": "", + "Could not pull trending pages.": "", + "Hidden field \"challenge\" is a required field": "", + "Hidden field \"token\" is a required field": "", + "Invalid challenge": "", + "Invalid token": "", + "Invalid user": "Nevalida uzanto", + "Token is expired, please try again": "", + "English": "Angla", + "English (auto-generated)": "Angla (aŭtomate generita)", + "Afrikaans": "Afrikansa", + "Albanian": "Albana", + "Amharic": "Amhara", + "Arabic": "Araba", + "Armenian": "Armena", + "Azerbaijani": "Azerbajĝana", + "Bangla": "Bengala", + "Basque": "Eŭska", + "Belarusian": "Belorusa", + "Bosnian": "Bosna", + "Bulgarian": "Bulgara", + "Burmese": "Birma", + "Catalan": "Kataluna", + "Cebuano": "Cebua", + "Chinese (Simplified)": "Ĉina (simpligita)", + "Chinese (Traditional)": "Ĉina (tradicia)", + "Corsican": "Korsika", + "Croatian": "Kroata", + "Czech": "Ĉeĥa", + "Danish": "Dana", + "Dutch": "Nederlanda", + "Esperanto": "Esperanto", + "Estonian": "Estona", + "Filipino": "Filipina", + "Finnish": "Finna", + "French": "Franca", + "Galician": "Galega", + "Georgian": "Kartvela", + "German": "Germana", + "Greek": "Greka", + "Gujarati": "Guĝarata", + "Haitian Creole": "Haitia kreola", + "Hausa": "Haŭsa", + "Hawaiian": "Havaja", + "Hebrew": "Hebrea", + "Hindi": "Hindia", + "Hmong": "Miaa", + "Hungarian": "Hungara", + "Icelandic": "Islanda", + "Igbo": "Igba", + "Indonesian": "Indonezia", + "Irish": "Irlanda", + "Italian": "Itala", + "Japanese": "Japana", + "Javanese": "Java", + "Kannada": "Kanara", + "Kazakh": "Kazaĥa", + "Khmer": "Kmera", + "Korean": "Korea", + "Kurdish": "Kurda", + "Kyrgyz": "Kirgiza", + "Lao": "Laosa", + "Latin": "Latina", + "Latvian": "Latva", + "Lithuanian": "Litova", + "Luxembourgish": "Luksemburga", + "Macedonian": "Makedona", + "Malagasy": "Malagasa", + "Malay": "Malaja", + "Malayalam": "Malajala", + "Maltese": "Malta", + "Maori": "Maoria", + "Marathi": "Marata", + "Mongolian": "Mongola", + "Nepali": "Nepala", + "Norwegian": "Norvega", + "Nyanja": "Njanĝa", + "Pashto": "Paŝtuna", + "Persian": "Persa", + "Polish": "Pola", + "Portuguese": "Portugala", + "Punjabi": "Panĝaba", + "Romanian": "Rumana", + "Russian": "Rusa", + "Samoan": "Samoa", + "Scottish Gaelic": "Skotgaela", + "Serbian": "Serba", + "Shona": "Ŝona", + "Sindhi": "Sinda", + "Sinhala": "Sinhala", + "Slovak": "Slovaka", + "Slovenian": "Slovena", + "Somali": "Somala", + "Southern Sotho": "Sota", + "Spanish": "Hispana", + "Spanish (Latin America)": "Hispana (Latinameriko)", + "Sundanese": "Sunda", + "Swahili": "Svahila", + "Swedish": "Sveda", + "Tajik": "Taĝika", + "Tamil": "Tamila", + "Telugu": "Telugua", + "Thai": "Taja", + "Turkish": "Turka", + "Ukrainian": "Ukraina", + "Urdu": "Urduo", + "Uzbek": "Uzbeka", + "Vietnamese": "Vjetnama", + "Welsh": "Kimra", + "Western Frisian": "Okcidentfrisa", + "Xhosa": "Kosa", + "Yiddish": "Jida", + "Yoruba": "Joruba", + "Zulu": "Zulua", + "`x` years": "`x` jaroj", + "`x` months": "`x` monatoj", + "`x` weeks": "`x` semajnoj", + "`x` days": "`x` tagoj", + "`x` hours": "`x` horoj", + "`x` minutes": "`x` minutoj", + "`x` seconds": "`x` sekundoj", + "Fallback comments: ": "", + "Popular": "Popularaj", + "Top": "Supraj", + "About": "Pri", + "Rating: ": "", + "Language: ": "Lingvo: ", + "Default": "Defaŭlte", + "Music": "Musiko", + "Gaming": "", + "News": "Novaĵoj", + "Movies": "Filmoj", + "Download": "Elŝuti", + "Download as: ": "Elŝuti kiel: ", + "%A %B %-d, %Y": "", + "(edited)": "(redaktita)", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "", + "Videos": "Videoj", + "Playlists": "", + "Current version: ": "Nuna versio: " } diff --git a/locales/es.json b/locales/es.json index 5a12c7bc..e8a8e7d2 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` suscriptores", - "`x` videos": "`x` vídeos", - "LIVE": "DIRECTO", - "Shared `x` ago": "Compartido hace `x`", - "Unsubscribe": "Desuscribirse", - "Subscribe": "Suscribirse", - "View channel on YouTube": "Ver el canal en YouTube", - "newest": "más nuevos", - "oldest": "más viejos", - "popular": "populares", - "last": "último", - "Next page": "Página siguiente", - "Previous page": "Página anterior", - "Clear watch history?": "¿Quiere borrar el historial de reproducción?", - "Yes": "Sí", - "No": "No", - "Import and Export Data": "Importación y exportación de datos", - "Import": "Importar", - "Import Invidious data": "Importar datos de Invidious", - "Import YouTube subscriptions": "Importar suscripciones de YouTube", - "Import FreeTube subscriptions (.db)": "Importar suscripciones de FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Importar suscripciones de NewPipe (.json)", - "Import NewPipe data (.zip)": "Importar datos de NewPipe (.zip)", - "Export": "Exportar", - "Export subscriptions as OPML": "Exportar suscripciones como OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar suscripciones como OPML (para NewPipe y FreeTube)", - "Export data as JSON": "Exportar datos como JSON", - "Delete account?": "¿Quiere borrar la cuenta?", - "History": "Historial", - "An alternative front-end to YouTube": "Una interfaz alternativa para YouTube", - "JavaScript license information": "Información de licencia de JavaScript", - "source": "código fuente", - "Login": "Iniciar sesión", - "Login/Register": "Iniciar sesión/Registrarse", - "Login to Google": "Iniciar sesión en Google", - "User ID": "Nombre", - "Password": "Contraseña", - "Time (h:mm:ss):": "Hora (h:mm:ss):", - "Text CAPTCHA": "CAPTCHA en texto", - "Image CAPTCHA": "CAPTCHA en imagen", - "Sign In": "Iniciar sesión", - "Register": "Registrarse", - "Email": "Correo", - "Google verification code": "Código de verificación de Google", - "Preferences": "Preferencias", - "Player preferences": "Preferencias del reproductor", - "Always loop: ": "Repetir siempre: ", - "Autoplay: ": "Reproducción automática: ", - "Autoplay next video: ": "Reproducir automáticamente el vídeo siguiente: ", - "Listen by default: ": "Activar el sonido por defecto: ", - "Proxy videos? ": "¿Usar un proxy para los vídeos? ", - "Default speed: ": "Velocidad por defecto: ", - "Preferred video quality: ": "Calidad de vídeo preferida: ", - "Player volume: ": "Volumen del reproductor: ", - "Default comments: ": "Comentarios por defecto: ", - "Default captions: ": "Subtítulos por defecto: ", - "Fallback captions: ": "Subtítulos alternativos: ", - "Show related videos? ": "¿Mostrar vídeos relacionados? ", - "Visual preferences": "Preferencias visuales", - "Dark mode: ": "Modo oscuro: ", - "Thin mode: ": "Modo compacto: ", - "Subscription preferences": "Preferencias de la suscripción", - "Redirect homepage to feed: ": "Redirigir la página de inicio a la fuente: ", - "Number of videos shown in feed: ": "Número de vídeos mostrados en la fuente: ", - "Sort videos by: ": "Ordenar los vídeos por: ", - "published": "fecha de publicación", - "published - reverse": "fecha de publicación: orden inverso", - "alphabetically": "alfabéticamente", - "alphabetically - reverse": "alfabéticamente: orden inverso", - "channel name": "nombre del canal", - "channel name - reverse": "nombre del canal: orden inverso", - "Only show latest video from channel: ": "Mostrar solo el último vídeo del canal: ", - "Only show latest unwatched video from channel: ": "Mostrar solo el último vídeo sin ver del canal: ", - "Only show unwatched: ": "Mostrar solo los no vistos: ", - "Only show notifications (if there are any): ": "Mostrar solo notificaciones (si hay alguna): ", - "Data preferences": "Preferencias de los datos", - "Clear watch history": "Borrar el historial de reproducción", - "Import/Export data": "Importar/Exportar datos", - "Manage subscriptions": "Gestionar las suscripciones", - "Watch history": "Historial de reproducción", - "Delete account": "Borrar cuenta", - "Administrator preferences": "Preferencias de administrador", - "Default homepage: ": "Página de inicio por defecto: ", - "Feed menu: ": "Menú de fuentes: ", - "Top enabled? ": "¿Habilitar los destacados? ", - "CAPTCHA enabled? ": "¿Habilitar los CAPTCHA? ", - "Login enabled? ": "¿Habilitar el inicio de sesión? ", - "Registration enabled? ": "¿Habilitar el registro? ", - "Report statistics? ": "¿Enviar estadísticas? ", - "Save preferences": "Guardar las preferencias", - "Subscription manager": "Gestor de suscripciones", - "`x` subscriptions": "`x` suscripciones", - "Import/Export": "Importar/Exportar", - "unsubscribe": "Desuscribirse", - "Subscriptions": "Suscripciones", - "`x` unseen notifications": "`x` notificaciones sin ver", - "search": "buscar", - "Sign out": "Cerrar la sesión", - "Released under the AGPLv3 by Omar Roth.": "Publicado bajo licencia AGPLv3 por Omar Roth.", - "Source available here.": "Código fuente disponible aquí.", - "View JavaScript license information.": "Ver información de licencia de JavaScript.", - "View privacy policy.": "Ver la política de privacidad.", - "Trending": "Tendencias", - "Unlisted": "", - "Watch video on Youtube": "Ver el vídeo en Youtube", - "Genre: ": "Género: ", - "License: ": "Licencia: ", - "Family friendly? ": "¿Filtrar contenidos? ", - "Wilson score: ": "Puntuación Wilson: ", - "Engagement: ": "Compromiso: ", - "Whitelisted regions: ": "Regiones permitidas: ", - "Blacklisted regions: ": "Regiones bloqueadas: ", - "Shared `x`": "Compartido `x`", - "`x` views": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.", - "View YouTube comments": "Ver los comentarios de YouTube", - "View more comments on Reddit": "Ver más comentarios en Reddit", - "View `x` comments": "Ver `x` comentarios", - "View Reddit comments": "Ver los comentarios de Reddit", - "Hide replies": "Ocultar las respuestas", - "Show replies": "Mostrar las respuestas", - "Incorrect password": "Contraseña incorrecta", - "Quota exceeded, try again in a few hours": "Cuota excedida, pruebe otra vez en unas horas", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "No se puede iniciar sesión, asegúrese de que la autentificación de dos factores (autentificador o SMS) esté habilitada.", - "Invalid TFA code": "Código TFA no válido", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Error de inicio de sesion. Puede deberse a que la autentificación de dos factores no está habilitada en su cuenta.", - "Invalid answer": "Respuesta no válida", - "Invalid CAPTCHA": "CAPTCHA no válido", - "CAPTCHA is a required field": "El CAPTCHA es un campo obligatorio", - "User ID is a required field": "El nombre es un campo obligatorio", - "Password is a required field": "La contraseña es un campo obligatorio", - "Invalid username or password": "Nombre o contraseña incorrecto", - "Please sign in using 'Sign in with Google'": "Inicie sesión con «Iniciar sesión con Google»", - "Password cannot be empty": "La contraseña no puede estar en blanco", - "Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres", - "Please sign in": "Inicie sesión, por favor", - "Invidious Private Feed for `x`": "Fuente privada de Invidious para `x`", - "channel:`x`": "canal: `x`", - "Deleted or invalid channel": "El canal no es válido o ha sido borrado", - "This channel does not exist.": "El canal no existe.", - "Could not get channel info.": "No se ha podido obtener información del canal.", - "Could not fetch comments": "No se han podido recuperar los comentarios.", - "View `x` replies": "Ver `x` respuestas", - "`x` ago": "hace `x`", - "Load more": "Cargar más", - "`x` points": "`x` puntos", - "Could not create mix.": "No se ha podido crear la mezcla.", - "Playlist is empty": "La lista de reproducción está vacía", - "Invalid playlist.": "Lista de reproducción no válida.", - "Playlist does not exist.": "La lista de reproducción no existe.", - "Could not pull trending pages.": "No se han podido obtener las páginas de tendencias.", - "Hidden field \"challenge\" is a required field": "El campo oculto «desafío» es un campo obligatorio", - "Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio", - "Invalid challenge": "Desafío no válido", - "Invalid token": "Símbolo no válido", - "Invalid user": "Usuario no válido", - "Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo", - "English": "Inglés", - "English (auto-generated)": "Inglés (autogenerado)", - "Afrikaans": "Afrikáans", - "Albanian": "Albanés", - "Amharic": "Amárico", - "Arabic": "Árabe", - "Armenian": "Armenio", - "Azerbaijani": "Azerbaiyano", - "Bangla": "Bengalí", - "Basque": "Euskera", - "Belarusian": "Bielorruso", - "Bosnian": "Bosnio", - "Bulgarian": "Búlgaro", - "Burmese": "Birmano", - "Catalan": "Catalán", - "Cebuano": "Cebuano", - "Chinese (Simplified)": "Chino (simplificado)", - "Chinese (Traditional)": "Chino (tradicional)", - "Corsican": "Corso", - "Croatian": "Croata", - "Czech": "Checo", - "Danish": "Danés", - "Dutch": "Holandés", - "Esperanto": "Esperanto", - "Estonian": "Estonio", - "Filipino": "Filipino", - "Finnish": "Finés", - "French": "Francés", - "Galician": "Gallego", - "Georgian": "Georgiano", - "German": "Alemán", - "Greek": "Griego", - "Gujarati": "Guyaratí", - "Haitian Creole": "Criollo haitiano", - "Hausa": "Hausa", - "Hawaiian": "Hawaiano", - "Hebrew": "Hebreo", - "Hindi": "Hindi", - "Hmong": "Hmong", - "Hungarian": "Húngaro", - "Icelandic": "Islandés", - "Igbo": "Igbo", - "Indonesian": "Indonesio", - "Irish": "Irlandés", - "Italian": "Italiano", - "Japanese": "Japonés", - "Javanese": "Javanés", - "Kannada": "Canarés", - "Kazakh": "Kazajo", - "Khmer": "Camboyano", - "Korean": "Coreano", - "Kurdish": "Kurdo", - "Kyrgyz": "Kirguís", - "Lao": "Laosiano", - "Latin": "Latín", - "Latvian": "Letón", - "Lithuanian": "Lituano", - "Luxembourgish": "Luxemburgués", - "Macedonian": "Macedonio", - "Malagasy": "Malgache", - "Malay": "Malayo", - "Malayalam": "Malabar", - "Maltese": "Maltés", - "Maori": "Maorí", - "Marathi": "Maratí", - "Mongolian": "Mongol", - "Nepali": "Nepalí", - "Norwegian": "Noruego", - "Nyanja": "Chichewa", - "Pashto": "Pastún", - "Persian": "Persa", - "Polish": "Polaco", - "Portuguese": "Portugués", - "Punjabi": "Panyabí", - "Romanian": "Rumano", - "Russian": "Ruso", - "Samoan": "Samoano", - "Scottish Gaelic": "Gaélico escocés", - "Serbian": "Serbio", - "Shona": "Shona", - "Sindhi": "Sindi", - "Sinhala": "Cingalés", - "Slovak": "Eslovaco", - "Slovenian": "Esloveno", - "Somali": "Somalí", - "Southern Sotho": "Sesoto", - "Spanish": "Español", - "Spanish (Latin America)": "Español (Hispanoamérica)", - "Sundanese": "Sondanés", - "Swahili": "Suajili", - "Swedish": "Sueco", - "Tajik": "Tayiko", - "Tamil": "Tamil", - "Telugu": "Telugu", - "Thai": "Tailandés", - "Turkish": "Turco", - "Ukrainian": "Ucraniano", - "Urdu": "Urdu", - "Uzbek": "Uzbeko", - "Vietnamese": "Vietnamita", - "Welsh": "Galés", - "Western Frisian": "Frisón", - "Xhosa": "Xhosa", - "Yiddish": "Yidis", - "Yoruba": "Yoruba", - "Zulu": "Zulú", - "`x` years": "`x` años", - "`x` months": "`x` meses", - "`x` weeks": "`x` semanas", - "`x` days": "`x` días", - "`x` hours": "`x` horas", - "`x` minutes": "`x` minutos", - "`x` seconds": "`x` segundos", - "Fallback comments: ": "Comentarios alternativos: ", - "Popular": "Populares", - "Top": "Destacados", - "About": "Acerca de", - "Rating: ": "Valoración: ", - "Language: ": "Idioma: ", - "View as playlist": "", - "Default": "Por defecto", - "Music": "Música", - "Gaming": "Videojuegos", - "News": "Noticias", - "Movies": "Películas", - "Download": "Descargar", - "Download as: ": "Descargar como: ", - "%A %B %-d, %Y": "%A %B %-d, %Y", - "(edited)": "(editado)", - "Youtube permalink of the comment": "Enlace permanente de YouTube del comentario", - "`x` marked it with a ❤": "`x` lo ha marcado con un ❤", - "Audio mode": "Modo de audio", - "Video mode": "Modo de vídeo", - "Videos": "Vídeos", - "Playlists": "Listas de reproducción", - "Current version: ": "Versión actual: " + "`x` subscribers": "`x` suscriptores", + "`x` videos": "`x` vídeos", + "LIVE": "DIRECTO", + "Shared `x` ago": "Compartido hace `x`", + "Unsubscribe": "Desuscribirse", + "Subscribe": "Suscribirse", + "View channel on YouTube": "Ver el canal en YouTube", + "newest": "más nuevos", + "oldest": "más viejos", + "popular": "populares", + "last": "último", + "Next page": "Página siguiente", + "Previous page": "Página anterior", + "Clear watch history?": "¿Quiere borrar el historial de reproducción?", + "Yes": "Sí", + "No": "No", + "Import and Export Data": "Importación y exportación de datos", + "Import": "Importar", + "Import Invidious data": "Importar datos de Invidious", + "Import YouTube subscriptions": "Importar suscripciones de YouTube", + "Import FreeTube subscriptions (.db)": "Importar suscripciones de FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importar suscripciones de NewPipe (.json)", + "Import NewPipe data (.zip)": "Importar datos de NewPipe (.zip)", + "Export": "Exportar", + "Export subscriptions as OPML": "Exportar suscripciones como OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar suscripciones como OPML (para NewPipe y FreeTube)", + "Export data as JSON": "Exportar datos como JSON", + "Delete account?": "¿Quiere borrar la cuenta?", + "History": "Historial", + "An alternative front-end to YouTube": "Una interfaz alternativa para YouTube", + "JavaScript license information": "Información de licencia de JavaScript", + "source": "código fuente", + "Login": "Iniciar sesión", + "Login/Register": "Iniciar sesión/Registrarse", + "Login to Google": "Iniciar sesión en Google", + "User ID": "Nombre", + "Password": "Contraseña", + "Time (h:mm:ss):": "Hora (h:mm:ss):", + "Text CAPTCHA": "CAPTCHA en texto", + "Image CAPTCHA": "CAPTCHA en imagen", + "Sign In": "Iniciar sesión", + "Register": "Registrarse", + "Email": "Correo", + "Google verification code": "Código de verificación de Google", + "Preferences": "Preferencias", + "Player preferences": "Preferencias del reproductor", + "Always loop: ": "Repetir siempre: ", + "Autoplay: ": "Reproducción automática: ", + "Autoplay next video: ": "Reproducir automáticamente el vídeo siguiente: ", + "Listen by default: ": "Activar el sonido por defecto: ", + "Proxy videos? ": "¿Usar un proxy para los vídeos? ", + "Default speed: ": "Velocidad por defecto: ", + "Preferred video quality: ": "Calidad de vídeo preferida: ", + "Player volume: ": "Volumen del reproductor: ", + "Default comments: ": "Comentarios por defecto: ", + "Default captions: ": "Subtítulos por defecto: ", + "Fallback captions: ": "Subtítulos alternativos: ", + "Show related videos? ": "¿Mostrar vídeos relacionados? ", + "Visual preferences": "Preferencias visuales", + "Dark mode: ": "Modo oscuro: ", + "Thin mode: ": "Modo compacto: ", + "Subscription preferences": "Preferencias de la suscripción", + "Redirect homepage to feed: ": "Redirigir la página de inicio a la fuente: ", + "Number of videos shown in feed: ": "Número de vídeos mostrados en la fuente: ", + "Sort videos by: ": "Ordenar los vídeos por: ", + "published": "fecha de publicación", + "published - reverse": "fecha de publicación: orden inverso", + "alphabetically": "alfabéticamente", + "alphabetically - reverse": "alfabéticamente: orden inverso", + "channel name": "nombre del canal", + "channel name - reverse": "nombre del canal: orden inverso", + "Only show latest video from channel: ": "Mostrar solo el último vídeo del canal: ", + "Only show latest unwatched video from channel: ": "Mostrar solo el último vídeo sin ver del canal: ", + "Only show unwatched: ": "Mostrar solo los no vistos: ", + "Only show notifications (if there are any): ": "Mostrar solo notificaciones (si hay alguna): ", + "Data preferences": "Preferencias de los datos", + "Clear watch history": "Borrar el historial de reproducción", + "Import/Export data": "Importar/Exportar datos", + "Manage subscriptions": "Gestionar las suscripciones", + "Watch history": "Historial de reproducción", + "Delete account": "Borrar cuenta", + "Administrator preferences": "Preferencias de administrador", + "Default homepage: ": "Página de inicio por defecto: ", + "Feed menu: ": "Menú de fuentes: ", + "Top enabled? ": "¿Habilitar los destacados? ", + "CAPTCHA enabled? ": "¿Habilitar los CAPTCHA? ", + "Login enabled? ": "¿Habilitar el inicio de sesión? ", + "Registration enabled? ": "¿Habilitar el registro? ", + "Report statistics? ": "¿Enviar estadísticas? ", + "Save preferences": "Guardar las preferencias", + "Subscription manager": "Gestor de suscripciones", + "`x` subscriptions": "`x` suscripciones", + "Import/Export": "Importar/Exportar", + "unsubscribe": "Desuscribirse", + "Subscriptions": "Suscripciones", + "`x` unseen notifications": "`x` notificaciones sin ver", + "search": "buscar", + "Sign out": "Cerrar la sesión", + "Released under the AGPLv3 by Omar Roth.": "Publicado bajo licencia AGPLv3 por Omar Roth.", + "Source available here.": "Código fuente disponible aquí.", + "View JavaScript license information.": "Ver información de licencia de JavaScript.", + "View privacy policy.": "Ver la política de privacidad.", + "Trending": "Tendencias", + "Unlisted": "", + "Watch video on Youtube": "Ver el vídeo en Youtube", + "Genre: ": "Género: ", + "License: ": "Licencia: ", + "Family friendly? ": "¿Filtrar contenidos? ", + "Wilson score: ": "Puntuación Wilson: ", + "Engagement: ": "Compromiso: ", + "Whitelisted regions: ": "Regiones permitidas: ", + "Blacklisted regions: ": "Regiones bloqueadas: ", + "Shared `x`": "Compartido `x`", + "`x` views": "", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.", + "View YouTube comments": "Ver los comentarios de YouTube", + "View more comments on Reddit": "Ver más comentarios en Reddit", + "View `x` comments": "Ver `x` comentarios", + "View Reddit comments": "Ver los comentarios de Reddit", + "Hide replies": "Ocultar las respuestas", + "Show replies": "Mostrar las respuestas", + "Incorrect password": "Contraseña incorrecta", + "Quota exceeded, try again in a few hours": "Cuota excedida, pruebe otra vez en unas horas", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "No se puede iniciar sesión, asegúrese de que la autentificación de dos factores (autentificador o SMS) esté habilitada.", + "Invalid TFA code": "Código TFA no válido", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Error de inicio de sesion. Puede deberse a que la autentificación de dos factores no está habilitada en su cuenta.", + "Invalid answer": "Respuesta no válida", + "Invalid CAPTCHA": "CAPTCHA no válido", + "CAPTCHA is a required field": "El CAPTCHA es un campo obligatorio", + "User ID is a required field": "El nombre es un campo obligatorio", + "Password is a required field": "La contraseña es un campo obligatorio", + "Invalid username or password": "Nombre o contraseña incorrecto", + "Please sign in using 'Sign in with Google'": "Inicie sesión con «Iniciar sesión con Google»", + "Password cannot be empty": "La contraseña no puede estar en blanco", + "Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres", + "Please sign in": "Inicie sesión, por favor", + "Invidious Private Feed for `x`": "Fuente privada de Invidious para `x`", + "channel:`x`": "canal: `x`", + "Deleted or invalid channel": "El canal no es válido o ha sido borrado", + "This channel does not exist.": "El canal no existe.", + "Could not get channel info.": "No se ha podido obtener información del canal.", + "Could not fetch comments": "No se han podido recuperar los comentarios.", + "View `x` replies": "Ver `x` respuestas", + "`x` ago": "hace `x`", + "Load more": "Cargar más", + "`x` points": "`x` puntos", + "Could not create mix.": "No se ha podido crear la mezcla.", + "Playlist is empty": "La lista de reproducción está vacía", + "Invalid playlist.": "Lista de reproducción no válida.", + "Playlist does not exist.": "La lista de reproducción no existe.", + "Could not pull trending pages.": "No se han podido obtener las páginas de tendencias.", + "Hidden field \"challenge\" is a required field": "El campo oculto «desafío» es un campo obligatorio", + "Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio", + "Invalid challenge": "Desafío no válido", + "Invalid token": "Símbolo no válido", + "Invalid user": "Usuario no válido", + "Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo", + "English": "Inglés", + "English (auto-generated)": "Inglés (autogenerado)", + "Afrikaans": "Afrikáans", + "Albanian": "Albanés", + "Amharic": "Amárico", + "Arabic": "Árabe", + "Armenian": "Armenio", + "Azerbaijani": "Azerbaiyano", + "Bangla": "Bengalí", + "Basque": "Euskera", + "Belarusian": "Bielorruso", + "Bosnian": "Bosnio", + "Bulgarian": "Búlgaro", + "Burmese": "Birmano", + "Catalan": "Catalán", + "Cebuano": "Cebuano", + "Chinese (Simplified)": "Chino (simplificado)", + "Chinese (Traditional)": "Chino (tradicional)", + "Corsican": "Corso", + "Croatian": "Croata", + "Czech": "Checo", + "Danish": "Danés", + "Dutch": "Holandés", + "Esperanto": "Esperanto", + "Estonian": "Estonio", + "Filipino": "Filipino", + "Finnish": "Finés", + "French": "Francés", + "Galician": "Gallego", + "Georgian": "Georgiano", + "German": "Alemán", + "Greek": "Griego", + "Gujarati": "Guyaratí", + "Haitian Creole": "Criollo haitiano", + "Hausa": "Hausa", + "Hawaiian": "Hawaiano", + "Hebrew": "Hebreo", + "Hindi": "Hindi", + "Hmong": "Hmong", + "Hungarian": "Húngaro", + "Icelandic": "Islandés", + "Igbo": "Igbo", + "Indonesian": "Indonesio", + "Irish": "Irlandés", + "Italian": "Italiano", + "Japanese": "Japonés", + "Javanese": "Javanés", + "Kannada": "Canarés", + "Kazakh": "Kazajo", + "Khmer": "Camboyano", + "Korean": "Coreano", + "Kurdish": "Kurdo", + "Kyrgyz": "Kirguís", + "Lao": "Laosiano", + "Latin": "Latín", + "Latvian": "Letón", + "Lithuanian": "Lituano", + "Luxembourgish": "Luxemburgués", + "Macedonian": "Macedonio", + "Malagasy": "Malgache", + "Malay": "Malayo", + "Malayalam": "Malabar", + "Maltese": "Maltés", + "Maori": "Maorí", + "Marathi": "Maratí", + "Mongolian": "Mongol", + "Nepali": "Nepalí", + "Norwegian": "Noruego", + "Nyanja": "Chichewa", + "Pashto": "Pastún", + "Persian": "Persa", + "Polish": "Polaco", + "Portuguese": "Portugués", + "Punjabi": "Panyabí", + "Romanian": "Rumano", + "Russian": "Ruso", + "Samoan": "Samoano", + "Scottish Gaelic": "Gaélico escocés", + "Serbian": "Serbio", + "Shona": "Shona", + "Sindhi": "Sindi", + "Sinhala": "Cingalés", + "Slovak": "Eslovaco", + "Slovenian": "Esloveno", + "Somali": "Somalí", + "Southern Sotho": "Sesoto", + "Spanish": "Español", + "Spanish (Latin America)": "Español (Hispanoamérica)", + "Sundanese": "Sondanés", + "Swahili": "Suajili", + "Swedish": "Sueco", + "Tajik": "Tayiko", + "Tamil": "Tamil", + "Telugu": "Telugu", + "Thai": "Tailandés", + "Turkish": "Turco", + "Ukrainian": "Ucraniano", + "Urdu": "Urdu", + "Uzbek": "Uzbeko", + "Vietnamese": "Vietnamita", + "Welsh": "Galés", + "Western Frisian": "Frisón", + "Xhosa": "Xhosa", + "Yiddish": "Yidis", + "Yoruba": "Yoruba", + "Zulu": "Zulú", + "`x` years": "`x` años", + "`x` months": "`x` meses", + "`x` weeks": "`x` semanas", + "`x` days": "`x` días", + "`x` hours": "`x` horas", + "`x` minutes": "`x` minutos", + "`x` seconds": "`x` segundos", + "Fallback comments: ": "Comentarios alternativos: ", + "Popular": "Populares", + "Top": "Destacados", + "About": "Acerca de", + "Rating: ": "Valoración: ", + "Language: ": "Idioma: ", + "View as playlist": "", + "Default": "Por defecto", + "Music": "Música", + "Gaming": "Videojuegos", + "News": "Noticias", + "Movies": "Películas", + "Download": "Descargar", + "Download as: ": "Descargar como: ", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "(edited)": "(editado)", + "Youtube permalink of the comment": "Enlace permanente de YouTube del comentario", + "`x` marked it with a ❤": "`x` lo ha marcado con un ❤", + "Audio mode": "Modo de audio", + "Video mode": "Modo de vídeo", + "Videos": "Vídeos", + "Playlists": "Listas de reproducción", + "Current version: ": "Versión actual: " } diff --git a/locales/eu.json b/locales/eu.json index 4ec5c46b..39d5a223 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` harpidedun", - "`x` videos": "`x` bideo", - "LIVE": "ZUZENEAN", - "Shared `x` ago": "Duela `x` partekatua", - "Unsubscribe": "Harpidetza kendu", - "Subscribe": "Harpidetu", - "View channel on YouTube": "Ikusi kanala YouTuben", - "newest": "berrienak", - "oldest": "zaharrenak", - "popular": "ospetsuenak", - "last": "", - "Next page": "Hurrengo orria", - "Previous page": "Aurreko orria", - "Clear watch history?": "Garbitu ikusitakoen historia?", - "Yes": "Bai", - "No": "Ez", - "Import and Export Data": "Datuak inportatu eta esportatu", - "Import": "Inportatu", - "Import Invidious data": "Invidiouseko datuak inportatu", - "Import YouTube subscriptions": "YouTubeko harpidetzak inportatu", - "Import FreeTube subscriptions (.db)": "FreeTubeko harpidetzak inportatu (.db)", - "Import NewPipe subscriptions (.json)": "NewPipeko harpidetzak inportatu (.json)", - "Import NewPipe data (.zip)": "NewPipeko datuak inportatu (.zip)", - "Export": "Esportatu", - "Export subscriptions as OPML": "Esportatu harpidetzak OPML bezala", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Harpidetzak OPML bezala esportatu (NewPipe eta FreeTuberako)", - "Export data as JSON": "Datuak JSON bezala esportatu", - "Delete account?": "Kontua ezabatu?", - "History": "Historia", - "An alternative front-end to YouTube": "YouTuberako interfaze alternatibo bat", - "JavaScript license information": "JavaScript lizentzia informazioa", - "source": "iturburua", - "Login": "Saioa hasi", - "Login/Register": "Saioa hasi/Izena eman", - "Login to Google": "Googlekin hasi saioa", - "User ID": "Erabiltzaile IDa", - "Password": "Pasahitza", - "Time (h:mm:ss):": "Denbora (o:mm:ss):", - "Text CAPTCHA": "Testu CAPTCHA", - "Image CAPTCHA": "Irudi CAPTCHA", - "Sign In": "", - "Register": "", - "Email:": "", - "Google verification code:": "", - "Preferences": "", - "Player preferences": "", - "Always loop: ": "", - "Autoplay: ": "", - "Autoplay next video: ": "", - "Listen by default: ": "", - "Proxy videos? ": "", - "Default speed: ": "", - "Preferred video quality: ": "", - "Player volume: ": "", - "Default comments: ": "", - "Default captions: ": "", - "Fallback captions: ": "", - "Show related videos? ": "", - "Visual preferences": "", - "Dark mode: ": "", - "Thin mode: ": "", - "Subscription preferences": "", - "Redirect homepage to feed: ": "", - "Number of videos shown in feed: ": "", - "Sort videos by: ": "", - "published": "", - "published - reverse": "", - "alphabetically": "", - "alphabetically - reverse": "", - "channel name": "", - "channel name - reverse": "", - "Only show latest video from channel: ": "", - "Only show latest unwatched video from channel: ": "", - "Only show unwatched: ": "", - "Only show notifications (if there are any): ": "", - "Data preferences": "", - "Clear watch history": "", - "Import/Export data": "", - "Manage subscriptions": "", - "Watch history": "", - "Delete account": "", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", - "Save preferences": "", - "Subscription manager": "", - "`x` subscriptions": "", - "Import/Export": "", - "unsubscribe": "", - "Subscriptions": "", - "`x` unseen notifications": "", - "search": "", - "Sign out": "", - "Released under the AGPLv3 by Omar Roth.": "", - "Source available here.": "", - "View JavaScript license information.": "", - "View privacy policy.": "", - "Unlisted": "", - "Trending": "", - "Watch video on Youtube": "", - "Genre: ": "", - "License: ": "", - "Family friendly? ": "", - "Wilson score: ": "", - "Engagement: ": "", - "Whitelisted regions: ": "", - "Blacklisted regions: ": "", - "Shared `x`": "", - "`x` views": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", - "View YouTube comments": "", - "View more comments on Reddit": "", - "View `x` comments": "", - "View Reddit comments": "", - "Hide replies": "", - "Show replies": "", - "Incorrect password": "", - "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", - "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "", - "Invalid CAPTCHA": "", - "CAPTCHA is a required field": "", - "User ID is a required field": "", - "Password is a required field": "", - "Invalid username or password": "", - "Please sign in using 'Sign in with Google'": "", - "Password cannot be empty": "", - "Password cannot be longer than 55 characters": "", - "Please sign in": "", - "Invidious Private Feed for `x`": "", - "channel:`x`": "", - "Deleted or invalid channel": "", - "This channel does not exist.": "", - "Could not get channel info.": "", - "Could not fetch comments": "", - "View `x` replies": "", - "`x` ago": "", - "Load more": "", - "`x` points": "", - "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", - "Playlist does not exist.": "", - "Could not pull trending pages.": "", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "", - "Token is expired, please try again": "", - "English": "", - "English (auto-generated)": "", - "Afrikaans": "", - "Albanian": "", - "Amharic": "", - "Arabic": "", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "", - "Bosnian": "", - "Bulgarian": "", - "Burmese": "", - "Catalan": "", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "", - "Danish": "", - "Dutch": "", - "Esperanto": "", - "Estonian": "", - "Filipino": "", - "Finnish": "", - "French": "", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "", - "Icelandic": "", - "Igbo": "", - "Indonesian": "", - "Irish": "", - "Italian": "", - "Japanese": "", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "", - "Slovenian": "", - "Somali": "", - "Southern Sotho": "", - "Spanish": "", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "", - "Ukrainian": "", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "", - "`x` months": "", - "`x` weeks": "", - "`x` days": "", - "`x` hours": "", - "`x` minutes": "", - "`x` seconds": "", - "Fallback comments: ": "", - "Popular": "", - "Top": "", - "About": "", - "Rating: ": "", - "Language: ": "", - "View as playlist": "", - "Default": "", - "Music": "", - "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", - "%A %B %-d, %Y": "", - "(edited)": "", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "", - "Playlists": "", - "Current version: ": "" + "`x` subscribers": "`x` harpidedun", + "`x` videos": "`x` bideo", + "LIVE": "ZUZENEAN", + "Shared `x` ago": "Duela `x` partekatua", + "Unsubscribe": "Harpidetza kendu", + "Subscribe": "Harpidetu", + "View channel on YouTube": "Ikusi kanala YouTuben", + "newest": "berrienak", + "oldest": "zaharrenak", + "popular": "ospetsuenak", + "last": "", + "Next page": "Hurrengo orria", + "Previous page": "Aurreko orria", + "Clear watch history?": "Garbitu ikusitakoen historia?", + "Yes": "Bai", + "No": "Ez", + "Import and Export Data": "Datuak inportatu eta esportatu", + "Import": "Inportatu", + "Import Invidious data": "Invidiouseko datuak inportatu", + "Import YouTube subscriptions": "YouTubeko harpidetzak inportatu", + "Import FreeTube subscriptions (.db)": "FreeTubeko harpidetzak inportatu (.db)", + "Import NewPipe subscriptions (.json)": "NewPipeko harpidetzak inportatu (.json)", + "Import NewPipe data (.zip)": "NewPipeko datuak inportatu (.zip)", + "Export": "Esportatu", + "Export subscriptions as OPML": "Esportatu harpidetzak OPML bezala", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Harpidetzak OPML bezala esportatu (NewPipe eta FreeTuberako)", + "Export data as JSON": "Datuak JSON bezala esportatu", + "Delete account?": "Kontua ezabatu?", + "History": "Historia", + "An alternative front-end to YouTube": "YouTuberako interfaze alternatibo bat", + "JavaScript license information": "JavaScript lizentzia informazioa", + "source": "iturburua", + "Login": "Saioa hasi", + "Login/Register": "Saioa hasi/Izena eman", + "Login to Google": "Googlekin hasi saioa", + "User ID": "Erabiltzaile IDa", + "Password": "Pasahitza", + "Time (h:mm:ss):": "Denbora (o:mm:ss):", + "Text CAPTCHA": "Testu CAPTCHA", + "Image CAPTCHA": "Irudi CAPTCHA", + "Sign In": "", + "Register": "", + "Email:": "", + "Google verification code:": "", + "Preferences": "", + "Player preferences": "", + "Always loop: ": "", + "Autoplay: ": "", + "Autoplay next video: ": "", + "Listen by default: ": "", + "Proxy videos? ": "", + "Default speed: ": "", + "Preferred video quality: ": "", + "Player volume: ": "", + "Default comments: ": "", + "Default captions: ": "", + "Fallback captions: ": "", + "Show related videos? ": "", + "Visual preferences": "", + "Dark mode: ": "", + "Thin mode: ": "", + "Subscription preferences": "", + "Redirect homepage to feed: ": "", + "Number of videos shown in feed: ": "", + "Sort videos by: ": "", + "published": "", + "published - reverse": "", + "alphabetically": "", + "alphabetically - reverse": "", + "channel name": "", + "channel name - reverse": "", + "Only show latest video from channel: ": "", + "Only show latest unwatched video from channel: ": "", + "Only show unwatched: ": "", + "Only show notifications (if there are any): ": "", + "Data preferences": "", + "Clear watch history": "", + "Import/Export data": "", + "Manage subscriptions": "", + "Watch history": "", + "Delete account": "", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Report statistics? ": "", + "Save preferences": "", + "Subscription manager": "", + "`x` subscriptions": "", + "Import/Export": "", + "unsubscribe": "", + "Subscriptions": "", + "`x` unseen notifications": "", + "search": "", + "Sign out": "", + "Released under the AGPLv3 by Omar Roth.": "", + "Source available here.": "", + "View JavaScript license information.": "", + "View privacy policy.": "", + "Unlisted": "", + "Trending": "", + "Watch video on Youtube": "", + "Genre: ": "", + "License: ": "", + "Family friendly? ": "", + "Wilson score: ": "", + "Engagement: ": "", + "Whitelisted regions: ": "", + "Blacklisted regions: ": "", + "Shared `x`": "", + "`x` views": "", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", + "View YouTube comments": "", + "View more comments on Reddit": "", + "View `x` comments": "", + "View Reddit comments": "", + "Hide replies": "", + "Show replies": "", + "Incorrect password": "", + "Quota exceeded, try again in a few hours": "", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", + "Invalid TFA code": "", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "", + "Invalid answer": "", + "Invalid CAPTCHA": "", + "CAPTCHA is a required field": "", + "User ID is a required field": "", + "Password is a required field": "", + "Invalid username or password": "", + "Please sign in using 'Sign in with Google'": "", + "Password cannot be empty": "", + "Password cannot be longer than 55 characters": "", + "Please sign in": "", + "Invidious Private Feed for `x`": "", + "channel:`x`": "", + "Deleted or invalid channel": "", + "This channel does not exist.": "", + "Could not get channel info.": "", + "Could not fetch comments": "", + "View `x` replies": "", + "`x` ago": "", + "Load more": "", + "`x` points": "", + "Could not create mix.": "", + "Playlist is empty": "", + "Invalid playlist.": "", + "Playlist does not exist.": "", + "Could not pull trending pages.": "", + "Hidden field \"challenge\" is a required field": "", + "Hidden field \"token\" is a required field": "", + "Invalid challenge": "", + "Invalid token": "", + "Invalid user": "", + "Token is expired, please try again": "", + "English": "", + "English (auto-generated)": "", + "Afrikaans": "", + "Albanian": "", + "Amharic": "", + "Arabic": "", + "Armenian": "", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "", + "Bosnian": "", + "Bulgarian": "", + "Burmese": "", + "Catalan": "", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "", + "Danish": "", + "Dutch": "", + "Esperanto": "", + "Estonian": "", + "Filipino": "", + "Finnish": "", + "French": "", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "", + "Icelandic": "", + "Igbo": "", + "Indonesian": "", + "Irish": "", + "Italian": "", + "Japanese": "", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "", + "Slovenian": "", + "Somali": "", + "Southern Sotho": "", + "Spanish": "", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "", + "Ukrainian": "", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "", + "`x` months": "", + "`x` weeks": "", + "`x` days": "", + "`x` hours": "", + "`x` minutes": "", + "`x` seconds": "", + "Fallback comments: ": "", + "Popular": "", + "Top": "", + "About": "", + "Rating: ": "", + "Language: ": "", + "View as playlist": "", + "Default": "", + "Music": "", + "Gaming": "", + "News": "", + "Movies": "", + "Download": "", + "Download as: ": "", + "%A %B %-d, %Y": "", + "(edited)": "", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "", + "Videos": "", + "Playlists": "", + "Current version: ": "" } diff --git a/locales/fr.json b/locales/fr.json index 69427f8e..6a7cb658 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` abonnés", - "`x` videos": "`x` vidéos", - "LIVE": "EN DIRECT", - "Shared `x` ago": "Ajoutée il y a `x`", - "Unsubscribe": "Se désabonner", - "Subscribe": "S'abonner", - "View channel on YouTube": "Voir la chaîne sur YouTube", - "newest": "Date d'ajout (la plus récente)", - "oldest": "Date d'ajout (la plus ancienne)", - "popular": "Les plus populaires", - "last": "Dernières", - "Next page": "Page suivante", - "Previous page": "Page précédente", - "Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?", - "Yes": "Oui", - "No": "Non", - "Import and Export Data": "Importer et exporter des données", - "Import": "Importer", - "Import Invidious data": "Importer des données Invidious", - "Import YouTube subscriptions": "Importer des abonnements YouTube", - "Import FreeTube subscriptions (.db)": "Importer des abonnements FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Importer des abonnements NewPipe (.json)", - "Import NewPipe data (.zip)": "Importer des données NewPipe (.zip)", - "Export": "Exporter", - "Export subscriptions as OPML": "Exporter les abonnements en OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporter les abonnements en OPML (pour NewPipe & FreeTube)", - "Export data as JSON": "Exporter les données au format JSON", - "Delete account?": "Êtes-vous sûr de vouloir supprimer votre compte ?", - "History": "Historique", - "An alternative front-end to YouTube": "Un front-end alternatif à YouTube", - "JavaScript license information": "Informations sur les licences JavaScript", - "source": "source", - "Login": "Se connecter", - "Login/Register": "Se connecter/Créer un compte", - "Login to Google": "Se connecter avec Google", - "User ID": "Identifiant utilisateur", - "Password": "Mot de passe", - "Time (h:mm:ss):": "Heure (h:mm:ss) :", - "Text CAPTCHA": "CAPTCHA Texte", - "Image CAPTCHA": "CAPTCHA Image", - "Sign In": "Se connecter", - "Register": "S'inscrire", - "Email": "E-mail", - "Google verification code": "Code de vérification Google", - "Preferences": "Préférences", - "Player preferences": "Préférences du lecteur", - "Always loop: ": "Lire en boucle : ", - "Autoplay: ": "Lire automatiquement : ", - "Autoplay next video: ": "Lire automatiquement la vidéo suivante : ", - "Listen by default: ": "Audio uniquement : ", - "Proxy videos? ": "Charger les vidéos à travers un proxy ? ", - "Default speed: ": "Vitesse par défaut : ", - "Preferred video quality: ": "Qualité vidéo souhaitée : ", - "Player volume: ": "Volume du lecteur : ", - "Default comments: ": "Source des commentaires : ", - "Default captions: ": "Sous-titres par défaut : ", - "Fallback captions: ": "Fallback captions: ", - "Show related videos? ": "Voir les vidéos liées ? ", - "Visual preferences": "Préférences du site", - "Dark mode: ": "Mode Sombre : ", - "Thin mode: ": "Mode Simplifié : ", - "Subscription preferences": "Préférences de la page d'abonnements", - "Redirect homepage to feed: ": "Rediriger la page d'accueil vers la page d'abonnements : ", - "Number of videos shown in feed: ": "Nombre de vidéos montrées dans la page d'abonnements : ", - "Sort videos by: ": "Trier les vidéos par : ", - "published": "publication", - "published - reverse": "publication - inversé", - "alphabetically": "alphabétiquement", - "alphabetically - reverse": "alphabétiquement - inversé", - "channel name": "nom de la chaîne", - "channel name - reverse": "nom de la chaîne - inversé", - "Only show latest video from channel: ": "Afficher uniquement la dernière vidéo de la chaîne : ", - "Only show latest unwatched video from channel: ": "Afficher uniquement la dernière vidéo de la chaîne non regardée : ", - "Only show unwatched: ": "Afficher uniquement les vidéos non regardées : ", - "Only show notifications (if there are any): ": "Afficher uniquement les notifications (s'il y en a) : ", - "Data preferences": "Préférences liées aux données", - "Clear watch history": "Supprimer l'historique des vidéos regardées", - "Import/Export data": "Importer/exporter les données", - "Manage subscriptions": "Gérer les abonnements", - "Watch history": "Historique de visionnage", - "Delete account": "Supprimer votre compte", - "Administrator preferences": "Préferences d'Administrateur", - "Default homepage: ": "Page d'accueil par défaut : ", - "Feed menu: ": "Menu des Flux : ", - "Top enabled? ": "Top activé ? ", - "CAPTCHA enabled? ": "CAPTCHA activé ? ", - "Login enabled? ": "Connexion activé ? ", - "Registration enabled? ": "Inscription activée ? ", - "Report statistics? ": "Télémétrie activé ? ", - "Save preferences": "Enregistrer les préférences", - "Subscription manager": "Gestionnaire d'abonnement", - "`x` subscriptions": "`x` abonnements", - "Import/Export": "Importer/Exporter", - "unsubscribe": "se désabonner", - "Subscriptions": "Abonnements", - "`x` unseen notifications": "`x` notifications non vues", - "search": "Rechercher", - "Sign out": "Déconnexion", - "Released under the AGPLv3 by Omar Roth.": "Publié sous licence AGPLv3 par Omar Roth.", - "Source available here.": "Code Source.", - "View JavaScript license information.": "Voir les informations des licences JavaScript.", - "View privacy policy.": "Politique de confidentialité", - "Trending": "Tendances", - "Unlisted": "Non répertoriée", - "Watch video on Youtube": "Voir la vidéo sur Youtube", - "Genre: ": "Genre : ", - "License: ": "Licence : ", - "Family friendly? ": "Tout Public ? ", - "Wilson score: ": "Score de Wilson : ", - "Engagement: ": "Poucentage de spectateur aillant aimé Like ou Dislike la vidéo : ", - "Whitelisted regions: ": "Régions en liste blanche : ", - "Blacklisted regions: ": "Régions sur liste noire : ", - "Shared `x`": "Ajoutée le `x`", - "`x` views": "", - "Premieres in `x`": "Première dans `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript soit désactivé. Cliquez ici pour voir les commentaires sans. Gardez à l'esprit que le chargement peut prendre plus de temps.", - "View YouTube comments": "Voir les commentaires YouTube", - "View more comments on Reddit": "Voir plus de commentaires sur Reddit", - "View `x` comments": "Voir `x` commentaires", - "View Reddit comments": "Voir les commentaires Reddit", - "Hide replies": "Masquer les réponses", - "Show replies": "Afficher les réponses", - "Incorrect password": "Mot de passe incorrect", - "Quota exceeded, try again in a few hours": "Nombre de tentative de connexion dépassé, réessayez dans quelques heures", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.", - "Invalid TFA code": "Code d'authentification à deux facteurs invalide", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.", - "Invalid answer": "Réponse invalide", - "Invalid CAPTCHA": "CAPTCHA invalide", - "CAPTCHA is a required field": "Veuillez entrer un CAPTCHA", - "User ID is a required field": "Veuillez entrer un Identifiant Utilisateur", - "Password is a required field": "Veuillez entrer un Mot de passe", - "Invalid username or password": "Nom d'utilisateur ou mot de passe invalide", - "Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"Se connecter avec Google\"", - "Password cannot be empty": "Le mot de passe ne peut pas être vide", - "Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères", - "Please sign in": "Veuillez vous connecter", - "Invidious Private Feed for `x`": "Flux RSS privé pour `x`", - "channel:`x`": "chaîne:`x`", - "Deleted or invalid channel": "Chaîne supprimée ou invalide", - "This channel does not exist.": "Cette chaine n'existe pas.", - "Could not get channel info.": "Impossible de charger les informations de cette chaîne.", - "Could not fetch comments": "Impossible de charger les commentaires", - "View `x` replies": "Voir `x` réponses", - "`x` ago": "il y a `x`", - "Load more": "Charger plus", - "`x` points": "`x` points", - "Could not create mix.": "Impossible de charger cette liste de lecture.", - "Playlist is empty": "La liste de lecture est vide", - "Invalid playlist.": "Liste de lecture invalide.", - "Playlist does not exist.": "La liste de lecture n'existe pas.", - "Could not pull trending pages.": "Impossible de charger les pages de tendances.", - "Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field", - "Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field", - "Invalid challenge": "Invalid challenge", - "Invalid token": "Invalid token", - "Invalid user": "Invalid user", - "Token is expired, please try again": "Token is expired, please try again", - "English": "Anglais", - "English (auto-generated)": "Anglais (générés automatiquement)", - "Afrikaans": "Afrikaans", - "Albanian": "Albanais", - "Amharic": "Amharique", - "Arabic": "Arabe", - "Armenian": "Arménien", - "Azerbaijani": "Azerbaïdjanais", - "Bangla": "Bangla", - "Basque": "Basque", - "Belarusian": "Belarusian", - "Bosnian": "Bosnian", - "Bulgarian": "Bulgarian", - "Burmese": "Birman", - "Catalan": "Catalan", - "Cebuano": "Cebuano", - "Chinese (Simplified)": "Chinois (Simplifié)", - "Chinese (Traditional)": "Chinois (Traditionnel)", - "Corsican": "Corse", - "Croatian": "Croate", - "Czech": "Tchèque", - "Danish": "Danois", - "Dutch": "Hollandais", - "Esperanto": "Espéranto", - "Estonian": "Estonien", - "Filipino": "Philippin", - "Finnish": "Finlandais", - "French": "Français", - "Galician": "Galicien", - "Georgian": "Géorgien", - "German": "Allemand", - "Greek": "Grec", - "Gujarati": "Gujarati", - "Haitian Creole": "Créole Haïtien", - "Hausa": "Haoussa", - "Hawaiian": "Hawaïen", - "Hebrew": "Hébraïque", - "Hindi": "Hindi", - "Hmong": "Hmong", - "Hungarian": "Hongrois", - "Icelandic": "Islandais", - "Igbo": "Igbo", - "Indonesian": "Indonésien", - "Irish": "Irlandais", - "Italian": "Italien", - "Japanese": "Japonais", - "Javanese": "Javanais", - "Kannada": "Kannada", - "Kazakh": "Kazakh", - "Khmer": "Khmer", - "Korean": "Coréen", - "Kurdish": "Kurde", - "Kyrgyz": "Kirghize", - "Lao": "Lao", - "Latin": "Latin", - "Latvian": "Letton", - "Lithuanian": "Lituanien", - "Luxembourgish": "Luxembourgeois", - "Macedonian": "Macédonien", - "Malagasy": "Malgache", - "Malay": "Malais", - "Malayalam": "Malayalam", - "Maltese": "Maltais", - "Maori": "Maori", - "Marathi": "Marathi", - "Mongolian": "Mongol", - "Nepali": "Népalais", - "Norwegian": "Norvégien", - "Nyanja": "Nyanja", - "Pashto": "Pachtou", - "Persian": "Persan", - "Polish": "Polonais", - "Portuguese": "Portugais", - "Punjabi": "Punjabi", - "Romanian": "Roumain", - "Russian": "Russe", - "Samoan": "Samoan", - "Scottish Gaelic": "Eaélique Ècossais", - "Serbian": "Serbe", - "Shona": "Shona", - "Sindhi": "Sindhi", - "Sinhala": "Cinghalais", - "Slovak": "Slovaque", - "Slovenian": "Slovène", - "Somali": "Somalien", - "Southern Sotho": "Sotho du Sud", - "Spanish": "Espagnol", - "Spanish (Latin America)": "Espagnol (Amérique latine)", - "Sundanese": "Sundanais", - "Swahili": "Swahili", - "Swedish": "Suédois", - "Tajik": "Tajik", - "Tamil": "Tamil", - "Telugu": "Telugu", - "Thai": "Thaï", - "Turkish": "Turc", - "Ukrainian": "Ukrainien", - "Urdu": "Ourdou", - "Uzbek": "Ouzbek", - "Vietnamese": "Vietnamien", - "Welsh": "Gallois", - "Western Frisian": "Frison occidental", - "Xhosa": "Xhosa", - "Yiddish": "Yiddish", - "Yoruba": "Yoruba", - "Zulu": "Zoulou", - "`x` years": "`x` ans", - "`x` months": "`x` mois", - "`x` weeks": "`x` semaines", - "`x` days": "`x` jours", - "`x` hours": "`x` heures", - "`x` minutes": "`x` minutes", - "`x` seconds": "`x` secondes", - "Fallback comments: ": "Fallback comments: ", - "Popular": "Populaire", - "Top": "Top", - "About": "A Propos", - "Rating: ": "Évaluation : ", - "Language: ": "Langue : ", - "View as playlist": "", - "Default": "Défaut", - "Music": "Musique", - "Gaming": "Jeux Vidéo", - "News": "Actualités", - "Movies": "Films", - "Download": "Télécharger", - "Download as: ": "Télécharger en : ", - "%A %B %-d, %Y": "%A %-d %B %Y", - "(edited)": "(modifié)", - "Youtube permalink of the comment": "Lien YouTube permanent vers le commentaire", - "`x` marked it with a ❤": "`x` l'a marqué d'un ❤", - "Audio mode": "Mode Audio", - "Video mode": "Mode Vidéo", - "Videos": "Vidéos", - "Playlists": "Liste de lecture", - "Current version: ": "Version :" + "`x` subscribers": "`x` abonnés", + "`x` videos": "`x` vidéos", + "LIVE": "EN DIRECT", + "Shared `x` ago": "Ajoutée il y a `x`", + "Unsubscribe": "Se désabonner", + "Subscribe": "S'abonner", + "View channel on YouTube": "Voir la chaîne sur YouTube", + "newest": "Date d'ajout (la plus récente)", + "oldest": "Date d'ajout (la plus ancienne)", + "popular": "Les plus populaires", + "last": "Dernières", + "Next page": "Page suivante", + "Previous page": "Page précédente", + "Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?", + "Yes": "Oui", + "No": "Non", + "Import and Export Data": "Importer et exporter des données", + "Import": "Importer", + "Import Invidious data": "Importer des données Invidious", + "Import YouTube subscriptions": "Importer des abonnements YouTube", + "Import FreeTube subscriptions (.db)": "Importer des abonnements FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importer des abonnements NewPipe (.json)", + "Import NewPipe data (.zip)": "Importer des données NewPipe (.zip)", + "Export": "Exporter", + "Export subscriptions as OPML": "Exporter les abonnements en OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporter les abonnements en OPML (pour NewPipe & FreeTube)", + "Export data as JSON": "Exporter les données au format JSON", + "Delete account?": "Êtes-vous sûr de vouloir supprimer votre compte ?", + "History": "Historique", + "An alternative front-end to YouTube": "Un front-end alternatif à YouTube", + "JavaScript license information": "Informations sur les licences JavaScript", + "source": "source", + "Login": "Se connecter", + "Login/Register": "Se connecter/Créer un compte", + "Login to Google": "Se connecter avec Google", + "User ID": "Identifiant utilisateur", + "Password": "Mot de passe", + "Time (h:mm:ss):": "Heure (h:mm:ss) :", + "Text CAPTCHA": "CAPTCHA Texte", + "Image CAPTCHA": "CAPTCHA Image", + "Sign In": "Se connecter", + "Register": "S'inscrire", + "Email": "E-mail", + "Google verification code": "Code de vérification Google", + "Preferences": "Préférences", + "Player preferences": "Préférences du lecteur", + "Always loop: ": "Lire en boucle : ", + "Autoplay: ": "Lire automatiquement : ", + "Autoplay next video: ": "Lire automatiquement la vidéo suivante : ", + "Listen by default: ": "Audio uniquement : ", + "Proxy videos? ": "Charger les vidéos à travers un proxy ? ", + "Default speed: ": "Vitesse par défaut : ", + "Preferred video quality: ": "Qualité vidéo souhaitée : ", + "Player volume: ": "Volume du lecteur : ", + "Default comments: ": "Source des commentaires : ", + "Default captions: ": "Sous-titres par défaut : ", + "Fallback captions: ": "Fallback captions: ", + "Show related videos? ": "Voir les vidéos liées ? ", + "Visual preferences": "Préférences du site", + "Dark mode: ": "Mode Sombre : ", + "Thin mode: ": "Mode Simplifié : ", + "Subscription preferences": "Préférences de la page d'abonnements", + "Redirect homepage to feed: ": "Rediriger la page d'accueil vers la page d'abonnements : ", + "Number of videos shown in feed: ": "Nombre de vidéos montrées dans la page d'abonnements : ", + "Sort videos by: ": "Trier les vidéos par : ", + "published": "publication", + "published - reverse": "publication - inversé", + "alphabetically": "alphabétiquement", + "alphabetically - reverse": "alphabétiquement - inversé", + "channel name": "nom de la chaîne", + "channel name - reverse": "nom de la chaîne - inversé", + "Only show latest video from channel: ": "Afficher uniquement la dernière vidéo de la chaîne : ", + "Only show latest unwatched video from channel: ": "Afficher uniquement la dernière vidéo de la chaîne non regardée : ", + "Only show unwatched: ": "Afficher uniquement les vidéos non regardées : ", + "Only show notifications (if there are any): ": "Afficher uniquement les notifications (s'il y en a) : ", + "Data preferences": "Préférences liées aux données", + "Clear watch history": "Supprimer l'historique des vidéos regardées", + "Import/Export data": "Importer/exporter les données", + "Manage subscriptions": "Gérer les abonnements", + "Watch history": "Historique de visionnage", + "Delete account": "Supprimer votre compte", + "Administrator preferences": "Préferences d'Administrateur", + "Default homepage: ": "Page d'accueil par défaut : ", + "Feed menu: ": "Menu des Flux : ", + "Top enabled? ": "Top activé ? ", + "CAPTCHA enabled? ": "CAPTCHA activé ? ", + "Login enabled? ": "Connexion activé ? ", + "Registration enabled? ": "Inscription activée ? ", + "Report statistics? ": "Télémétrie activé ? ", + "Save preferences": "Enregistrer les préférences", + "Subscription manager": "Gestionnaire d'abonnement", + "`x` subscriptions": "`x` abonnements", + "Import/Export": "Importer/Exporter", + "unsubscribe": "se désabonner", + "Subscriptions": "Abonnements", + "`x` unseen notifications": "`x` notifications non vues", + "search": "Rechercher", + "Sign out": "Déconnexion", + "Released under the AGPLv3 by Omar Roth.": "Publié sous licence AGPLv3 par Omar Roth.", + "Source available here.": "Code Source.", + "View JavaScript license information.": "Voir les informations des licences JavaScript.", + "View privacy policy.": "Politique de confidentialité", + "Trending": "Tendances", + "Unlisted": "Non répertoriée", + "Watch video on Youtube": "Voir la vidéo sur Youtube", + "Genre: ": "Genre : ", + "License: ": "Licence : ", + "Family friendly? ": "Tout Public ? ", + "Wilson score: ": "Score de Wilson : ", + "Engagement: ": "Poucentage de spectateur aillant aimé Like ou Dislike la vidéo : ", + "Whitelisted regions: ": "Régions en liste blanche : ", + "Blacklisted regions: ": "Régions sur liste noire : ", + "Shared `x`": "Ajoutée le `x`", + "`x` views": "", + "Premieres in `x`": "Première dans `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript soit désactivé. Cliquez ici pour voir les commentaires sans. Gardez à l'esprit que le chargement peut prendre plus de temps.", + "View YouTube comments": "Voir les commentaires YouTube", + "View more comments on Reddit": "Voir plus de commentaires sur Reddit", + "View `x` comments": "Voir `x` commentaires", + "View Reddit comments": "Voir les commentaires Reddit", + "Hide replies": "Masquer les réponses", + "Show replies": "Afficher les réponses", + "Incorrect password": "Mot de passe incorrect", + "Quota exceeded, try again in a few hours": "Nombre de tentative de connexion dépassé, réessayez dans quelques heures", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.", + "Invalid TFA code": "Code d'authentification à deux facteurs invalide", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.", + "Invalid answer": "Réponse invalide", + "Invalid CAPTCHA": "CAPTCHA invalide", + "CAPTCHA is a required field": "Veuillez entrer un CAPTCHA", + "User ID is a required field": "Veuillez entrer un Identifiant Utilisateur", + "Password is a required field": "Veuillez entrer un Mot de passe", + "Invalid username or password": "Nom d'utilisateur ou mot de passe invalide", + "Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"Se connecter avec Google\"", + "Password cannot be empty": "Le mot de passe ne peut pas être vide", + "Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères", + "Please sign in": "Veuillez vous connecter", + "Invidious Private Feed for `x`": "Flux RSS privé pour `x`", + "channel:`x`": "chaîne:`x`", + "Deleted or invalid channel": "Chaîne supprimée ou invalide", + "This channel does not exist.": "Cette chaine n'existe pas.", + "Could not get channel info.": "Impossible de charger les informations de cette chaîne.", + "Could not fetch comments": "Impossible de charger les commentaires", + "View `x` replies": "Voir `x` réponses", + "`x` ago": "il y a `x`", + "Load more": "Charger plus", + "`x` points": "`x` points", + "Could not create mix.": "Impossible de charger cette liste de lecture.", + "Playlist is empty": "La liste de lecture est vide", + "Invalid playlist.": "Liste de lecture invalide.", + "Playlist does not exist.": "La liste de lecture n'existe pas.", + "Could not pull trending pages.": "Impossible de charger les pages de tendances.", + "Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field", + "Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field", + "Invalid challenge": "Invalid challenge", + "Invalid token": "Invalid token", + "Invalid user": "Invalid user", + "Token is expired, please try again": "Token is expired, please try again", + "English": "Anglais", + "English (auto-generated)": "Anglais (générés automatiquement)", + "Afrikaans": "Afrikaans", + "Albanian": "Albanais", + "Amharic": "Amharique", + "Arabic": "Arabe", + "Armenian": "Arménien", + "Azerbaijani": "Azerbaïdjanais", + "Bangla": "Bangla", + "Basque": "Basque", + "Belarusian": "Belarusian", + "Bosnian": "Bosnian", + "Bulgarian": "Bulgarian", + "Burmese": "Birman", + "Catalan": "Catalan", + "Cebuano": "Cebuano", + "Chinese (Simplified)": "Chinois (Simplifié)", + "Chinese (Traditional)": "Chinois (Traditionnel)", + "Corsican": "Corse", + "Croatian": "Croate", + "Czech": "Tchèque", + "Danish": "Danois", + "Dutch": "Hollandais", + "Esperanto": "Espéranto", + "Estonian": "Estonien", + "Filipino": "Philippin", + "Finnish": "Finlandais", + "French": "Français", + "Galician": "Galicien", + "Georgian": "Géorgien", + "German": "Allemand", + "Greek": "Grec", + "Gujarati": "Gujarati", + "Haitian Creole": "Créole Haïtien", + "Hausa": "Haoussa", + "Hawaiian": "Hawaïen", + "Hebrew": "Hébraïque", + "Hindi": "Hindi", + "Hmong": "Hmong", + "Hungarian": "Hongrois", + "Icelandic": "Islandais", + "Igbo": "Igbo", + "Indonesian": "Indonésien", + "Irish": "Irlandais", + "Italian": "Italien", + "Japanese": "Japonais", + "Javanese": "Javanais", + "Kannada": "Kannada", + "Kazakh": "Kazakh", + "Khmer": "Khmer", + "Korean": "Coréen", + "Kurdish": "Kurde", + "Kyrgyz": "Kirghize", + "Lao": "Lao", + "Latin": "Latin", + "Latvian": "Letton", + "Lithuanian": "Lituanien", + "Luxembourgish": "Luxembourgeois", + "Macedonian": "Macédonien", + "Malagasy": "Malgache", + "Malay": "Malais", + "Malayalam": "Malayalam", + "Maltese": "Maltais", + "Maori": "Maori", + "Marathi": "Marathi", + "Mongolian": "Mongol", + "Nepali": "Népalais", + "Norwegian": "Norvégien", + "Nyanja": "Nyanja", + "Pashto": "Pachtou", + "Persian": "Persan", + "Polish": "Polonais", + "Portuguese": "Portugais", + "Punjabi": "Punjabi", + "Romanian": "Roumain", + "Russian": "Russe", + "Samoan": "Samoan", + "Scottish Gaelic": "Eaélique Ècossais", + "Serbian": "Serbe", + "Shona": "Shona", + "Sindhi": "Sindhi", + "Sinhala": "Cinghalais", + "Slovak": "Slovaque", + "Slovenian": "Slovène", + "Somali": "Somalien", + "Southern Sotho": "Sotho du Sud", + "Spanish": "Espagnol", + "Spanish (Latin America)": "Espagnol (Amérique latine)", + "Sundanese": "Sundanais", + "Swahili": "Swahili", + "Swedish": "Suédois", + "Tajik": "Tajik", + "Tamil": "Tamil", + "Telugu": "Telugu", + "Thai": "Thaï", + "Turkish": "Turc", + "Ukrainian": "Ukrainien", + "Urdu": "Ourdou", + "Uzbek": "Ouzbek", + "Vietnamese": "Vietnamien", + "Welsh": "Gallois", + "Western Frisian": "Frison occidental", + "Xhosa": "Xhosa", + "Yiddish": "Yiddish", + "Yoruba": "Yoruba", + "Zulu": "Zoulou", + "`x` years": "`x` ans", + "`x` months": "`x` mois", + "`x` weeks": "`x` semaines", + "`x` days": "`x` jours", + "`x` hours": "`x` heures", + "`x` minutes": "`x` minutes", + "`x` seconds": "`x` secondes", + "Fallback comments: ": "Fallback comments: ", + "Popular": "Populaire", + "Top": "Top", + "About": "A Propos", + "Rating: ": "Évaluation : ", + "Language: ": "Langue : ", + "View as playlist": "", + "Default": "Défaut", + "Music": "Musique", + "Gaming": "Jeux Vidéo", + "News": "Actualités", + "Movies": "Films", + "Download": "Télécharger", + "Download as: ": "Télécharger en : ", + "%A %B %-d, %Y": "%A %-d %B %Y", + "(edited)": "(modifié)", + "Youtube permalink of the comment": "Lien YouTube permanent vers le commentaire", + "`x` marked it with a ❤": "`x` l'a marqué d'un ❤", + "Audio mode": "Mode Audio", + "Video mode": "Mode Vidéo", + "Videos": "Vidéos", + "Playlists": "Liste de lecture", + "Current version: ": "Version :" } diff --git a/locales/it.json b/locales/it.json index 1def7c2e..97b7c8a4 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` iscritti", - "`x` videos": "`x` video", - "LIVE": "IN DIRETTA", - "Shared `x` ago": "Condiviso `x` fa", - "Unsubscribe": "Disiscriviti", - "Subscribe": "Iscriviti", - "View channel on YouTube": "Vedi canale su YouTube", - "newest": "Data di aggiunta (più recente)", - "oldest": "Data di aggiunta (più vecchia)", - "popular": "Tendenze", - "last": "", - "Next page": "Pagina successiva", - "Previous page": "Pagina precedente", - "Clear watch history?": "Sei sicuro di voler cancellare la cronologia dei video guardati?", - "Yes": "Si", - "No": "No", - "Import and Export Data": "Importazione ed esportazione dati", - "Import": "Importa", - "Import Invidious data": "Importa dati Invidious", - "Import YouTube subscriptions": "Importa le iscrizioni da YouTube", - "Import FreeTube subscriptions (.db)": "Importa le iscrizioni da FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Importa le iscrizioni da NewPipe (.json)", - "Import NewPipe data (.zip)": "Importa i dati di NewPipe (.zip)", - "Export": "Esporta", - "Export subscriptions as OPML": "Esporta gli abbonamenti come OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Esporta gli abbonamenti come OPML (per NewPipe e FreeTube)", - "Export data as JSON": "Esporta i dati in formato JSON", - "Delete account?": "Sei sicuro di voler cancellare l'account?", - "History": "Cronologia", - "An alternative front-end to YouTube": "Un'interfaccia alternativa per YouTube", - "JavaScript license information": "Info licenze JavaScript", - "source": "sorgente", - "Login": "Entra", - "Login/Register": "Entra/Registrati", - "Login to Google": "Entra con Google", - "User ID": "ID utente", - "Password": "Password", - "Time (h:mm:ss):": "Orario (h:mm:ss):", - "Text CAPTCHA": "Testo del CAPTCHA", - "Image CAPTCHA": "Immagine CAPTCHA", - "Sign In": "Entra", - "Register": "Registrati", - "Email": "Email", - "Google verification code": "Codice di verifica Google", - "Preferences": "Preferenze", - "Player preferences": "Preferenze del riproduttore", - "Always loop: ": "Ripeti sempre: ", - "Autoplay: ": "Riproduzione automatica: ", - "Autoplay next video: ": "Riproduci automaticamente il prossimo video: ", - "Listen by default: ": "Modalità solo audio come predefinita: ", - "Proxy videos? ": "", - "Default speed: ": "Velocità di riproduzione predefinita: ", - "Preferred video quality: ": "Preferenza sulla qualità video: ", - "Player volume: ": "Volume di riproduzione: ", - "Default comments: ": "Origine dei commenti: ", - "Default captions: ": "Sottotitoli predefiniti: ", - "Fallback captions: ": "Sottotitoli alternativi: ", - "Show related videos? ": "Mostra video correlati? ", - "Visual preferences": "Preferenze grafiche", - "Dark mode: ": "Tema scuro: ", - "Thin mode: ": "Modalità per connessioni lente: ", - "Subscription preferences": "Preferenze iscrizioni", - "Redirect homepage to feed: ": "Reindirizza la pagina principale a quella delle iscrizioni: ", - "Number of videos shown in feed: ": "Numero di video da mostrare nelle iscrizioni: ", - "Sort videos by: ": "Ordinare i video per: ", - "published": "data di pubblicazione", - "published - reverse": "data di pubblicazione - decrescente", - "alphabetically": "ordine alfabetico", - "alphabetically - reverse": "ordine alfabetico - decrescente", - "channel name": "nome del canale", - "channel name - reverse": "nome del canale - decrescente", - "Only show latest video from channel: ": "Mostra solo il video più recente del canale: ", - "Only show latest unwatched video from channel: ": "Mostra solo il video più recente non guardato del canale: ", - "Only show unwatched: ": "Mostra solo i video non guardati: ", - "Only show notifications (if there are any): ": "Mostra solo le notifiche (se presenti): ", - "Data preferences": "Preferenze dati", - "Clear watch history": "Cancella la cronologia dei video guardati", - "Import/Export data": "Importazione/esportazione dati", - "Manage subscriptions": "Gestisci le iscrizioni", - "Watch history": "Cronologia dei video", - "Delete account": "Elimina l'account", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", - "Save preferences": "Salva le preferenze", - "Subscription manager": "Gestisci le iscrizioni", - "`x` subscriptions": "`x` iscrizioni", - "Import/Export": "Importa/esporta", - "unsubscribe": "disiscriviti", - "Subscriptions": "Iscrizioni", - "`x` unseen notifications": "`x` notifiche non visualizzate", - "search": "Cerca", - "Sign out": "Esci", - "Released under the AGPLv3 by Omar Roth.": "Pubblicato con licenza AGPLv3 da Omar Roth.", - "Source available here.": "Codice sorgente.", - "View JavaScript license information.": "Guarda le informazioni di licenza del codice JavaScript.", - "View privacy policy.": "", - "Trending": "Tendenze", - "Unlisted": "", - "Watch video on Youtube": "Guarda il video su YouTube", - "Genre: ": "Genere: ", - "License: ": "Licenza: ", - "Family friendly? ": "Per tutti? ", - "Wilson score: ": "Punteggio di Wilson: ", - "Engagement: ": "Tasso di coinvolgimento: ", - "Whitelisted regions: ": "Regioni nella lista bianca: ", - "Blacklisted regions: ": "Regioni nella lista nera: ", - "Shared `x`": "Condiviso `x`", - "`x` views": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Ciao! Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti. Considera che potrebbe volerci più tempo.", - "View YouTube comments": "Visualizza i commenti da YouTube", - "View more comments on Reddit": "Visualizza più commenti su Reddit", - "View `x` comments": "Visualizza `x` commenti", - "View Reddit comments": "Visualizza i commenti da Reddit", - "Hide replies": "Nascondi le risposte", - "Show replies": "Mostra le risposte", - "Incorrect password": "Password sbagliata", - "Quota exceeded, try again in a few hours": "Limite superato, prova di nuovo fra qualche ora", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Impossibile autenticarsi, controlla che l'autenticazione in due passaggi (Authenticator o SMS) sia attiva.", - "Invalid TFA code": "Codice di autenticazione a due fattori non valido", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login fallito. L'errore potrebbe essere causato dal fatto che la verifica in due passaggi non è attiva sul tuo account.", - "Invalid answer": "Risposta errata", - "Invalid CAPTCHA": "CAPTCHA errato", - "CAPTCHA is a required field": "Il CAPTCHA è un campo obbligatorio", - "User ID is a required field": "L'ID utente è obbligatorio", - "Password is a required field": "La password è un campo obbligatorio", - "Invalid username or password": "Nome utente o password errati", - "Please sign in using 'Sign in with Google'": "Per favore accedi con \"Entra con Google\"", - "Password cannot be empty": "La password non può essere vuota", - "Password cannot be longer than 55 characters": "La password non può contenere più di 55 caratteri", - "Please sign in": "Per favore, entra", - "Invidious Private Feed for `x`": "Feed privato Invidious per `x`", - "channel:`x`": "canale:`x`", - "Deleted or invalid channel": "Canale cancellato o invalido", - "This channel does not exist.": "Canale inesistente.", - "Could not get channel info.": "Impossibile ottenere le informazioni del canale.", - "Could not fetch comments": "Impossibile recuperare i commenti", - "View `x` replies": "Visualizza `x` risposte", - "`x` ago": "`x` fa", - "Load more": "Carica altro", - "`x` points": "`x` punti", - "Could not create mix.": "Impossibile creare il mix.", - "Playlist is empty": "Playlist vuota", - "Invalid playlist.": "Playlist invalida.", - "Playlist does not exist.": "Playlist inesistente.", - "Could not pull trending pages.": "Impossibile recuperare le tendenze.", - "Hidden field \"challenge\" is a required field": "Il campo nascosto \"challenge\" è obbligatorio", - "Hidden field \"token\" is a required field": "Il campo nascosto \"token\" è obbligatorio", - "Invalid challenge": "Campo \"challenge\" invalido", - "Invalid token": "Campo \"token\" invalido", - "Invalid user": "Utente invalido", - "Token is expired, please try again": "Token scaduto, riprova", - "English": "Inglese", - "English (auto-generated)": "Inglese (generati automaticamente)", - "Afrikaans": "Afrikaans", - "Albanian": "Albanese", - "Amharic": "Amarico", - "Arabic": "Arabo", - "Armenian": "Armeno", - "Azerbaijani": "Azero", - "Bangla": "Bengalese", - "Basque": "Basco", - "Belarusian": "Biellorusso", - "Bosnian": "Bosniaco", - "Bulgarian": "Bulgaro", - "Burmese": "Birmano", - "Catalan": "Catalano", - "Cebuano": "Sugbuanon", - "Chinese (Simplified)": "Cinese semplifiato", - "Chinese (Traditional)": "Cinese tradizionale", - "Corsican": "Corso", - "Croatian": "Croato", - "Czech": "Ceco", - "Danish": "Danese", - "Dutch": "Olandese", - "Esperanto": "Esperanto", - "Estonian": "Estone", - "Filipino": "Filippino", - "Finnish": "Finlandese", - "French": "Francese", - "Galician": "Galiziano", - "Georgian": "Georgiano", - "German": "Tedesco", - "Greek": "Greco", - "Gujarati": "Gujarati", - "Haitian Creole": "Creolo haitiano", - "Hausa": "Lingua hausa", - "Hawaiian": "Hawaiano", - "Hebrew": "Ebreo", - "Hindi": "Hindi", - "Hmong": "Hmong", - "Hungarian": "Ungarese", - "Icelandic": "Islandese", - "Igbo": "Igbo", - "Indonesian": "Indonesiano", - "Irish": "Irlandese", - "Italian": "Italiano", - "Japanese": "Giapponese", - "Javanese": "Giavanese", - "Kannada": "Kannada", - "Kazakh": "Kazaco", - "Khmer": "Khmer", - "Korean": "Coreano", - "Kurdish": "Curdo", - "Kyrgyz": "Kirghize", - "Lao": "Lao", - "Latin": "Latino", - "Latvian": "Lettone", - "Lithuanian": "Lituano", - "Luxembourgish": "Lussemburghese", - "Macedonian": "Macedone", - "Malagasy": "Malgascio", - "Malay": "Malese", - "Malayalam": "Lingua malayalam", - "Maltese": "Maltese", - "Maori": "Maori", - "Marathi": "Marathi", - "Mongolian": "Mongolo", - "Nepali": "Nepalese", - "Norwegian": "Norvegese", - "Nyanja": "Nyanja", - "Pashto": "Lingua pashtu", - "Persian": "Persiano", - "Polish": "Polacco", - "Portuguese": "Portoghese", - "Punjabi": "Punjabi", - "Romanian": "Rumeno", - "Russian": "Russo", - "Samoan": "Samoan", - "Scottish Gaelic": "Gaelico scozzese", - "Serbian": "Serbo", - "Shona": "Shona", - "Sindhi": "Sindhi", - "Sinhala": "Cingalese", - "Slovak": "Slovacco", - "Slovenian": "Sloveno", - "Somali": "Somalo", - "Southern Sotho": "Sotho del Sud", - "Spanish": "Spagnolo", - "Spanish (Latin America)": "Spagnolo (America latina)", - "Sundanese": "Sudanese", - "Swahili": "Swahili", - "Swedish": "Svedese", - "Tajik": "Tajik", - "Tamil": "Tamil", - "Telugu": "Telugu", - "Thai": "Thaï", - "Turkish": "Turco", - "Ukrainian": "Ucraino", - "Urdu": "Urdu", - "Uzbek": "Uzbeco", - "Vietnamese": "Vietnamese", - "Welsh": "Gallese", - "Western Frisian": "Frisone occidentale", - "Xhosa": "Xhosa", - "Yiddish": "Yiddish", - "Yoruba": "Yoruba", - "Zulu": "Zulu", - "`x` years": "`x` anni", - "`x` months": "`x` mesi", - "`x` weeks": "`x` settimane", - "`x` days": "`x` giorni", - "`x` hours": "`x` ore", - "`x` minutes": "`x` minuti", - "`x` seconds": "`x` secondi", - "Fallback comments: ": "Commenti alternativi: ", - "Popular": "Popolare", - "Top": "Top", - "About": "A proposito", - "Rating: ": "Punteggio: ", - "Language: ": "Lingua: ", - "View as playlist": "", - "Default": "Predefinito", - "Music": "Musica", - "Gaming": "Videogiochi", - "News": "Notizie", - "Movies": "Film", - "Download": "Scarica", - "Download as: ": "Scarica come: ", - "%A %B %-d, %Y": "%A %-d %B %Y", - "(edited)": "(modificato)", - "Youtube permalink of the comment": "Link permanente al commento di YouTube", - "`x` marked it with a ❤": "`x` l'ha contrassegnato con un ❤", - "Audio mode": "Modalità audio", - "Video mode": "Modalità video", - "Videos": "", - "Playlists": "", - "Current version: ": "" + "`x` subscribers": "`x` iscritti", + "`x` videos": "`x` video", + "LIVE": "IN DIRETTA", + "Shared `x` ago": "Condiviso `x` fa", + "Unsubscribe": "Disiscriviti", + "Subscribe": "Iscriviti", + "View channel on YouTube": "Vedi canale su YouTube", + "newest": "Data di aggiunta (più recente)", + "oldest": "Data di aggiunta (più vecchia)", + "popular": "Tendenze", + "last": "", + "Next page": "Pagina successiva", + "Previous page": "Pagina precedente", + "Clear watch history?": "Sei sicuro di voler cancellare la cronologia dei video guardati?", + "Yes": "Si", + "No": "No", + "Import and Export Data": "Importazione ed esportazione dati", + "Import": "Importa", + "Import Invidious data": "Importa dati Invidious", + "Import YouTube subscriptions": "Importa le iscrizioni da YouTube", + "Import FreeTube subscriptions (.db)": "Importa le iscrizioni da FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importa le iscrizioni da NewPipe (.json)", + "Import NewPipe data (.zip)": "Importa i dati di NewPipe (.zip)", + "Export": "Esporta", + "Export subscriptions as OPML": "Esporta gli abbonamenti come OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Esporta gli abbonamenti come OPML (per NewPipe e FreeTube)", + "Export data as JSON": "Esporta i dati in formato JSON", + "Delete account?": "Sei sicuro di voler cancellare l'account?", + "History": "Cronologia", + "An alternative front-end to YouTube": "Un'interfaccia alternativa per YouTube", + "JavaScript license information": "Info licenze JavaScript", + "source": "sorgente", + "Login": "Entra", + "Login/Register": "Entra/Registrati", + "Login to Google": "Entra con Google", + "User ID": "ID utente", + "Password": "Password", + "Time (h:mm:ss):": "Orario (h:mm:ss):", + "Text CAPTCHA": "Testo del CAPTCHA", + "Image CAPTCHA": "Immagine CAPTCHA", + "Sign In": "Entra", + "Register": "Registrati", + "Email": "Email", + "Google verification code": "Codice di verifica Google", + "Preferences": "Preferenze", + "Player preferences": "Preferenze del riproduttore", + "Always loop: ": "Ripeti sempre: ", + "Autoplay: ": "Riproduzione automatica: ", + "Autoplay next video: ": "Riproduci automaticamente il prossimo video: ", + "Listen by default: ": "Modalità solo audio come predefinita: ", + "Proxy videos? ": "", + "Default speed: ": "Velocità di riproduzione predefinita: ", + "Preferred video quality: ": "Preferenza sulla qualità video: ", + "Player volume: ": "Volume di riproduzione: ", + "Default comments: ": "Origine dei commenti: ", + "Default captions: ": "Sottotitoli predefiniti: ", + "Fallback captions: ": "Sottotitoli alternativi: ", + "Show related videos? ": "Mostra video correlati? ", + "Visual preferences": "Preferenze grafiche", + "Dark mode: ": "Tema scuro: ", + "Thin mode: ": "Modalità per connessioni lente: ", + "Subscription preferences": "Preferenze iscrizioni", + "Redirect homepage to feed: ": "Reindirizza la pagina principale a quella delle iscrizioni: ", + "Number of videos shown in feed: ": "Numero di video da mostrare nelle iscrizioni: ", + "Sort videos by: ": "Ordinare i video per: ", + "published": "data di pubblicazione", + "published - reverse": "data di pubblicazione - decrescente", + "alphabetically": "ordine alfabetico", + "alphabetically - reverse": "ordine alfabetico - decrescente", + "channel name": "nome del canale", + "channel name - reverse": "nome del canale - decrescente", + "Only show latest video from channel: ": "Mostra solo il video più recente del canale: ", + "Only show latest unwatched video from channel: ": "Mostra solo il video più recente non guardato del canale: ", + "Only show unwatched: ": "Mostra solo i video non guardati: ", + "Only show notifications (if there are any): ": "Mostra solo le notifiche (se presenti): ", + "Data preferences": "Preferenze dati", + "Clear watch history": "Cancella la cronologia dei video guardati", + "Import/Export data": "Importazione/esportazione dati", + "Manage subscriptions": "Gestisci le iscrizioni", + "Watch history": "Cronologia dei video", + "Delete account": "Elimina l'account", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Report statistics? ": "", + "Save preferences": "Salva le preferenze", + "Subscription manager": "Gestisci le iscrizioni", + "`x` subscriptions": "`x` iscrizioni", + "Import/Export": "Importa/esporta", + "unsubscribe": "disiscriviti", + "Subscriptions": "Iscrizioni", + "`x` unseen notifications": "`x` notifiche non visualizzate", + "search": "Cerca", + "Sign out": "Esci", + "Released under the AGPLv3 by Omar Roth.": "Pubblicato con licenza AGPLv3 da Omar Roth.", + "Source available here.": "Codice sorgente.", + "View JavaScript license information.": "Guarda le informazioni di licenza del codice JavaScript.", + "View privacy policy.": "", + "Trending": "Tendenze", + "Unlisted": "", + "Watch video on Youtube": "Guarda il video su YouTube", + "Genre: ": "Genere: ", + "License: ": "Licenza: ", + "Family friendly? ": "Per tutti? ", + "Wilson score: ": "Punteggio di Wilson: ", + "Engagement: ": "Tasso di coinvolgimento: ", + "Whitelisted regions: ": "Regioni nella lista bianca: ", + "Blacklisted regions: ": "Regioni nella lista nera: ", + "Shared `x`": "Condiviso `x`", + "`x` views": "", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Ciao! Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti. Considera che potrebbe volerci più tempo.", + "View YouTube comments": "Visualizza i commenti da YouTube", + "View more comments on Reddit": "Visualizza più commenti su Reddit", + "View `x` comments": "Visualizza `x` commenti", + "View Reddit comments": "Visualizza i commenti da Reddit", + "Hide replies": "Nascondi le risposte", + "Show replies": "Mostra le risposte", + "Incorrect password": "Password sbagliata", + "Quota exceeded, try again in a few hours": "Limite superato, prova di nuovo fra qualche ora", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Impossibile autenticarsi, controlla che l'autenticazione in due passaggi (Authenticator o SMS) sia attiva.", + "Invalid TFA code": "Codice di autenticazione a due fattori non valido", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login fallito. L'errore potrebbe essere causato dal fatto che la verifica in due passaggi non è attiva sul tuo account.", + "Invalid answer": "Risposta errata", + "Invalid CAPTCHA": "CAPTCHA errato", + "CAPTCHA is a required field": "Il CAPTCHA è un campo obbligatorio", + "User ID is a required field": "L'ID utente è obbligatorio", + "Password is a required field": "La password è un campo obbligatorio", + "Invalid username or password": "Nome utente o password errati", + "Please sign in using 'Sign in with Google'": "Per favore accedi con \"Entra con Google\"", + "Password cannot be empty": "La password non può essere vuota", + "Password cannot be longer than 55 characters": "La password non può contenere più di 55 caratteri", + "Please sign in": "Per favore, entra", + "Invidious Private Feed for `x`": "Feed privato Invidious per `x`", + "channel:`x`": "canale:`x`", + "Deleted or invalid channel": "Canale cancellato o invalido", + "This channel does not exist.": "Canale inesistente.", + "Could not get channel info.": "Impossibile ottenere le informazioni del canale.", + "Could not fetch comments": "Impossibile recuperare i commenti", + "View `x` replies": "Visualizza `x` risposte", + "`x` ago": "`x` fa", + "Load more": "Carica altro", + "`x` points": "`x` punti", + "Could not create mix.": "Impossibile creare il mix.", + "Playlist is empty": "Playlist vuota", + "Invalid playlist.": "Playlist invalida.", + "Playlist does not exist.": "Playlist inesistente.", + "Could not pull trending pages.": "Impossibile recuperare le tendenze.", + "Hidden field \"challenge\" is a required field": "Il campo nascosto \"challenge\" è obbligatorio", + "Hidden field \"token\" is a required field": "Il campo nascosto \"token\" è obbligatorio", + "Invalid challenge": "Campo \"challenge\" invalido", + "Invalid token": "Campo \"token\" invalido", + "Invalid user": "Utente invalido", + "Token is expired, please try again": "Token scaduto, riprova", + "English": "Inglese", + "English (auto-generated)": "Inglese (generati automaticamente)", + "Afrikaans": "Afrikaans", + "Albanian": "Albanese", + "Amharic": "Amarico", + "Arabic": "Arabo", + "Armenian": "Armeno", + "Azerbaijani": "Azero", + "Bangla": "Bengalese", + "Basque": "Basco", + "Belarusian": "Biellorusso", + "Bosnian": "Bosniaco", + "Bulgarian": "Bulgaro", + "Burmese": "Birmano", + "Catalan": "Catalano", + "Cebuano": "Sugbuanon", + "Chinese (Simplified)": "Cinese semplifiato", + "Chinese (Traditional)": "Cinese tradizionale", + "Corsican": "Corso", + "Croatian": "Croato", + "Czech": "Ceco", + "Danish": "Danese", + "Dutch": "Olandese", + "Esperanto": "Esperanto", + "Estonian": "Estone", + "Filipino": "Filippino", + "Finnish": "Finlandese", + "French": "Francese", + "Galician": "Galiziano", + "Georgian": "Georgiano", + "German": "Tedesco", + "Greek": "Greco", + "Gujarati": "Gujarati", + "Haitian Creole": "Creolo haitiano", + "Hausa": "Lingua hausa", + "Hawaiian": "Hawaiano", + "Hebrew": "Ebreo", + "Hindi": "Hindi", + "Hmong": "Hmong", + "Hungarian": "Ungarese", + "Icelandic": "Islandese", + "Igbo": "Igbo", + "Indonesian": "Indonesiano", + "Irish": "Irlandese", + "Italian": "Italiano", + "Japanese": "Giapponese", + "Javanese": "Giavanese", + "Kannada": "Kannada", + "Kazakh": "Kazaco", + "Khmer": "Khmer", + "Korean": "Coreano", + "Kurdish": "Curdo", + "Kyrgyz": "Kirghize", + "Lao": "Lao", + "Latin": "Latino", + "Latvian": "Lettone", + "Lithuanian": "Lituano", + "Luxembourgish": "Lussemburghese", + "Macedonian": "Macedone", + "Malagasy": "Malgascio", + "Malay": "Malese", + "Malayalam": "Lingua malayalam", + "Maltese": "Maltese", + "Maori": "Maori", + "Marathi": "Marathi", + "Mongolian": "Mongolo", + "Nepali": "Nepalese", + "Norwegian": "Norvegese", + "Nyanja": "Nyanja", + "Pashto": "Lingua pashtu", + "Persian": "Persiano", + "Polish": "Polacco", + "Portuguese": "Portoghese", + "Punjabi": "Punjabi", + "Romanian": "Rumeno", + "Russian": "Russo", + "Samoan": "Samoan", + "Scottish Gaelic": "Gaelico scozzese", + "Serbian": "Serbo", + "Shona": "Shona", + "Sindhi": "Sindhi", + "Sinhala": "Cingalese", + "Slovak": "Slovacco", + "Slovenian": "Sloveno", + "Somali": "Somalo", + "Southern Sotho": "Sotho del Sud", + "Spanish": "Spagnolo", + "Spanish (Latin America)": "Spagnolo (America latina)", + "Sundanese": "Sudanese", + "Swahili": "Swahili", + "Swedish": "Svedese", + "Tajik": "Tajik", + "Tamil": "Tamil", + "Telugu": "Telugu", + "Thai": "Thaï", + "Turkish": "Turco", + "Ukrainian": "Ucraino", + "Urdu": "Urdu", + "Uzbek": "Uzbeco", + "Vietnamese": "Vietnamese", + "Welsh": "Gallese", + "Western Frisian": "Frisone occidentale", + "Xhosa": "Xhosa", + "Yiddish": "Yiddish", + "Yoruba": "Yoruba", + "Zulu": "Zulu", + "`x` years": "`x` anni", + "`x` months": "`x` mesi", + "`x` weeks": "`x` settimane", + "`x` days": "`x` giorni", + "`x` hours": "`x` ore", + "`x` minutes": "`x` minuti", + "`x` seconds": "`x` secondi", + "Fallback comments: ": "Commenti alternativi: ", + "Popular": "Popolare", + "Top": "Top", + "About": "A proposito", + "Rating: ": "Punteggio: ", + "Language: ": "Lingua: ", + "View as playlist": "", + "Default": "Predefinito", + "Music": "Musica", + "Gaming": "Videogiochi", + "News": "Notizie", + "Movies": "Film", + "Download": "Scarica", + "Download as: ": "Scarica come: ", + "%A %B %-d, %Y": "%A %-d %B %Y", + "(edited)": "(modificato)", + "Youtube permalink of the comment": "Link permanente al commento di YouTube", + "`x` marked it with a ❤": "`x` l'ha contrassegnato con un ❤", + "Audio mode": "Modalità audio", + "Video mode": "Modalità video", + "Videos": "", + "Playlists": "", + "Current version: ": "" } diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 0de28379..9ce52477 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` abonnenter", - "`x` videos": "`x` videoer", - "LIVE": "SANNTIDSVISNING", - "Shared `x` ago": "Delt for `x` siden", - "Unsubscribe": "Opphev abonnement", - "Subscribe": "Abonner", - "View channel on YouTube": "Vis kanal på YouTube", - "newest": "nyeste", - "oldest": "eldste", - "popular": "populært", - "last": "siste", - "Next page": "Neste side", - "Previous page": "Forrige side", - "Clear watch history?": "Tøm visningshistorikk?", - "Yes": "Ja", - "No": "Nei", - "Import and Export Data": "Importer- og eksporter data", - "Import": "Importer", - "Import Invidious data": "Importer Invidious-data", - "Import YouTube subscriptions": "Importer YouTube-abonnenter", - "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", - "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", - "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", - "Export": "Eksporter", - "Export subscriptions as OPML": "Eksporter abonnenter som OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", - "Export data as JSON": "Eksporter data som JSON", - "Delete account?": "Slett konto?", - "History": "Historikk", - "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", - "JavaScript license information": "JavaScript-lisensinformasjon", - "source": "kilde", - "Login": "Logg inn", - "Login/Register": "Logg inn/registrer", - "Login to Google": "Logg inn med Google", - "User ID": "Bruker-ID", - "Password": "Passord", - "Time (h:mm:ss):": "Tid (h:mm:ss):", - "Text CAPTCHA": "Tekst-CAPTCHA", - "Image CAPTCHA": "Bilde-CAPTCHA", - "Sign In": "Innlogging", - "Register": "Registrer", - "Email": "E-post", - "Google verification code": "Google-bekreftelseskode", - "Preferences": "Innstillinger", - "Player preferences": "Avspillerinnstillinger", - "Always loop: ": "Alltid gjenta: ", - "Autoplay: ": "Autoavspilling: ", - "Autoplay next video: ": "Autospill neste video: ", - "Listen by default: ": "Lytt som forvalg: ", - "Proxy videos? ": "Mellomtjen videoer? ", - "Default speed: ": "Forvalgt hastighet: ", - "Preferred video quality: ": "Foretrukket videokvalitet: ", - "Player volume: ": "Avspillerlydstyrke: ", - "Default comments: ": "Forvalgte kommentarer: ", - "Default captions: ": "Forvalgte undertitler: ", - "Fallback captions: ": "Tilbakefallsundertitler: ", - "Show related videos? ": "Vis relaterte videoer? ", - "Visual preferences": "Visuelle innstillinger", - "Dark mode: ": "Mørk drakt: ", - "Thin mode: ": "Tynt modus: ", - "Subscription preferences": "Abonnementsinnstillinger", - "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", - "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", - "Sort videos by: ": "Sorter videoer etter: ", - "published": "publisert", - "published - reverse": "publisert - motsatt", - "alphabetically": "alfabetisk", - "alphabetically - reverse": "alfabetisk - motsatt", - "channel name": "kanalnavn", - "channel name - reverse": "kanalnavn - motsatt", - "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", - "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", - "Only show unwatched: ": "Kun vis usette: ", - "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", - "Data preferences": "Datainnstillinger", - "Clear watch history": "Tøm visningshistorikk", - "Import/Export data": "Importer/eksporter data", - "Manage subscriptions": "Behandle abonnementer", - "Watch history": "Visningshistorikk", - "Delete account": "Slett konto", - "Administrator preferences": "Administratorinnstillinger", - "Default homepage: ": "Forvalgt hjemmeside: ", - "Feed menu: ": "Flyt-meny: ", - "Top enabled? ": "Topp påskrudd? ", - "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", - "Login enabled? ": "Innlogging påskrudd? ", - "Registration enabled? ": "Registrering påskrudd? ", - "Report statistics? ": "Innrapporter statistikk? ", - "Save preferences": "Lagre innstillinger", - "Subscription manager": "Abonnementsbehandler", - "`x` subscriptions": "`x` abonnementer", - "Import/Export": "Importer/eksporter", - "unsubscribe": "opphev abonnement", - "Subscriptions": "Abonnement", - "`x` unseen notifications": "`x` usette merknader", - "search": "søk", - "Sign out": "Logg ut", - "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", - "Source available here.": "Kildekode tilgjengelig her.", - "View JavaScript license information.": "Vis JavaScript-lisensinfo.", - "View privacy policy.": "Vis personvernspraksis.", - "Trending": "Trendsettende", - "Unlisted": "Ulistet", - "Watch video on Youtube": "Vis video på YouTube", - "Genre: ": "Sjanger: ", - "License: ": "Lisens: ", - "Family friendly? ": "Familievennlig? ", - "Wilson score: ": "Wilson-poengsum: ", - "Engagement: ": "Engasjement: ", - "Whitelisted regions: ": "Hvitlistede regioner: ", - "Blacklisted regions: ": "Svartelistede regioner: ", - "Shared `x`": "Delt `x`", - "`x` views": "", - "Premieres in `x`": "Premiere om `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", - "View YouTube comments": "Vis YouTube-kommentarer", - "View more comments on Reddit": "Vis flere kommenterer på Reddit", - "View `x` comments": "Vis `x` kommentarer", - "View Reddit comments": "Vis Reddit-kommentarer", - "Hide replies": "Skjul svar", - "Show replies": "Vis svar", - "Incorrect password": "Feil passord", - "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", - "Invalid TFA code": "Ugyldig tofaktorkode", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", - "Invalid answer": "Ugyldig svar", - "Invalid CAPTCHA": "Ugyldig CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", - "User ID is a required field": "Bruker-ID er et påkrevd felt", - "Password is a required field": "Passord er et påkrevd felt", - "Invalid username or password": "Ugyldig brukernavn eller passord", - "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", - "Password cannot be empty": "Passordet kan ikke være tomt", - "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", - "Please sign in": "Logg inn", - "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", - "channel:`x`": "kanal `x`", - "Deleted or invalid channel": "Slettet eller ugyldig kanal", - "This channel does not exist.": "Denne kanalen finnes ikke.", - "Could not get channel info.": "Kunne ikke innhente kanalinfo.", - "Could not fetch comments": "Kunne ikke hente kommentarer", - "View `x` replies": "Vis `x` svar", - "`x` ago": "`x` siden", - "Load more": "Last inn flere", - "`x` points": "`x` poeng", - "Could not create mix.": "Kunne ikke opprette miks.", - "Playlist is empty": "Spillelisten er tom", - "Invalid playlist.": "Ugyldig spilleliste.", - "Playlist does not exist.": "Spillelisten finnes ikke.", - "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", - "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", - "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", - "Invalid challenge": "Ugyldig utfordring", - "Invalid token": "Ugyldig symbol", - "Invalid user": "Ugyldig bruker", - "Token is expired, please try again": "Symbol utløpt, prøv igjen", - "English": "Engelsk", - "English (auto-generated)": "Engelsk (auto-generert)", - "Afrikaans": "", - "Albanian": "Albansk", - "Amharic": "", - "Arabic": "Arabisk", - "Armenian": "Armensk", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "Hviterussisk", - "Bosnian": "Bosnisk", - "Bulgarian": "Bulgarsk", - "Burmese": "Burmesisk", - "Catalan": "Katalansk", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "Tsjekkisk", - "Danish": "Dansk", - "Dutch": "", - "Esperanto": "Esperanto", - "Estonian": "", - "Filipino": "", - "Finnish": "Finsk", - "French": "Fransk", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "Ungarsk", - "Icelandic": "Islandsk", - "Igbo": "", - "Indonesian": "Indonesisk", - "Irish": "Irsk", - "Italian": "Italiensk", - "Japanese": "Japansk", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "Norsk bokmål", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "Russisk", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "Serbisk", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "Slovakisk", - "Slovenian": "Slovensk", - "Somali": "Somali", - "Southern Sotho": "", - "Spanish": "Spansk", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "Svensk", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "Tyrkisk", - "Ukrainian": "Ukrainsk", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "Vietnamesisk", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` år", - "`x` months": "`x` måneder", - "`x` weeks": "`x` uker", - "`x` days": "`x` dager", - "`x` hours": "`x` timer", - "`x` minutes": "`x` minutter", - "`x` seconds": "`x` sekunder", - "Fallback comments: ": "Tilbakefallskommentarer: ", - "Popular": "Pupulært", - "Top": "Topp", - "About": "Om", - "Rating: ": "Vurdering: ", - "Language: ": "Språk: ", - "View as playlist": "", - "Default": "Forvalg", - "Music": "Musikk", - "Gaming": "Spill", - "News": "Nyheter", - "Movies": "Filmer", - "Download": "Last ned", - "Download as: ": "Last ned som: ", - "%A %B %-d, %Y": "", - "(edited)": "(redigert)", - "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", - "`x` marked it with a ❤": "`x` levnet et ❤", - "Audio mode": "Lydmodus", - "Video mode": "Video-modus", - "Videos": "Videoer", - "Playlists": "Spillelister", - "Current version: ": "Nåværende versjon: " + "`x` subscribers": "`x` abonnenter", + "`x` videos": "`x` videoer", + "LIVE": "SANNTIDSVISNING", + "Shared `x` ago": "Delt for `x` siden", + "Unsubscribe": "Opphev abonnement", + "Subscribe": "Abonner", + "View channel on YouTube": "Vis kanal på YouTube", + "newest": "nyeste", + "oldest": "eldste", + "popular": "populært", + "last": "siste", + "Next page": "Neste side", + "Previous page": "Forrige side", + "Clear watch history?": "Tøm visningshistorikk?", + "Yes": "Ja", + "No": "Nei", + "Import and Export Data": "Importer- og eksporter data", + "Import": "Importer", + "Import Invidious data": "Importer Invidious-data", + "Import YouTube subscriptions": "Importer YouTube-abonnenter", + "Import FreeTube subscriptions (.db)": "Importer FreeTube-abonnenter (.db)", + "Import NewPipe subscriptions (.json)": "Importer NewPipe-abonnenter (.json)", + "Import NewPipe data (.zip)": "Importer NewPipe-data (.zip)", + "Export": "Eksporter", + "Export subscriptions as OPML": "Eksporter abonnenter som OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksporter abonnenter som OPML (for NewPipe og FreeTube)", + "Export data as JSON": "Eksporter data som JSON", + "Delete account?": "Slett konto?", + "History": "Historikk", + "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", + "JavaScript license information": "JavaScript-lisensinformasjon", + "source": "kilde", + "Login": "Logg inn", + "Login/Register": "Logg inn/registrer", + "Login to Google": "Logg inn med Google", + "User ID": "Bruker-ID", + "Password": "Passord", + "Time (h:mm:ss):": "Tid (h:mm:ss):", + "Text CAPTCHA": "Tekst-CAPTCHA", + "Image CAPTCHA": "Bilde-CAPTCHA", + "Sign In": "Innlogging", + "Register": "Registrer", + "Email": "E-post", + "Google verification code": "Google-bekreftelseskode", + "Preferences": "Innstillinger", + "Player preferences": "Avspillerinnstillinger", + "Always loop: ": "Alltid gjenta: ", + "Autoplay: ": "Autoavspilling: ", + "Autoplay next video: ": "Autospill neste video: ", + "Listen by default: ": "Lytt som forvalg: ", + "Proxy videos? ": "Mellomtjen videoer? ", + "Default speed: ": "Forvalgt hastighet: ", + "Preferred video quality: ": "Foretrukket videokvalitet: ", + "Player volume: ": "Avspillerlydstyrke: ", + "Default comments: ": "Forvalgte kommentarer: ", + "Default captions: ": "Forvalgte undertitler: ", + "Fallback captions: ": "Tilbakefallsundertitler: ", + "Show related videos? ": "Vis relaterte videoer? ", + "Visual preferences": "Visuelle innstillinger", + "Dark mode: ": "Mørk drakt: ", + "Thin mode: ": "Tynt modus: ", + "Subscription preferences": "Abonnementsinnstillinger", + "Redirect homepage to feed: ": "Videresend hjemmeside til flyt: ", + "Number of videos shown in feed: ": "Antall videoer å vise i flyt: ", + "Sort videos by: ": "Sorter videoer etter: ", + "published": "publisert", + "published - reverse": "publisert - motsatt", + "alphabetically": "alfabetisk", + "alphabetically - reverse": "alfabetisk - motsatt", + "channel name": "kanalnavn", + "channel name - reverse": "kanalnavn - motsatt", + "Only show latest video from channel: ": "Kun vis siste video fra kanal: ", + "Only show latest unwatched video from channel: ": "Kun vis siste usette video fra kanal: ", + "Only show unwatched: ": "Kun vis usette: ", + "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", + "Data preferences": "Datainnstillinger", + "Clear watch history": "Tøm visningshistorikk", + "Import/Export data": "Importer/eksporter data", + "Manage subscriptions": "Behandle abonnementer", + "Watch history": "Visningshistorikk", + "Delete account": "Slett konto", + "Administrator preferences": "Administratorinnstillinger", + "Default homepage: ": "Forvalgt hjemmeside: ", + "Feed menu: ": "Flyt-meny: ", + "Top enabled? ": "Topp påskrudd? ", + "CAPTCHA enabled? ": "CAPTCHA påskrudd? ", + "Login enabled? ": "Innlogging påskrudd? ", + "Registration enabled? ": "Registrering påskrudd? ", + "Report statistics? ": "Innrapporter statistikk? ", + "Save preferences": "Lagre innstillinger", + "Subscription manager": "Abonnementsbehandler", + "`x` subscriptions": "`x` abonnementer", + "Import/Export": "Importer/eksporter", + "unsubscribe": "opphev abonnement", + "Subscriptions": "Abonnement", + "`x` unseen notifications": "`x` usette merknader", + "search": "søk", + "Sign out": "Logg ut", + "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", + "Source available here.": "Kildekode tilgjengelig her.", + "View JavaScript license information.": "Vis JavaScript-lisensinfo.", + "View privacy policy.": "Vis personvernspraksis.", + "Trending": "Trendsettende", + "Unlisted": "Ulistet", + "Watch video on Youtube": "Vis video på YouTube", + "Genre: ": "Sjanger: ", + "License: ": "Lisens: ", + "Family friendly? ": "Familievennlig? ", + "Wilson score: ": "Wilson-poengsum: ", + "Engagement: ": "Engasjement: ", + "Whitelisted regions: ": "Hvitlistede regioner: ", + "Blacklisted regions: ": "Svartelistede regioner: ", + "Shared `x`": "Delt `x`", + "`x` views": "", + "Premieres in `x`": "Premiere om `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", + "View YouTube comments": "Vis YouTube-kommentarer", + "View more comments on Reddit": "Vis flere kommenterer på Reddit", + "View `x` comments": "Vis `x` kommentarer", + "View Reddit comments": "Vis Reddit-kommentarer", + "Hide replies": "Skjul svar", + "Show replies": "Vis svar", + "Incorrect password": "Feil passord", + "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", + "Invalid TFA code": "Ugyldig tofaktorkode", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", + "Invalid answer": "Ugyldig svar", + "Invalid CAPTCHA": "Ugyldig CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", + "User ID is a required field": "Bruker-ID er et påkrevd felt", + "Password is a required field": "Passord er et påkrevd felt", + "Invalid username or password": "Ugyldig brukernavn eller passord", + "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", + "Password cannot be empty": "Passordet kan ikke være tomt", + "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", + "Please sign in": "Logg inn", + "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", + "channel:`x`": "kanal `x`", + "Deleted or invalid channel": "Slettet eller ugyldig kanal", + "This channel does not exist.": "Denne kanalen finnes ikke.", + "Could not get channel info.": "Kunne ikke innhente kanalinfo.", + "Could not fetch comments": "Kunne ikke hente kommentarer", + "View `x` replies": "Vis `x` svar", + "`x` ago": "`x` siden", + "Load more": "Last inn flere", + "`x` points": "`x` poeng", + "Could not create mix.": "Kunne ikke opprette miks.", + "Playlist is empty": "Spillelisten er tom", + "Invalid playlist.": "Ugyldig spilleliste.", + "Playlist does not exist.": "Spillelisten finnes ikke.", + "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", + "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", + "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", + "Invalid challenge": "Ugyldig utfordring", + "Invalid token": "Ugyldig symbol", + "Invalid user": "Ugyldig bruker", + "Token is expired, please try again": "Symbol utløpt, prøv igjen", + "English": "Engelsk", + "English (auto-generated)": "Engelsk (auto-generert)", + "Afrikaans": "", + "Albanian": "Albansk", + "Amharic": "", + "Arabic": "Arabisk", + "Armenian": "Armensk", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "Hviterussisk", + "Bosnian": "Bosnisk", + "Bulgarian": "Bulgarsk", + "Burmese": "Burmesisk", + "Catalan": "Katalansk", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "Tsjekkisk", + "Danish": "Dansk", + "Dutch": "", + "Esperanto": "Esperanto", + "Estonian": "", + "Filipino": "", + "Finnish": "Finsk", + "French": "Fransk", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "Ungarsk", + "Icelandic": "Islandsk", + "Igbo": "", + "Indonesian": "Indonesisk", + "Irish": "Irsk", + "Italian": "Italiensk", + "Japanese": "Japansk", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "Norsk bokmål", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "Russisk", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "Serbisk", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "Slovakisk", + "Slovenian": "Slovensk", + "Somali": "Somali", + "Southern Sotho": "", + "Spanish": "Spansk", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "Svensk", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "Tyrkisk", + "Ukrainian": "Ukrainsk", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "Vietnamesisk", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` år", + "`x` months": "`x` måneder", + "`x` weeks": "`x` uker", + "`x` days": "`x` dager", + "`x` hours": "`x` timer", + "`x` minutes": "`x` minutter", + "`x` seconds": "`x` sekunder", + "Fallback comments: ": "Tilbakefallskommentarer: ", + "Popular": "Pupulært", + "Top": "Topp", + "About": "Om", + "Rating: ": "Vurdering: ", + "Language: ": "Språk: ", + "View as playlist": "", + "Default": "Forvalg", + "Music": "Musikk", + "Gaming": "Spill", + "News": "Nyheter", + "Movies": "Filmer", + "Download": "Last ned", + "Download as: ": "Last ned som: ", + "%A %B %-d, %Y": "", + "(edited)": "(redigert)", + "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", + "`x` marked it with a ❤": "`x` levnet et ❤", + "Audio mode": "Lydmodus", + "Video mode": "Video-modus", + "Videos": "Videoer", + "Playlists": "Spillelister", + "Current version: ": "Nåværende versjon: " } diff --git a/locales/nl.json b/locales/nl.json index ac5f4ea5..05708939 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` abonnees", - "`x` videos": "`x` videos", - "LIVE": "LIVE", - "Shared `x` ago": "Gedeeld `x` geleden", - "Unsubscribe": "Abonnement opzeggen", - "Subscribe": "Abonneren", - "View channel on YouTube": "Bekijk kanaal op Youtube", - "newest": "nieuwste", - "oldest": "oudste", - "popular": "populair", - "last": "", - "Next page": "Volgende pagina", - "Previous page": "Vorige pagina", - "Clear watch history?": "Kijk geschiedenis wissen?", - "Yes": "Ja", - "No": "Nee", - "Import and Export Data": "Importeer en Exporteer Gegevens", - "Import": "Importeren", - "Import Invidious data": "Importeer Invidious gegevens", - "Import YouTube subscriptions": "Importeer Youtube abonnees", - "Import FreeTube subscriptions (.db)": "Importeer FreeTube abonnees (.db)", - "Import NewPipe subscriptions (.json)": "Importeer NewPipe abonnees (.json)", - "Import NewPipe data (.zip)": "Importeer NewPipe gegevens (.zip)", - "Export": "Exporteren", - "Export subscriptions as OPML": "Exporteer abonnees als OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporteer abonnees als OPML (voor NewPipe & FreeTube)", - "Export data as JSON": "Exporteer gegevens als JSON", - "Delete account?": "Verwijder account?", - "History": "Geschiedenis", - "An alternative front-end to YouTube": "Een alternatieve front-end voor YouTube", - "JavaScript license information": "JavaScript licentie informatie", - "source": "bron", - "Login": "Inloggen", - "Login/Register": "Inloggen/Registreren", - "Login to Google": "Inloggen op Google", - "User ID": "Gebruiker ID", - "Password": "Wachtwoord", - "Time (h:mm:ss):": "Tijd (h:mm:ss):", - "Text CAPTCHA": "Tekst CAPTCHA", - "Image CAPTCHA": "Afbeelding CAPTCHA", - "Sign In": "Aanmelden", - "Register": "Registreren", - "Email": "Email", - "Google verification code": "Google verificatie code", - "Preferences": "Voorkeuren", - "Player preferences": "Afspeler voorkeuren", - "Always loop: ": "Altijd herhalen: ", - "Autoplay: ": "Automatisch afspelen: ", - "Autoplay next video: ": "Automatisch volgende video afspelen: ", - "Listen by default: ": "Standaard luisteren: ", - "Proxy videos? ": "", - "Default speed: ": "Standaard snelheid: ", - "Preferred video quality: ": "Video kwaliteit voorkeur: ", - "Player volume: ": "Afspeler volume: ", - "Default comments: ": "Standaard reacties: ", - "Default captions: ": "Standaard ondertitels: ", - "Fallback captions: ": "Alternatieve ondertitels: ", - "Show related videos? ": "Laat gerelateerde videos zien? ", - "Visual preferences": "Visuele voorkeuren", - "Dark mode: ": "Donkere modus: ", - "Thin mode: ": "Smalle modus: ", - "Subscription preferences": "Abonnement voorkeuren", - "Redirect homepage to feed: ": "Startpagina omleiden naar feed: ", - "Number of videos shown in feed: ": "Aantal videos te zien in feed: ", - "Sort videos by: ": "Sorteer videos op: ", - "published": "gepubliceerd", - "published - reverse": "gepubliceerd - omgekeerd", - "alphabetically": "alfabetische volgorde", - "alphabetically - reverse": "alfabetisch - omgekeerd", - "channel name": "kanaal naam", - "channel name - reverse": "kanaal naam - omgekeerd", - "Only show latest video from channel: ": "Laat alleen laatste video van kanaal zien: ", - "Only show latest unwatched video from channel: ": "Laat alleen de laatste onbekeken video zien van kanaal: ", - "Only show unwatched: ": "Laat alleen onbekeken videos zien: ", - "Only show notifications (if there are any): ": "Laat alleen notificaties zien (als die er zijn): ", - "Data preferences": "Gegevens voorkeuren", - "Clear watch history": "Kijkgeschiedenis wissen", - "Import/Export data": "Importeer/Exporteer gegevens", - "Manage subscriptions": "Abonnees beheren", - "Watch history": "Kijkgeschiedenis", - "Delete account": "Account verwijderen", - "Administrator preferences": "", - "Default homepage: ": "", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "", - "Login enabled? ": "", - "Registration enabled? ": "", - "Report statistics? ": "", - "Save preferences": "Opslaan voorkeuren", - "Subscription manager": "Abonnees beheerder", - "`x` subscriptions": "`x` abonnees", - "Import/Export": "Importeer/Exporteer", - "unsubscribe": "abonnement opzeggen", - "Subscriptions": "Abonnees", - "`x` unseen notifications": "`x` onbekeken notificaties", - "search": "zoeken", - "Sign out": "Afmelden", - "Released under the AGPLv3 by Omar Roth.": "Uitgegeven onder AGPLv3 door Omar Roth.", - "Source available here.": "Bron beschikbaar hier.", - "View JavaScript license information.": "Bekijk JavaScript licentie informatie.", - "View privacy policy.": "", - "Trending": "Trending", - "Unlisted": "", - "Watch video on Youtube": "Bekijk video op Youtube", - "Genre: ": "Genre: ", - "License: ": "Licentie: ", - "Family friendly? ": "Gezinsvriendelijk? ", - "Wilson score: ": "Wilson score: ", - "Engagement: ": "Betrokkenheid: ", - "Whitelisted regions: ": "Toegestane regio's: ", - "Blacklisted regions: ": "Geblokkeerde regio's: ", - "Shared `x`": "`x` gedeeld", - "`x` views": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hoi! Het lijkt erop dat je JavaScript uit hebt staan. Klik hier om de reacties te bekijken, hou er rekening mee dat het wat langer duurt om te laden.", - "View YouTube comments": "Bekijk YouTube reacties", - "View more comments on Reddit": "Bekijk meer reacties op Reddit", - "View `x` comments": "`x` reacties zien", - "View Reddit comments": "Bekijk Reddit reacties", - "Hide replies": "Verberg antwoorden", - "Show replies": "Laat antwoorden zien", - "Incorrect password": "Onjuist wachtwoord", - "Quota exceeded, try again in a few hours": "Quota overschreden, probeer het over een paar uur opnieuw", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Niet in staat om in te loggen, zorg ervoor dat two-factor authentication (Authenticator of SMS) is ingeschakeld.", - "Invalid TFA code": "Onjuiste TFA code", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Aanmelden mislukt. Dit kan zijn omdat two-factor authentication niet is ingeschakeld voor uw account.", - "Invalid answer": "Onjuist antwoord", - "Invalid CAPTCHA": "Onjuiste CAPTCHA", - "CAPTCHA is a required field": "CAPTCHA is een vereist veld", - "User ID is a required field": "Gebruiker ID is een vereist veld", - "Password is a required field": "Wachtwoord is een vereist veld", - "Invalid username or password": "Ongeldige gebruikersnaam of wachtwoord", - "Please sign in using 'Sign in with Google'": "Meld u aan met 'Aanmelden met Google'", - "Password cannot be empty": "Wachtwoord mag niet leeg zijn", - "Password cannot be longer than 55 characters": "Wachtwoord mag niet langer dan 55 tekens zijn", - "Please sign in": "Meld u aan", - "Invidious Private Feed for `x`": "Invidious Privé Feed voor `x`", - "channel:`x`": "kanaal:`x`", - "Deleted or invalid channel": "Verwijderd of ongeldig kanaal", - "This channel does not exist.": "Dit kanaal bestaat niet.", - "Could not get channel info.": "Kan kanaal informatie niet verkrijgen.", - "Could not fetch comments": "Kan reacties niet verkrijgen", - "View `x` replies": "`x` antwoorden zien", - "`x` ago": "`x` geleden", - "Load more": "Meer laden", - "`x` points": "`x` punten", - "Could not create mix.": "Kon mix niet maken.", - "Playlist is empty": "Afspeellijst is leeg", - "Invalid playlist.": "Ongeldige afspeellijst.", - "Playlist does not exist.": "Afspeellijst bestaat niet.", - "Could not pull trending pages.": "Kon trending paginas niet verkrijgen.", - "Hidden field \"challenge\" is a required field": "Verborgen veld \"uitdaging\" is een vereist veld", - "Hidden field \"token\" is a required field": "Verborgen veld \"token\" is een vereist veld", - "Invalid challenge": "Ongeldige uitdaging", - "Invalid token": "Ongeldige token", - "Invalid user": "Ongeldige gebruiker", - "Token is expired, please try again": "Token is verlopen, probeer het opnieuw", - "English": "", - "English (auto-generated)": "", - "Afrikaans": "", - "Albanian": "", - "Amharic": "", - "Arabic": "", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "", - "Bosnian": "", - "Bulgarian": "", - "Burmese": "", - "Catalan": "", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "", - "Danish": "", - "Dutch": "", - "Esperanto": "", - "Estonian": "", - "Filipino": "", - "Finnish": "", - "French": "", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "", - "Icelandic": "", - "Igbo": "", - "Indonesian": "", - "Irish": "", - "Italian": "", - "Japanese": "", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "", - "Slovenian": "", - "Somali": "", - "Southern Sotho": "", - "Spanish": "", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "", - "Ukrainian": "", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "`x` jaar", - "`x` months": "`x` maanden", - "`x` weeks": "`x` weken", - "`x` days": "`x` dagen", - "`x` hours": "`x` uur", - "`x` minutes": "`x` minuten", - "`x` seconds": "`x` seconden", - "Fallback comments: ": "", - "Popular": "", - "Top": "", - "About": "", - "Rating: ": "", - "Language: ": "", - "View as playlist": "", - "Default": "", - "Music": "", - "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", - "%A %B %-d, %Y": "", - "(edited)": "", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "", - "Playlists": "", - "Current version: ": "" + "`x` subscribers": "`x` abonnees", + "`x` videos": "`x` videos", + "LIVE": "LIVE", + "Shared `x` ago": "Gedeeld `x` geleden", + "Unsubscribe": "Abonnement opzeggen", + "Subscribe": "Abonneren", + "View channel on YouTube": "Bekijk kanaal op Youtube", + "newest": "nieuwste", + "oldest": "oudste", + "popular": "populair", + "last": "", + "Next page": "Volgende pagina", + "Previous page": "Vorige pagina", + "Clear watch history?": "Kijk geschiedenis wissen?", + "Yes": "Ja", + "No": "Nee", + "Import and Export Data": "Importeer en Exporteer Gegevens", + "Import": "Importeren", + "Import Invidious data": "Importeer Invidious gegevens", + "Import YouTube subscriptions": "Importeer Youtube abonnees", + "Import FreeTube subscriptions (.db)": "Importeer FreeTube abonnees (.db)", + "Import NewPipe subscriptions (.json)": "Importeer NewPipe abonnees (.json)", + "Import NewPipe data (.zip)": "Importeer NewPipe gegevens (.zip)", + "Export": "Exporteren", + "Export subscriptions as OPML": "Exporteer abonnees als OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporteer abonnees als OPML (voor NewPipe & FreeTube)", + "Export data as JSON": "Exporteer gegevens als JSON", + "Delete account?": "Verwijder account?", + "History": "Geschiedenis", + "An alternative front-end to YouTube": "Een alternatieve front-end voor YouTube", + "JavaScript license information": "JavaScript licentie informatie", + "source": "bron", + "Login": "Inloggen", + "Login/Register": "Inloggen/Registreren", + "Login to Google": "Inloggen op Google", + "User ID": "Gebruiker ID", + "Password": "Wachtwoord", + "Time (h:mm:ss):": "Tijd (h:mm:ss):", + "Text CAPTCHA": "Tekst CAPTCHA", + "Image CAPTCHA": "Afbeelding CAPTCHA", + "Sign In": "Aanmelden", + "Register": "Registreren", + "Email": "Email", + "Google verification code": "Google verificatie code", + "Preferences": "Voorkeuren", + "Player preferences": "Afspeler voorkeuren", + "Always loop: ": "Altijd herhalen: ", + "Autoplay: ": "Automatisch afspelen: ", + "Autoplay next video: ": "Automatisch volgende video afspelen: ", + "Listen by default: ": "Standaard luisteren: ", + "Proxy videos? ": "", + "Default speed: ": "Standaard snelheid: ", + "Preferred video quality: ": "Video kwaliteit voorkeur: ", + "Player volume: ": "Afspeler volume: ", + "Default comments: ": "Standaard reacties: ", + "Default captions: ": "Standaard ondertitels: ", + "Fallback captions: ": "Alternatieve ondertitels: ", + "Show related videos? ": "Laat gerelateerde videos zien? ", + "Visual preferences": "Visuele voorkeuren", + "Dark mode: ": "Donkere modus: ", + "Thin mode: ": "Smalle modus: ", + "Subscription preferences": "Abonnement voorkeuren", + "Redirect homepage to feed: ": "Startpagina omleiden naar feed: ", + "Number of videos shown in feed: ": "Aantal videos te zien in feed: ", + "Sort videos by: ": "Sorteer videos op: ", + "published": "gepubliceerd", + "published - reverse": "gepubliceerd - omgekeerd", + "alphabetically": "alfabetische volgorde", + "alphabetically - reverse": "alfabetisch - omgekeerd", + "channel name": "kanaal naam", + "channel name - reverse": "kanaal naam - omgekeerd", + "Only show latest video from channel: ": "Laat alleen laatste video van kanaal zien: ", + "Only show latest unwatched video from channel: ": "Laat alleen de laatste onbekeken video zien van kanaal: ", + "Only show unwatched: ": "Laat alleen onbekeken videos zien: ", + "Only show notifications (if there are any): ": "Laat alleen notificaties zien (als die er zijn): ", + "Data preferences": "Gegevens voorkeuren", + "Clear watch history": "Kijkgeschiedenis wissen", + "Import/Export data": "Importeer/Exporteer gegevens", + "Manage subscriptions": "Abonnees beheren", + "Watch history": "Kijkgeschiedenis", + "Delete account": "Account verwijderen", + "Administrator preferences": "", + "Default homepage: ": "", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "", + "Login enabled? ": "", + "Registration enabled? ": "", + "Report statistics? ": "", + "Save preferences": "Opslaan voorkeuren", + "Subscription manager": "Abonnees beheerder", + "`x` subscriptions": "`x` abonnees", + "Import/Export": "Importeer/Exporteer", + "unsubscribe": "abonnement opzeggen", + "Subscriptions": "Abonnees", + "`x` unseen notifications": "`x` onbekeken notificaties", + "search": "zoeken", + "Sign out": "Afmelden", + "Released under the AGPLv3 by Omar Roth.": "Uitgegeven onder AGPLv3 door Omar Roth.", + "Source available here.": "Bron beschikbaar hier.", + "View JavaScript license information.": "Bekijk JavaScript licentie informatie.", + "View privacy policy.": "", + "Trending": "Trending", + "Unlisted": "", + "Watch video on Youtube": "Bekijk video op Youtube", + "Genre: ": "Genre: ", + "License: ": "Licentie: ", + "Family friendly? ": "Gezinsvriendelijk? ", + "Wilson score: ": "Wilson score: ", + "Engagement: ": "Betrokkenheid: ", + "Whitelisted regions: ": "Toegestane regio's: ", + "Blacklisted regions: ": "Geblokkeerde regio's: ", + "Shared `x`": "`x` gedeeld", + "`x` views": "", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hoi! Het lijkt erop dat je JavaScript uit hebt staan. Klik hier om de reacties te bekijken, hou er rekening mee dat het wat langer duurt om te laden.", + "View YouTube comments": "Bekijk YouTube reacties", + "View more comments on Reddit": "Bekijk meer reacties op Reddit", + "View `x` comments": "`x` reacties zien", + "View Reddit comments": "Bekijk Reddit reacties", + "Hide replies": "Verberg antwoorden", + "Show replies": "Laat antwoorden zien", + "Incorrect password": "Onjuist wachtwoord", + "Quota exceeded, try again in a few hours": "Quota overschreden, probeer het over een paar uur opnieuw", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Niet in staat om in te loggen, zorg ervoor dat two-factor authentication (Authenticator of SMS) is ingeschakeld.", + "Invalid TFA code": "Onjuiste TFA code", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Aanmelden mislukt. Dit kan zijn omdat two-factor authentication niet is ingeschakeld voor uw account.", + "Invalid answer": "Onjuist antwoord", + "Invalid CAPTCHA": "Onjuiste CAPTCHA", + "CAPTCHA is a required field": "CAPTCHA is een vereist veld", + "User ID is a required field": "Gebruiker ID is een vereist veld", + "Password is a required field": "Wachtwoord is een vereist veld", + "Invalid username or password": "Ongeldige gebruikersnaam of wachtwoord", + "Please sign in using 'Sign in with Google'": "Meld u aan met 'Aanmelden met Google'", + "Password cannot be empty": "Wachtwoord mag niet leeg zijn", + "Password cannot be longer than 55 characters": "Wachtwoord mag niet langer dan 55 tekens zijn", + "Please sign in": "Meld u aan", + "Invidious Private Feed for `x`": "Invidious Privé Feed voor `x`", + "channel:`x`": "kanaal:`x`", + "Deleted or invalid channel": "Verwijderd of ongeldig kanaal", + "This channel does not exist.": "Dit kanaal bestaat niet.", + "Could not get channel info.": "Kan kanaal informatie niet verkrijgen.", + "Could not fetch comments": "Kan reacties niet verkrijgen", + "View `x` replies": "`x` antwoorden zien", + "`x` ago": "`x` geleden", + "Load more": "Meer laden", + "`x` points": "`x` punten", + "Could not create mix.": "Kon mix niet maken.", + "Playlist is empty": "Afspeellijst is leeg", + "Invalid playlist.": "Ongeldige afspeellijst.", + "Playlist does not exist.": "Afspeellijst bestaat niet.", + "Could not pull trending pages.": "Kon trending paginas niet verkrijgen.", + "Hidden field \"challenge\" is a required field": "Verborgen veld \"uitdaging\" is een vereist veld", + "Hidden field \"token\" is a required field": "Verborgen veld \"token\" is een vereist veld", + "Invalid challenge": "Ongeldige uitdaging", + "Invalid token": "Ongeldige token", + "Invalid user": "Ongeldige gebruiker", + "Token is expired, please try again": "Token is verlopen, probeer het opnieuw", + "English": "", + "English (auto-generated)": "", + "Afrikaans": "", + "Albanian": "", + "Amharic": "", + "Arabic": "", + "Armenian": "", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "", + "Bosnian": "", + "Bulgarian": "", + "Burmese": "", + "Catalan": "", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "", + "Danish": "", + "Dutch": "", + "Esperanto": "", + "Estonian": "", + "Filipino": "", + "Finnish": "", + "French": "", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "", + "Icelandic": "", + "Igbo": "", + "Indonesian": "", + "Irish": "", + "Italian": "", + "Japanese": "", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "", + "Slovenian": "", + "Somali": "", + "Southern Sotho": "", + "Spanish": "", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "", + "Ukrainian": "", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "`x` jaar", + "`x` months": "`x` maanden", + "`x` weeks": "`x` weken", + "`x` days": "`x` dagen", + "`x` hours": "`x` uur", + "`x` minutes": "`x` minuten", + "`x` seconds": "`x` seconden", + "Fallback comments: ": "", + "Popular": "", + "Top": "", + "About": "", + "Rating: ": "", + "Language: ": "", + "View as playlist": "", + "Default": "", + "Music": "", + "Gaming": "", + "News": "", + "Movies": "", + "Download": "", + "Download as: ": "", + "%A %B %-d, %Y": "", + "(edited)": "", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "", + "Videos": "", + "Playlists": "", + "Current version: ": "" } diff --git a/locales/pl.json b/locales/pl.json index f4c4f6b2..c39c32b9 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,296 +1,296 @@ { - "`x` subscribers": "`x` subskrybcji", - "`x` videos": "`x` filmów", - "LIVE": "NA ŻYWO", - "Shared `x` ago": "Udostępniono `x` temu", - "Unsubscribe": "Odsubskrybuj", - "Subscribe": "Subskrybuj", - "View channel on YouTube": "Wyświetl kanał na YouTube", - "newest": "najnowsze", - "oldest": "najstarsze", - "popular": "popularne", - "last": "ostatnie", - "Next page": "Następna strona", - "Previous page": "Poprzednia strona", - "Clear watch history?": "Wyczyścić historię?", - "Yes": "Tak", - "No": "Nie", - "Import and Export Data": "Import i eksport danych", - "Import": "Import", - "Import Invidious data": "Importuj dane Invidious", - "Import YouTube subscriptions": "Importuj subskrybcje z YouTube", - "Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)", - "Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)", - "Export": "Eksport", - "Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)", - "Export data as JSON": "Eksportuj dane jako JSON", - "Delete account?": "Usunąć konto?", - "History": "Historia", - "An alternative front-end to YouTube": "Alternatywny front-end dla YouTube", - "JavaScript license information": "Informacja o licencji JavaScript", - "source": "źródło", - "Login": "Zaloguj", - "Login/Register": "Zaloguj/Zarejestruj", - "Login to Google": "Zaloguj do Google", - "User ID": "ID użytkownika", - "Password": "Hasło", - "Time (h:mm:ss):": "Godzina (h:mm:ss):", - "Text CAPTCHA": "Tekst CAPTCHA", - "Image CAPTCHA": "Obraz CAPTCHA", - "Sign In": "Zaloguj się", - "Register": "Zarejestruj się", - "Email": "Email", - "Google verification code": "Kod weryfikacyjny Google", - "Preferences": "Preferencje", - "Player preferences": "Ustawienia odtwarzacza", - "Always loop: ": "Zawsze zapętlaj: ", - "Autoplay: ": "Autoodtwarzanie: ", - "Autoplay next video: ": "Odtwórz następny film: ", - "Listen by default: ": "Tryb dźwiękowy: ", - "Proxy videos? ": "Filmy przez proxy? ", - "Default speed: ": "Domyślna prędkość: ", - "Preferred video quality: ": "Preferowana jakość filmów: ", - "Player volume: ": "Głośność odtwarzacza: ", - "Default comments: ": "Domyślne komentarze: ", - "Default captions: ": "Domyślne napisy: ", - "Fallback captions: ": "Zastępcze napisy: ", - "Show related videos? ": "Pokaż powiązane filmy? ", - "Visual preferences": "Preferencje Wizualne", - "Dark mode: ": "Ciemny motyw: ", - "Thin mode: ": "Tryb minimalny: ", - "Subscription preferences": "Preferencje subskrybcji", - "Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ", - "Number of videos shown in feed: ": "Liczba filmów widoczna na stronie subskrybcji: ", - "Sort videos by: ": "Sortuj filmy: ", - "published": "po czasie publikacji", - "published - reverse": "po czasie publikacji od najstarszych", - "alphabetically": "alfabetycznie", - "alphabetically - reverse": "alfabetycznie od tyłu", - "channel name": "po nazwie kanału", - "channel name - reverse": "po nazwie kanału od tyłu", - "Only show latest video from channel: ": "Pokazuj tylko najnowszy film z kanału: ", - "Only show latest unwatched video from channel: ": "Pokazuj tylko najnowszy nie obejrzany film z kanału: ", - "Only show unwatched: ": "Pokazuj tylko nie obejrzane: ", - "Only show notifications (if there are any): ": "Pokazuj tylko powiadomienia (jeśli są): ", - "Data preferences": "Preferencje danych", - "Clear watch history": "Wyczyść historię", - "Import/Export data": "Import/Eksport danych", - "Manage subscriptions": "Organizuj subskrybcje", - "Watch history": "Historia", - "Delete account": "Usuń konto", - "Administrator preferences": "Preferencje administratora", - "Default homepage: ": "Domyślna strona główna: ", - "Feed menu: ": "", - "Top enabled? ": "", - "CAPTCHA enabled? ": "CAPTCHA aktywna? ", - "Login enabled? ": "Logowanie włączone? ", - "Registration enabled? ": "Rejestracja włączona? ", - "Report statistics? ": "Raportować statystyki? ", - "Save preferences": "Zapisz preferencje", - "Subscription manager": "Manager subskrybcji", - "`x` subscriptions": "`x` subskrybcji", - "Import/Export": "Import/Eksport", - "unsubscribe": "odsubskrybuj", - "Subscriptions": "Subskrybcje", - "`x` unseen notifications": "`x` niewidzianych powiadomień", - "search": "szukaj", - "Sign out": "Wyloguj", - "Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.", - "Source available here.": "Kod źródłowy dostępny tutaj.", - "View JavaScript license information.": "Wyświetl informację o licencji JavaScript.", - "View privacy policy.": "Polityka prywatności.", - "Trending": "Na czasie", - "Unlisted": "", - "Watch video on Youtube": "Zobacz film na YouTube", - "Genre: ": "Gatunek: ", - "License: ": "Licencja: ", - "Family friendly? ": "Przyjazny rodzinie? ", - "Wilson score: ": "Punktacja Wilsona: ", - "Engagement: ": "Zaangażowanie: ", - "Whitelisted regions: ": "Dostępny na obszarach: ", - "Blacklisted regions: ": "Niedostępny na obszarach: ", - "Shared `x`": "Udostępniono `x`", - "`x` views": "", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", - "View YouTube comments": "Wyświetl komentarze z YouTube", - "View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie", - "View `x` comments": "Wyświetl `x` komentarzy", - "View Reddit comments": "Wyświetl komentarze z Redditta", - "Hide replies": "Ukryj odpowiedzi", - "Show replies": "Pokaż odpowiedzi", - "Incorrect password": "Niepoprawne hasło", - "Quota exceeded, try again in a few hours": "Przekroczony limit zapytań, spróbuj ponownie za kilka godzin", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.", - "Invalid TFA code": "Niepoprawny kod TFA", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.", - "Invalid answer": "Niepoprawna odpowiedź", - "Invalid CAPTCHA": "CAPTCHA wykonane błędnie", - "CAPTCHA is a required field": "CAPTCHA jest polem wymaganym", - "User ID is a required field": "ID użytkownika jest polem wymaganym", - "Password is a required field": "Hasło jest polem wymaganym", - "Invalid username or password": "Niepoprawny login lub hasło", - "Please sign in using 'Sign in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"", - "Password cannot be empty": "Hasło nie może być puste", - "Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków", - "Please sign in": "Proszę się zalogować", - "Invidious Private Feed for `x`": "", - "channel:`x`": "kanał:`x", - "Deleted or invalid channel": "Usunięty lub niepoprawny kanał", - "This channel does not exist.": "Ten kanał nie istnieje.", - "Could not get channel info.": "Nie udało się uzyskać informacji o kanale.", - "Could not fetch comments": "Nie udało się pobrać komentarzy", - "View `x` replies": "Wyświetl `x` odpowiedzi", - "`x` ago": "`x` temu", - "Load more": "Wczytaj więcej", - "`x` points": "`x` punktów", - "Could not create mix.": "Nie udało się utworzyć miksu.", - "Playlist is empty": "Lista odtwarzania jest pusta", - "Invalid playlist.": "Niepoprawna lista.", - "Playlist does not exist.": "Lista odtwarzania nie istnieje.", - "Could not pull trending pages.": "Nie udało się pobrać strony na czasie.", - "Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym", - "Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym", - "Invalid challenge": "Niepoprawne wyzwanie", - "Invalid token": "Niepoprawny token", - "Invalid user": "Niepoprawny użytkownik", - "Token is expired, please try again": "Token wygasł, spróbuj ponownie", - "English": "angielski", - "English (auto-generated)": "angielski (automatycznie generowane)", - "Afrikaans": "afrykanerski", - "Albanian": "albański", - "Amharic": "amharski", - "Arabic": "arabski", - "Armenian": "armeński", - "Azerbaijani": "azerski", - "Bangla": "bengalski", - "Basque": "baskijski", - "Belarusian": "białoruski", - "Bosnian": "bośniacki", - "Bulgarian": "bułgarski", - "Burmese": "birmański", - "Catalan": "kataloński", - "Cebuano": "cebuański", - "Chinese (Simplified)": "chiński (uproszczony)", - "Chinese (Traditional)": "chiński (tradycyjny)", - "Corsican": "korsykański", - "Croatian": "chorwacki", - "Czech": "czeski", - "Danish": "duński", - "Dutch": "holenderski", - "Esperanto": "esperanto", - "Estonian": "estoński", - "Filipino": "filipiński", - "Finnish": "fiński", - "French": "francuski", - "Galician": "galicyjski", - "Georgian": "gruziński", - "German": "niemiecki", - "Greek": "grecki", - "Gujarati": "gudźarati", - "Haitian Creole": "kreolski haitański", - "Hausa": "hausa", - "Hawaiian": "hawajski", - "Hebrew": "hebrajski", - "Hindi": "hindi", - "Hmong": "hmong", - "Hungarian": "węgierski", - "Icelandic": "islandzki", - "Igbo": "ibo", - "Indonesian": "indonezyjski", - "Irish": "irlandzki", - "Italian": "włoski", - "Japanese": "japoński", - "Javanese": "jawajski", - "Kannada": "kannada", - "Kazakh": "kazachski", - "Khmer": "khmerski", - "Korean": "koreański", - "Kurdish": "kurdyjski", - "Kyrgyz": "kirgiski", - "Lao": "laotański", - "Latin": "łaciński", - "Latvian": "łotewski", - "Lithuanian": "litewski", - "Luxembourgish": "luksemburski", - "Macedonian": "macedoński", - "Malagasy": "malgaski", - "Malay": "malajski", - "Malayalam": "malajalam", - "Maltese": "maltański", - "Maori": "maoryski", - "Marathi": "marathi", - "Mongolian": "mongolski", - "Nepali": "nepalski", - "Norwegian": "norweski", - "Nyanja": "njandża", - "Pashto": "paszto", - "Persian": "perski", - "Polish": "polski", - "Portuguese": "portugalski", - "Punjabi": "pendżabski", - "Romanian": "rumuński", - "Russian": "rosyjski", - "Samoan": "samoański", - "Scottish Gaelic": "gaelicki szkocki", - "Serbian": "serbski", - "Shona": "shona", - "Sindhi": "sindhi", - "Sinhala": "syngaleski", - "Slovak": "słowacki", - "Slovenian": "słoweński", - "Somali": "somalijski", - "Southern Sotho": "sotho południowy", - "Spanish": "hiszpański", - "Spanish (Latin America)": "hiszpański (ameryka łacińska)", - "Sundanese": "sundajski", - "Swahili": "suahili", - "Swedish": "szwedzki", - "Tajik": "tadżycki", - "Tamil": "tamilski", - "Telugu": "telugu", - "Thai": "tajski", - "Turkish": "turecki", - "Ukrainian": "ukraiński", - "Urdu": "urdu", - "Uzbek": "uzbecki", - "Vietnamese": "wietnamski", - "Welsh": "walijski", - "Western Frisian": "zachodniofryzyjski", - "Xhosa": "xhosa", - "Yiddish": "jidysz", - "Yoruba": "joruba", - "Zulu": "zuluski", - "`x` years": "`x` lat", - "`x` months": "`x` miesięcy", - "`x` weeks": "`x` tygodni", - "`x` days": "`x` dni", - "`x` hours": "`x` godzin", - "`x` minutes": "`x` minut", - "`x` seconds": "`x` sekund", - "Fallback comments: ": "Zastępcze komentarze: ", - "Popular": "Popularne", - "Top": "Najczęściej oglądane", - "About": "Informacje", - "Rating: ": "Ocena: ", - "Language: ": "Język: ", - "View as playlist": "", - "Default": "Domyślnie", - "Music": "Muzyka", - "Gaming": "Gry", - "News": "Wiadomości", - "Movies": "Filmy", - "Download": "Pobierz", - "Download as: ": "Pobierz jako: ", - "%A %B %-d, %Y": "", - "(edited)": "(edytowany)", - "Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube", - "`x` marked it with a ❤": "'x' oznaczonych ❤", - "Audio mode": "Tryb audio", - "Video mode": "Tryb wideo", - "Videos": "Filmy", - "Playlists": "Playlisty", - "Current version: ": "Aktualna wersja: " + "`x` subscribers": "`x` subskrybcji", + "`x` videos": "`x` filmów", + "LIVE": "NA ŻYWO", + "Shared `x` ago": "Udostępniono `x` temu", + "Unsubscribe": "Odsubskrybuj", + "Subscribe": "Subskrybuj", + "View channel on YouTube": "Wyświetl kanał na YouTube", + "newest": "najnowsze", + "oldest": "najstarsze", + "popular": "popularne", + "last": "ostatnie", + "Next page": "Następna strona", + "Previous page": "Poprzednia strona", + "Clear watch history?": "Wyczyścić historię?", + "Yes": "Tak", + "No": "Nie", + "Import and Export Data": "Import i eksport danych", + "Import": "Import", + "Import Invidious data": "Importuj dane Invidious", + "Import YouTube subscriptions": "Importuj subskrybcje z YouTube", + "Import FreeTube subscriptions (.db)": "Importuj subskrybcje z FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Importuj subskrybcje z NewPipe (.json)", + "Import NewPipe data (.zip)": "Importuj dane NewPipe (.zip)", + "Export": "Eksport", + "Export subscriptions as OPML": "Eksportuj subskrybcje jako OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Eksportuj subskrybcje jako OPML (dla NewPipe i FreeTube)", + "Export data as JSON": "Eksportuj dane jako JSON", + "Delete account?": "Usunąć konto?", + "History": "Historia", + "An alternative front-end to YouTube": "Alternatywny front-end dla YouTube", + "JavaScript license information": "Informacja o licencji JavaScript", + "source": "źródło", + "Login": "Zaloguj", + "Login/Register": "Zaloguj/Zarejestruj", + "Login to Google": "Zaloguj do Google", + "User ID": "ID użytkownika", + "Password": "Hasło", + "Time (h:mm:ss):": "Godzina (h:mm:ss):", + "Text CAPTCHA": "Tekst CAPTCHA", + "Image CAPTCHA": "Obraz CAPTCHA", + "Sign In": "Zaloguj się", + "Register": "Zarejestruj się", + "Email": "Email", + "Google verification code": "Kod weryfikacyjny Google", + "Preferences": "Preferencje", + "Player preferences": "Ustawienia odtwarzacza", + "Always loop: ": "Zawsze zapętlaj: ", + "Autoplay: ": "Autoodtwarzanie: ", + "Autoplay next video: ": "Odtwórz następny film: ", + "Listen by default: ": "Tryb dźwiękowy: ", + "Proxy videos? ": "Filmy przez proxy? ", + "Default speed: ": "Domyślna prędkość: ", + "Preferred video quality: ": "Preferowana jakość filmów: ", + "Player volume: ": "Głośność odtwarzacza: ", + "Default comments: ": "Domyślne komentarze: ", + "Default captions: ": "Domyślne napisy: ", + "Fallback captions: ": "Zastępcze napisy: ", + "Show related videos? ": "Pokaż powiązane filmy? ", + "Visual preferences": "Preferencje Wizualne", + "Dark mode: ": "Ciemny motyw: ", + "Thin mode: ": "Tryb minimalny: ", + "Subscription preferences": "Preferencje subskrybcji", + "Redirect homepage to feed: ": "Przekieruj stronę główną do subskrybcji: ", + "Number of videos shown in feed: ": "Liczba filmów widoczna na stronie subskrybcji: ", + "Sort videos by: ": "Sortuj filmy: ", + "published": "po czasie publikacji", + "published - reverse": "po czasie publikacji od najstarszych", + "alphabetically": "alfabetycznie", + "alphabetically - reverse": "alfabetycznie od tyłu", + "channel name": "po nazwie kanału", + "channel name - reverse": "po nazwie kanału od tyłu", + "Only show latest video from channel: ": "Pokazuj tylko najnowszy film z kanału: ", + "Only show latest unwatched video from channel: ": "Pokazuj tylko najnowszy nie obejrzany film z kanału: ", + "Only show unwatched: ": "Pokazuj tylko nie obejrzane: ", + "Only show notifications (if there are any): ": "Pokazuj tylko powiadomienia (jeśli są): ", + "Data preferences": "Preferencje danych", + "Clear watch history": "Wyczyść historię", + "Import/Export data": "Import/Eksport danych", + "Manage subscriptions": "Organizuj subskrybcje", + "Watch history": "Historia", + "Delete account": "Usuń konto", + "Administrator preferences": "Preferencje administratora", + "Default homepage: ": "Domyślna strona główna: ", + "Feed menu: ": "", + "Top enabled? ": "", + "CAPTCHA enabled? ": "CAPTCHA aktywna? ", + "Login enabled? ": "Logowanie włączone? ", + "Registration enabled? ": "Rejestracja włączona? ", + "Report statistics? ": "Raportować statystyki? ", + "Save preferences": "Zapisz preferencje", + "Subscription manager": "Manager subskrybcji", + "`x` subscriptions": "`x` subskrybcji", + "Import/Export": "Import/Eksport", + "unsubscribe": "odsubskrybuj", + "Subscriptions": "Subskrybcje", + "`x` unseen notifications": "`x` niewidzianych powiadomień", + "search": "szukaj", + "Sign out": "Wyloguj", + "Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.", + "Source available here.": "Kod źródłowy dostępny tutaj.", + "View JavaScript license information.": "Wyświetl informację o licencji JavaScript.", + "View privacy policy.": "Polityka prywatności.", + "Trending": "Na czasie", + "Unlisted": "", + "Watch video on Youtube": "Zobacz film na YouTube", + "Genre: ": "Gatunek: ", + "License: ": "Licencja: ", + "Family friendly? ": "Przyjazny rodzinie? ", + "Wilson score: ": "Punktacja Wilsona: ", + "Engagement: ": "Zaangażowanie: ", + "Whitelisted regions: ": "Dostępny na obszarach: ", + "Blacklisted regions: ": "Niedostępny na obszarach: ", + "Shared `x`": "Udostępniono `x`", + "`x` views": "", + "Premieres in `x`": "", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", + "View YouTube comments": "Wyświetl komentarze z YouTube", + "View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie", + "View `x` comments": "Wyświetl `x` komentarzy", + "View Reddit comments": "Wyświetl komentarze z Redditta", + "Hide replies": "Ukryj odpowiedzi", + "Show replies": "Pokaż odpowiedzi", + "Incorrect password": "Niepoprawne hasło", + "Quota exceeded, try again in a few hours": "Przekroczony limit zapytań, spróbuj ponownie za kilka godzin", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.", + "Invalid TFA code": "Niepoprawny kod TFA", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.", + "Invalid answer": "Niepoprawna odpowiedź", + "Invalid CAPTCHA": "CAPTCHA wykonane błędnie", + "CAPTCHA is a required field": "CAPTCHA jest polem wymaganym", + "User ID is a required field": "ID użytkownika jest polem wymaganym", + "Password is a required field": "Hasło jest polem wymaganym", + "Invalid username or password": "Niepoprawny login lub hasło", + "Please sign in using 'Sign in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"", + "Password cannot be empty": "Hasło nie może być puste", + "Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków", + "Please sign in": "Proszę się zalogować", + "Invidious Private Feed for `x`": "", + "channel:`x`": "kanał:`x", + "Deleted or invalid channel": "Usunięty lub niepoprawny kanał", + "This channel does not exist.": "Ten kanał nie istnieje.", + "Could not get channel info.": "Nie udało się uzyskać informacji o kanale.", + "Could not fetch comments": "Nie udało się pobrać komentarzy", + "View `x` replies": "Wyświetl `x` odpowiedzi", + "`x` ago": "`x` temu", + "Load more": "Wczytaj więcej", + "`x` points": "`x` punktów", + "Could not create mix.": "Nie udało się utworzyć miksu.", + "Playlist is empty": "Lista odtwarzania jest pusta", + "Invalid playlist.": "Niepoprawna lista.", + "Playlist does not exist.": "Lista odtwarzania nie istnieje.", + "Could not pull trending pages.": "Nie udało się pobrać strony na czasie.", + "Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym", + "Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym", + "Invalid challenge": "Niepoprawne wyzwanie", + "Invalid token": "Niepoprawny token", + "Invalid user": "Niepoprawny użytkownik", + "Token is expired, please try again": "Token wygasł, spróbuj ponownie", + "English": "angielski", + "English (auto-generated)": "angielski (automatycznie generowane)", + "Afrikaans": "afrykanerski", + "Albanian": "albański", + "Amharic": "amharski", + "Arabic": "arabski", + "Armenian": "armeński", + "Azerbaijani": "azerski", + "Bangla": "bengalski", + "Basque": "baskijski", + "Belarusian": "białoruski", + "Bosnian": "bośniacki", + "Bulgarian": "bułgarski", + "Burmese": "birmański", + "Catalan": "kataloński", + "Cebuano": "cebuański", + "Chinese (Simplified)": "chiński (uproszczony)", + "Chinese (Traditional)": "chiński (tradycyjny)", + "Corsican": "korsykański", + "Croatian": "chorwacki", + "Czech": "czeski", + "Danish": "duński", + "Dutch": "holenderski", + "Esperanto": "esperanto", + "Estonian": "estoński", + "Filipino": "filipiński", + "Finnish": "fiński", + "French": "francuski", + "Galician": "galicyjski", + "Georgian": "gruziński", + "German": "niemiecki", + "Greek": "grecki", + "Gujarati": "gudźarati", + "Haitian Creole": "kreolski haitański", + "Hausa": "hausa", + "Hawaiian": "hawajski", + "Hebrew": "hebrajski", + "Hindi": "hindi", + "Hmong": "hmong", + "Hungarian": "węgierski", + "Icelandic": "islandzki", + "Igbo": "ibo", + "Indonesian": "indonezyjski", + "Irish": "irlandzki", + "Italian": "włoski", + "Japanese": "japoński", + "Javanese": "jawajski", + "Kannada": "kannada", + "Kazakh": "kazachski", + "Khmer": "khmerski", + "Korean": "koreański", + "Kurdish": "kurdyjski", + "Kyrgyz": "kirgiski", + "Lao": "laotański", + "Latin": "łaciński", + "Latvian": "łotewski", + "Lithuanian": "litewski", + "Luxembourgish": "luksemburski", + "Macedonian": "macedoński", + "Malagasy": "malgaski", + "Malay": "malajski", + "Malayalam": "malajalam", + "Maltese": "maltański", + "Maori": "maoryski", + "Marathi": "marathi", + "Mongolian": "mongolski", + "Nepali": "nepalski", + "Norwegian": "norweski", + "Nyanja": "njandża", + "Pashto": "paszto", + "Persian": "perski", + "Polish": "polski", + "Portuguese": "portugalski", + "Punjabi": "pendżabski", + "Romanian": "rumuński", + "Russian": "rosyjski", + "Samoan": "samoański", + "Scottish Gaelic": "gaelicki szkocki", + "Serbian": "serbski", + "Shona": "shona", + "Sindhi": "sindhi", + "Sinhala": "syngaleski", + "Slovak": "słowacki", + "Slovenian": "słoweński", + "Somali": "somalijski", + "Southern Sotho": "sotho południowy", + "Spanish": "hiszpański", + "Spanish (Latin America)": "hiszpański (ameryka łacińska)", + "Sundanese": "sundajski", + "Swahili": "suahili", + "Swedish": "szwedzki", + "Tajik": "tadżycki", + "Tamil": "tamilski", + "Telugu": "telugu", + "Thai": "tajski", + "Turkish": "turecki", + "Ukrainian": "ukraiński", + "Urdu": "urdu", + "Uzbek": "uzbecki", + "Vietnamese": "wietnamski", + "Welsh": "walijski", + "Western Frisian": "zachodniofryzyjski", + "Xhosa": "xhosa", + "Yiddish": "jidysz", + "Yoruba": "joruba", + "Zulu": "zuluski", + "`x` years": "`x` lat", + "`x` months": "`x` miesięcy", + "`x` weeks": "`x` tygodni", + "`x` days": "`x` dni", + "`x` hours": "`x` godzin", + "`x` minutes": "`x` minut", + "`x` seconds": "`x` sekund", + "Fallback comments: ": "Zastępcze komentarze: ", + "Popular": "Popularne", + "Top": "Najczęściej oglądane", + "About": "Informacje", + "Rating: ": "Ocena: ", + "Language: ": "Język: ", + "View as playlist": "", + "Default": "Domyślnie", + "Music": "Muzyka", + "Gaming": "Gry", + "News": "Wiadomości", + "Movies": "Filmy", + "Download": "Pobierz", + "Download as: ": "Pobierz jako: ", + "%A %B %-d, %Y": "", + "(edited)": "(edytowany)", + "Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube", + "`x` marked it with a ❤": "'x' oznaczonych ❤", + "Audio mode": "Tryb audio", + "Video mode": "Tryb wideo", + "Videos": "Filmy", + "Playlists": "Playlisty", + "Current version: ": "Aktualna wersja: " } diff --git a/locales/ru.json b/locales/ru.json index 8dc04c2a..26a452f3 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,298 +1,298 @@ { - "`x` subscribers": "`x` подписчиков", - "`x` videos": "`x` видео", - "LIVE": "ПРЯМОЙ ЭФИР", - "Shared `x` ago": "Опубликовано `x` назад", - "Unsubscribe": "Отписаться", - "Subscribe": "Подписаться", - "View channel on YouTube": "Канал на YouTube", - "newest": "новые", - "oldest": "старые", - "popular": "популярные", - "last": "недавно обновленные", - "Next page": "Следующая страница", - "Previous page": "Предыдущая страница", - "Clear watch history?": "Очистить историю просмотров?", - "Yes": "Да", - "No": "Нет", - "Import and Export Data": "Импорт и экспорт данных", - "Import": "Импорт", - "Import Invidious data": "Импортировать данные Invidious", - "Import YouTube subscriptions": "Импортировать YouTube подписки", - "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", - "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", - "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", - "Export": "Экспорт", - "Export subscriptions as OPML": "Экспортировать подписки в OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", - "Export data as JSON": "Экспортировать данные в JSON", - "Delete account?": "Удалить аккаунт?", - "History": "История", - "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", - "JavaScript license information": "Лицензии JavaScript", - "source": "источник", - "Login": "Войти", - "Login/Register": "Войти/Регистрация", - "Login to Google": "Войти через Google", - "User ID": "ID пользователя", - "Password": "Пароль", - "Time (h:mm:ss):": "Время (ч:мм:сс):", - "Text CAPTCHA": "Текст капчи", - "Image CAPTCHA": "Изображение капчи", - "Sign In": "Войти", - "Register": "Регистрация", - "Email": "Эл. почта", - "Google verification code": "Код подтверждения Google", - "Preferences": "Настройки", - "Player preferences": "Настройки проигрывателя", - "Always loop: ": "Всегда повторять: ", - "Autoplay: ": "Автовоспроизведение: ", - "Autoplay next video: ": "Автовоспроизведение следующего видео: ", - "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", - "Proxy videos? ": "Проксировать видео? ", - "Default speed: ": "Скорость по умолчанию: ", - "Preferred video quality: ": "Предпочтительное качество видео: ", - "Player volume: ": "Громкость воспроизведения: ", - "Default comments: ": "Источник комментариев: ", - "youtube": "YouTube", - "reddit": "Reddit", - "Default captions: ": "Субтитры по умолчанию: ", - "Fallback captions: ": "Резервные субтитры: ", - "Show related videos? ": "Показывать похожие видео? ", - "Visual preferences": "Визуальные настройки", - "Dark mode: ": "Темная тема: ", - "Thin mode: ": "Облегченный режим: ", - "Subscription preferences": "Настройки подписок", - "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", - "Number of videos shown in feed: ": "Число видео в ленте: ", - "Sort videos by: ": "Сортировать видео по: ", - "published": "дате публикации", - "published - reverse": "дате - обратный порядок", - "alphabetically": "алфавиту", - "alphabetically - reverse": "алфавиту - обратный порядок", - "channel name": "имени канала", - "channel name - reverse": "имени канала - обратный порядок", - "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", - "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", - "Only show unwatched: ": "Отображать только непросмотренные видео: ", - "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", - "Data preferences": "Настройки данных", - "Clear watch history": "Очистить историю просмотра", - "Import/Export data": "Импорт/Экспорт данных", - "Manage subscriptions": "Управление подписками", - "Watch history": "История просмотров", - "Delete account": "Удалить аккаунт", - "Administrator preferences": "Настройки администратора", - "Default homepage: ": "Главная страница по умолчанию: ", - "Feed menu: ": "Меню ленты: ", - "Top enabled? ": "Включить топ? ", - "CAPTCHA enabled? ": "Включить капчу? ", - "Login enabled? ": "Включить логин? ", - "Registration enabled? ": "Включить регистрацию? ", - "Report statistics? ": "Отображать статистику? ", - "Save preferences": "Сохранить настройки", - "Subscription manager": "Менеджер подписок", - "`x` subscriptions": "`x` подписок", - "Import/Export": "Импорт/Экспорт", - "unsubscribe": "отписаться", - "Subscriptions": "Подписки", - "`x` unseen notifications": "`x` новых оповещений", - "search": "поиск", - "Sign out": "Выйти", - "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", - "Source available here.": "Исходный код доступен здесь.", - "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", - "View privacy policy.": "См. политику конфиденциальности.", - "Trending": "В тренде", - "Unlisted": "Доступно по ссылке", - "Watch video on Youtube": "Смотреть на YouTube", - "Genre: ": "Жанр: ", - "License: ": "Лицензия: ", - "Family friendly? ": "Семейный просмотр: ", - "Wilson score: ": "Рейтинг Уилсона: ", - "Engagement: ": "Вовлеченность: ", - "Whitelisted regions: ": "Доступно для: ", - "Blacklisted regions: ": "Недоступно для: ", - "Shared `x`": "Опубликовано `x`", - "`x` views": "", - "Premieres in `x`": "Премьера через `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", - "View YouTube comments": "Смотреть комментарии с YouTube", - "View more comments on Reddit": "Больше комментариев на Reddit", - "View `x` comments": "Показать `x` комментариев", - "View Reddit comments": "Смотреть комментарии с Reddit", - "Hide replies": "Скрыть ответы", - "Show replies": "Показать ответы", - "Incorrect password": "Неправильный пароль", - "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", - "Invalid TFA code": "Неправильный TFA код", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", - "Invalid answer": "Неверный ответ", - "Invalid CAPTCHA": "Неверная капча", - "CAPTCHA is a required field": "Необходимо ввести капчу", - "User ID is a required field": "Необходимо ввести идентификатор пользователя", - "Password is a required field": "Необходимо ввести пароль", - "Invalid username or password": "Недопустимый пароль или имя пользователя", - "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", - "Password cannot be empty": "Пароль не может быть пустым", - "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", - "Please sign in": "Пожалуйста, войдите", - "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", - "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удален или не найден", - "This channel does not exist.": "Такой канал не существует.", - "Could not get channel info.": "Невозможно получить информацию о канале.", - "Could not fetch comments": "Невозможно получить комментарии", - "View `x` replies": "Показать `x` ответов", - "`x` ago": "`x` назад", - "Load more": "Загрузить больше", - "`x` points": "`x` очков", - "Could not create mix.": "Невозможно создать \"микс\".", - "Playlist is empty": "Плейлист пуст", - "Invalid playlist.": "Некорректный плейлист.", - "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", - "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", - "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", - "Invalid challenge": "Неправильный ответ в \"challenge\"", - "Invalid token": "Неправильный токен", - "Invalid user": "Недопустимое имя пользователя", - "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", - "English": "Английский", - "English (auto-generated)": "Английский (созданы автоматически)", - "Afrikaans": "Африкаанс", - "Albanian": "Албанский", - "Amharic": "Амхарский", - "Arabic": "Арабский", - "Armenian": "Армянский", - "Azerbaijani": "Азербайджанский", - "Bangla": "Бенгальский", - "Basque": "Баскский", - "Belarusian": "Белорусский", - "Bosnian": "Боснийский", - "Bulgarian": "Болгарский", - "Burmese": "Бирманский", - "Catalan": "Каталонский", - "Cebuano": "Себуанский", - "Chinese (Simplified)": "Китайский (упрощенный)", - "Chinese (Traditional)": "Китайский (традиционный)", - "Corsican": "Корсиканский", - "Croatian": "Хорватский", - "Czech": "Чешский", - "Danish": "Датский", - "Dutch": "Нидерландский", - "Esperanto": "Эсперанто", - "Estonian": "Эстонский", - "Filipino": "Филиппинский", - "Finnish": "Финский", - "French": "Французский", - "Galician": "Галисийский", - "Georgian": "Грузинский", - "German": "Немецкий", - "Greek": "Греческий", - "Gujarati": "Гуджаратский", - "Haitian Creole": "Гаит. креольский", - "Hausa": "Хауса", - "Hawaiian": "Гавайский", - "Hebrew": "Иврит", - "Hindi": "Хинди", - "Hmong": "Хмонг (мяо)", - "Hungarian": "Венгерский", - "Icelandic": "Исландский", - "Igbo": "Игбо", - "Indonesian": "Индонезийский", - "Irish": "Ирландский", - "Italian": "Итальянский", - "Japanese": "Японский", - "Javanese": "Яванский", - "Kannada": "Каннада", - "Kazakh": "Казахский", - "Khmer": "Кхмерский", - "Korean": "Корейский", - "Kurdish": "Курдский", - "Kyrgyz": "Киргизский", - "Lao": "Лаосский", - "Latin": "Латинский", - "Latvian": "Латышский", - "Lithuanian": "Литовский", - "Luxembourgish": "Люксембургский", - "Macedonian": "Македонский", - "Malagasy": "Малагасийский", - "Malay": "Малайский", - "Malayalam": "Малаялам", - "Maltese": "Мальтийский", - "Maori": "Маори", - "Marathi": "Маратхи", - "Mongolian": "Монгольская", - "Nepali": "Непальский", - "Norwegian": "Норвежский", - "Nyanja": "Ньянджа", - "Pashto": "Пушту", - "Persian": "Персидский", - "Polish": "Польский", - "Portuguese": "Португальский", - "Punjabi": "Панджаби", - "Romanian": "Румынский", - "Russian": "Русский", - "Samoan": "Самоанский", - "Scottish Gaelic": "Шотландский (гэльский)", - "Serbian": "Сербский", - "Shona": "Шона", - "Sindhi": "Синдхи", - "Sinhala": "Сингальский", - "Slovak": "Словацкий", - "Slovenian": "Словенский", - "Somali": "Сомалийский", - "Southern Sotho": "Сесото (южный сото)", - "Spanish": "Испанский", - "Spanish (Latin America)": "Испанский (Латинская Америка)", - "Sundanese": "Сунданский", - "Swahili": "Суахили", - "Swedish": "Шведский", - "Tajik": "Таджикский", - "Tamil": "Тамильский", - "Telugu": "Телугу", - "Thai": "Тайский", - "Turkish": "Турецкий", - "Ukrainian": "Украинский", - "Urdu": "Урду", - "Uzbek": "Узбекский", - "Vietnamese": "Вьетнамский", - "Welsh": "Валлийский", - "Western Frisian": "Западнофризский", - "Xhosa": "Коса", - "Yiddish": "Идиш", - "Yoruba": "Йоруба", - "Zulu": "Зулусский", - "`x` years": "`x` лет", - "`x` months": "`x` месяцев", - "`x` weeks": "`x` недель", - "`x` days": "`x` дней", - "`x` hours": "`x` часов", - "`x` minutes": "`x` минут", - "`x` seconds": "`x` секунд", - "Fallback comments: ": "Резервные комментарии: ", - "Popular": "Популярное", - "Top": "Топ", - "About": "О сайте", - "Rating: ": "Рейтинг: ", - "Language: ": "Язык: ", - "View as playlist": "", - "Default": "По-умолчанию", - "Music": "Музыка", - "Gaming": "Игры", - "News": "Новости", - "Movies": "Фильмы", - "Download": "Скачать", - "Download as: ": "Скачать как: ", - "%A %B %-d, %Y": "%-d %B %Y, %A", - "(edited)": "(изменено)", - "Youtube permalink of the comment": "Прямая ссылка на YouTube", - "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", - "Audio mode": "Аудио режим", - "Video mode": "Видео режим", - "Videos": "Видео", - "Playlists": "Плейлисты", - "Current version: ": "Текущая версия: " + "`x` subscribers": "`x` подписчиков", + "`x` videos": "`x` видео", + "LIVE": "ПРЯМОЙ ЭФИР", + "Shared `x` ago": "Опубликовано `x` назад", + "Unsubscribe": "Отписаться", + "Subscribe": "Подписаться", + "View channel on YouTube": "Канал на YouTube", + "newest": "новые", + "oldest": "старые", + "popular": "популярные", + "last": "недавно обновленные", + "Next page": "Следующая страница", + "Previous page": "Предыдущая страница", + "Clear watch history?": "Очистить историю просмотров?", + "Yes": "Да", + "No": "Нет", + "Import and Export Data": "Импорт и экспорт данных", + "Import": "Импорт", + "Import Invidious data": "Импортировать данные Invidious", + "Import YouTube subscriptions": "Импортировать YouTube подписки", + "Import FreeTube subscriptions (.db)": "Импортировать FreeTube подписки (.db)", + "Import NewPipe subscriptions (.json)": "Импортировать NewPipe подписки (.json)", + "Import NewPipe data (.zip)": "Импортировать данные NewPipe (.zip)", + "Export": "Экспорт", + "Export subscriptions as OPML": "Экспортировать подписки в OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в OPML (для NewPipe и FreeTube)", + "Export data as JSON": "Экспортировать данные в JSON", + "Delete account?": "Удалить аккаунт?", + "History": "История", + "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", + "JavaScript license information": "Лицензии JavaScript", + "source": "источник", + "Login": "Войти", + "Login/Register": "Войти/Регистрация", + "Login to Google": "Войти через Google", + "User ID": "ID пользователя", + "Password": "Пароль", + "Time (h:mm:ss):": "Время (ч:мм:сс):", + "Text CAPTCHA": "Текст капчи", + "Image CAPTCHA": "Изображение капчи", + "Sign In": "Войти", + "Register": "Регистрация", + "Email": "Эл. почта", + "Google verification code": "Код подтверждения Google", + "Preferences": "Настройки", + "Player preferences": "Настройки проигрывателя", + "Always loop: ": "Всегда повторять: ", + "Autoplay: ": "Автовоспроизведение: ", + "Autoplay next video: ": "Автовоспроизведение следующего видео: ", + "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", + "Proxy videos? ": "Проксировать видео? ", + "Default speed: ": "Скорость по умолчанию: ", + "Preferred video quality: ": "Предпочтительное качество видео: ", + "Player volume: ": "Громкость воспроизведения: ", + "Default comments: ": "Источник комментариев: ", + "youtube": "YouTube", + "reddit": "Reddit", + "Default captions: ": "Субтитры по умолчанию: ", + "Fallback captions: ": "Резервные субтитры: ", + "Show related videos? ": "Показывать похожие видео? ", + "Visual preferences": "Визуальные настройки", + "Dark mode: ": "Темная тема: ", + "Thin mode: ": "Облегченный режим: ", + "Subscription preferences": "Настройки подписок", + "Redirect homepage to feed: ": "Отображать ленту вместо главной страницы: ", + "Number of videos shown in feed: ": "Число видео в ленте: ", + "Sort videos by: ": "Сортировать видео по: ", + "published": "дате публикации", + "published - reverse": "дате - обратный порядок", + "alphabetically": "алфавиту", + "alphabetically - reverse": "алфавиту - обратный порядок", + "channel name": "имени канала", + "channel name - reverse": "имени канала - обратный порядок", + "Only show latest video from channel: ": "Отображать только последние видео с каждого канала: ", + "Only show latest unwatched video from channel: ": "Отображать только непросмотренные видео с каждого канала: ", + "Only show unwatched: ": "Отображать только непросмотренные видео: ", + "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", + "Data preferences": "Настройки данных", + "Clear watch history": "Очистить историю просмотра", + "Import/Export data": "Импорт/Экспорт данных", + "Manage subscriptions": "Управление подписками", + "Watch history": "История просмотров", + "Delete account": "Удалить аккаунт", + "Administrator preferences": "Настройки администратора", + "Default homepage: ": "Главная страница по умолчанию: ", + "Feed menu: ": "Меню ленты: ", + "Top enabled? ": "Включить топ? ", + "CAPTCHA enabled? ": "Включить капчу? ", + "Login enabled? ": "Включить логин? ", + "Registration enabled? ": "Включить регистрацию? ", + "Report statistics? ": "Отображать статистику? ", + "Save preferences": "Сохранить настройки", + "Subscription manager": "Менеджер подписок", + "`x` subscriptions": "`x` подписок", + "Import/Export": "Импорт/Экспорт", + "unsubscribe": "отписаться", + "Subscriptions": "Подписки", + "`x` unseen notifications": "`x` новых оповещений", + "search": "поиск", + "Sign out": "Выйти", + "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", + "Source available here.": "Исходный код доступен здесь.", + "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", + "View privacy policy.": "См. политику конфиденциальности.", + "Trending": "В тренде", + "Unlisted": "Доступно по ссылке", + "Watch video on Youtube": "Смотреть на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Лицензия: ", + "Family friendly? ": "Семейный просмотр: ", + "Wilson score: ": "Рейтинг Уилсона: ", + "Engagement: ": "Вовлеченность: ", + "Whitelisted regions: ": "Доступно для: ", + "Blacklisted regions: ": "Недоступно для: ", + "Shared `x`": "Опубликовано `x`", + "`x` views": "", + "Premieres in `x`": "Премьера через `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", + "View YouTube comments": "Смотреть комментарии с YouTube", + "View more comments on Reddit": "Больше комментариев на Reddit", + "View `x` comments": "Показать `x` комментариев", + "View Reddit comments": "Смотреть комментарии с Reddit", + "Hide replies": "Скрыть ответы", + "Show replies": "Показать ответы", + "Incorrect password": "Неправильный пароль", + "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", + "Invalid TFA code": "Неправильный TFA код", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", + "Invalid answer": "Неверный ответ", + "Invalid CAPTCHA": "Неверная капча", + "CAPTCHA is a required field": "Необходимо ввести капчу", + "User ID is a required field": "Необходимо ввести идентификатор пользователя", + "Password is a required field": "Необходимо ввести пароль", + "Invalid username or password": "Недопустимый пароль или имя пользователя", + "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", + "Password cannot be empty": "Пароль не может быть пустым", + "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", + "Please sign in": "Пожалуйста, войдите", + "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал удален или не найден", + "This channel does not exist.": "Такой канал не существует.", + "Could not get channel info.": "Невозможно получить информацию о канале.", + "Could not fetch comments": "Невозможно получить комментарии", + "View `x` replies": "Показать `x` ответов", + "`x` ago": "`x` назад", + "Load more": "Загрузить больше", + "`x` points": "`x` очков", + "Could not create mix.": "Невозможно создать \"микс\".", + "Playlist is empty": "Плейлист пуст", + "Invalid playlist.": "Некорректный плейлист.", + "Playlist does not exist.": "Плейлист не существует.", + "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", + "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", + "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", + "Invalid challenge": "Неправильный ответ в \"challenge\"", + "Invalid token": "Неправильный токен", + "Invalid user": "Недопустимое имя пользователя", + "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", + "English": "Английский", + "English (auto-generated)": "Английский (созданы автоматически)", + "Afrikaans": "Африкаанс", + "Albanian": "Албанский", + "Amharic": "Амхарский", + "Arabic": "Арабский", + "Armenian": "Армянский", + "Azerbaijani": "Азербайджанский", + "Bangla": "Бенгальский", + "Basque": "Баскский", + "Belarusian": "Белорусский", + "Bosnian": "Боснийский", + "Bulgarian": "Болгарский", + "Burmese": "Бирманский", + "Catalan": "Каталонский", + "Cebuano": "Себуанский", + "Chinese (Simplified)": "Китайский (упрощенный)", + "Chinese (Traditional)": "Китайский (традиционный)", + "Corsican": "Корсиканский", + "Croatian": "Хорватский", + "Czech": "Чешский", + "Danish": "Датский", + "Dutch": "Нидерландский", + "Esperanto": "Эсперанто", + "Estonian": "Эстонский", + "Filipino": "Филиппинский", + "Finnish": "Финский", + "French": "Французский", + "Galician": "Галисийский", + "Georgian": "Грузинский", + "German": "Немецкий", + "Greek": "Греческий", + "Gujarati": "Гуджаратский", + "Haitian Creole": "Гаит. креольский", + "Hausa": "Хауса", + "Hawaiian": "Гавайский", + "Hebrew": "Иврит", + "Hindi": "Хинди", + "Hmong": "Хмонг (мяо)", + "Hungarian": "Венгерский", + "Icelandic": "Исландский", + "Igbo": "Игбо", + "Indonesian": "Индонезийский", + "Irish": "Ирландский", + "Italian": "Итальянский", + "Japanese": "Японский", + "Javanese": "Яванский", + "Kannada": "Каннада", + "Kazakh": "Казахский", + "Khmer": "Кхмерский", + "Korean": "Корейский", + "Kurdish": "Курдский", + "Kyrgyz": "Киргизский", + "Lao": "Лаосский", + "Latin": "Латинский", + "Latvian": "Латышский", + "Lithuanian": "Литовский", + "Luxembourgish": "Люксембургский", + "Macedonian": "Македонский", + "Malagasy": "Малагасийский", + "Malay": "Малайский", + "Malayalam": "Малаялам", + "Maltese": "Мальтийский", + "Maori": "Маори", + "Marathi": "Маратхи", + "Mongolian": "Монгольская", + "Nepali": "Непальский", + "Norwegian": "Норвежский", + "Nyanja": "Ньянджа", + "Pashto": "Пушту", + "Persian": "Персидский", + "Polish": "Польский", + "Portuguese": "Португальский", + "Punjabi": "Панджаби", + "Romanian": "Румынский", + "Russian": "Русский", + "Samoan": "Самоанский", + "Scottish Gaelic": "Шотландский (гэльский)", + "Serbian": "Сербский", + "Shona": "Шона", + "Sindhi": "Синдхи", + "Sinhala": "Сингальский", + "Slovak": "Словацкий", + "Slovenian": "Словенский", + "Somali": "Сомалийский", + "Southern Sotho": "Сесото (южный сото)", + "Spanish": "Испанский", + "Spanish (Latin America)": "Испанский (Латинская Америка)", + "Sundanese": "Сунданский", + "Swahili": "Суахили", + "Swedish": "Шведский", + "Tajik": "Таджикский", + "Tamil": "Тамильский", + "Telugu": "Телугу", + "Thai": "Тайский", + "Turkish": "Турецкий", + "Ukrainian": "Украинский", + "Urdu": "Урду", + "Uzbek": "Узбекский", + "Vietnamese": "Вьетнамский", + "Welsh": "Валлийский", + "Western Frisian": "Западнофризский", + "Xhosa": "Коса", + "Yiddish": "Идиш", + "Yoruba": "Йоруба", + "Zulu": "Зулусский", + "`x` years": "`x` лет", + "`x` months": "`x` месяцев", + "`x` weeks": "`x` недель", + "`x` days": "`x` дней", + "`x` hours": "`x` часов", + "`x` minutes": "`x` минут", + "`x` seconds": "`x` секунд", + "Fallback comments: ": "Резервные комментарии: ", + "Popular": "Популярное", + "Top": "Топ", + "About": "О сайте", + "Rating: ": "Рейтинг: ", + "Language: ": "Язык: ", + "View as playlist": "", + "Default": "По-умолчанию", + "Music": "Музыка", + "Gaming": "Игры", + "News": "Новости", + "Movies": "Фильмы", + "Download": "Скачать", + "Download as: ": "Скачать как: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "(edited)": "(изменено)", + "Youtube permalink of the comment": "Прямая ссылка на YouTube", + "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", + "Audio mode": "Аудио режим", + "Video mode": "Видео режим", + "Videos": "Видео", + "Playlists": "Плейлисты", + "Current version: ": "Текущая версия: " } diff --git a/locales/uk.json b/locales/uk.json index 22c0fb86..33236183 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -1,295 +1,295 @@ { - "`x` subscribers": "`x` підписник / підписників / підписника", - "`x` videos": "`x` відео", - "LIVE": "ПРЯМИЙ ЕФІР", - "Shared `x` ago": "Розміщено `x` назад", - "Unsubscribe": "Відписатися", - "Subscribe": "Підписатися", - "Login to subscribe to `x`": "Увійдіть, щоб підписатися на `x`", - "View channel on YouTube": "Подивитися канал на YouTube", - "newest": "найновіше", - "oldest": "найстаріше", - "popular": "популярне", - "last": "останнє", - "Next page": "Наступна сторінка", - "Previous page": "Попередня сторінка", - "Clear watch history?": "Очистити історію переглядів?", - "Yes": "Так", - "No": "Ні", - "Import and Export Data": "Імпорт і експорт даних", - "Import": "Імпорт", - "Import Invidious data": "Імпортувати дані Invidious", - "Import YouTube subscriptions": "Імпортувати підписки з YouTube", - "Import FreeTube subscriptions (.db)": "Імпортувати підписки з FreeTube (.db)", - "Import NewPipe subscriptions (.json)": "Імпортувати підписки з NewPipe (.json)", - "Import NewPipe data (.zip)": "Імпортувати дані з NewPipe (.zip)", - "Export": "Експорт", - "Export subscriptions as OPML": "Експортувати підписки у форматі OPML", - "Export subscriptions as OPML (for NewPipe & FreeTube)": "Експортувати підписки у форматі OPML (для NewPipe та FreeTube)", - "Export data as JSON": "Експортувати дані у форматі JSON", - "Delete account?": "Видалити обліківку?", - "History": "Історія", - "An alternative front-end to YouTube": "Альтернативний фронтенд до YouTube", - "JavaScript license information": "Інформація щодо ліцензій JavaScript", - "source": "джерело", - "Login": "Увійти", - "Login/Register": "Увійти або зареєструватися", - "Login to Google": "Увійти через Google", - "User ID:": "ID користувача:", - "Password:": "Пароль:", - "Time (h:mm:ss):": "Час (г:мм:сс):", - "Text CAPTCHA": "Текст капчі", - "Image CAPTCHA": "Зображення капчі", - "Sign In": "Увійти", - "Register": "Зареєструватися", - "Email:": "Електронна пошта:", - "Google verification code:": "Код підтвердження Google:", - "Preferences": "Налаштування", - "Player preferences": "Налаштування програвача", - "Always loop: ": "Завжди повторювати: ", - "Autoplay: ": "Автовідтворення: ", - "Autoplay next video: ": "Автовідтворення наступного відео: ", - "Listen by default: ": "Режим «тільки звук» як усталений: ", - "Proxy videos? ": "Програвати відео через проксі? ", - "Default speed: ": "Усталена швидкість відео: ", - "Preferred video quality: ": "Пріорітетна якість відео: ", - "Player volume: ": "Гучність відео: ", - "Default comments: ": "Джерело коментарів: ", - "Default captions: ": "Основна мова субтитрів: ", - "Fallback captions: ": "Запасна мова субтитрів: ", - "Show related videos? ": "Показувати схожі відео? ", - "Visual preferences": "Налаштування сайту", - "Dark mode: ": "Темне оформлення: ", - "Thin mode: ": "Полегшене оформлення: ", - "Subscription preferences": "Налаштування підписок", - "Redirect homepage to feed: ": "Показувати відео з каналів, на які підписані, як головну сторінку: ", - "Number of videos shown in feed: ": "Кількість відео з каналів, на які підписані, у потоці: ", - "Sort videos by: ": "Сортувати відео: ", - "published": "за датою розміщення", - "published - reverse": "за датою розміщення в зворотному порядку", - "alphabetically": "за абеткою", - "alphabetically - reverse": "за абеткою в зворотному порядку", - "channel name": "за назвою каналу", - "channel name - reverse": "за назвою каналу в зворотному порядку", - "Only show latest video from channel: ": "Показувати тільки останнє відео з каналів: ", - "Only show latest unwatched video from channel: ": "Показувати тільки непереглянуті відео з каналів: ", - "Only show unwatched: ": "Показувати тільки непереглянуті відео: ", - "Only show notifications (if there are any): ": "Показувати лише сповіщення, якщо вони є: ", - "Data preferences": "Налаштування даних", - "Clear watch history": "Очистити історію переглядів", - "Import/Export data": "Імпорт і експорт даних", - "Manage subscriptions": "Керування підписками", - "Watch history": "Історія переглядів", - "Delete account": "Видалити обліківку", - "Administrator preferences": "Адміністраторські налаштування", - "Default homepage: ": "Усталена домашня сторінка: ", - "Feed menu: ": "Меню потоку з відео: ", - "Top enabled? ": "Увімкнути топ відео? ", - "CAPTCHA enabled? ": "Увімкнути капчу? ", - "Login enabled? ": "Увімкнути авторизацію? ", - "Registration enabled? ": "Увімкнути реєстрацію? ", - "Report statistics? ": "Повідомляти статистику? ", - "Save preferences": "Зберегти налаштування", - "Subscription manager": "Менеджер підписок", - "`x` subscriptions": "`x` підписка / підписок / підписки", - "Import/Export": "Імпорт і експорт", - "unsubscribe": "відписатися", - "Subscriptions": "Підписки", - "`x` unseen notifications": "`x` непереглянуте сповіщення / непереглянутих сповіщень / непереглянутих сповіщення", - "search": "пошук", - "Sign out": "Вийти", - "Released under the AGPLv3 by Omar Roth.": "Реалізовано Омаром Ротом за ліцензією AGPLv3.", - "Source available here.": "Програмний код доступний тут.", - "View JavaScript license information.": "Переглянути інформацію щодо ліцензії JavaScript.", - "View privacy policy.": "Переглянути політику приватності.", - "Trending": "У тренді", - "Unlisted": "", - "Watch video on Youtube": "Дивитися відео на YouTube", - "Genre: ": "Жанр: ", - "License: ": "Ліцензія: ", - "Family friendly? ": "Перегляд із родиною? ", - "Wilson score: ": "Рейтинг Вілсона: ", - "Engagement: ": "Залученість: ", - "Whitelisted regions: ": "Доступно у регіонах: ", - "Blacklisted regions: ": "Недоступно у регіонах: ", - "Shared `x`": "Розміщено `x`", - "Premieres in `x`": "Прем’єра через `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Схоже, у вас відключений JavaScript. Щоб побачити коментарі, натисніть сюда, але майте на увазі, що вони можуть завантажуватися трохи довше.", - "View YouTube comments": "Переглянути коментарі з YouTube", - "View more comments on Reddit": "Переглянути більше коментарів на Reddit", - "View `x` comments": "Переглянути `x` коментар / коментарів / коментаря", - "View Reddit comments": "Переглянути коментарі з Reddit", - "Hide replies": "Сховати відповіді", - "Show replies": "Показати відповіді", - "Incorrect password": "Неправильний пароль", - "Quota exceeded, try again in a few hours": "Ліміт перевищено, спробуйте знову за декілька годин", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Не вдається увійти. Перевірте, чи не ввімкнена двофакторна аутентифікація (за кодом чи смс).", - "Invalid TFA code": "Неправильний код двофакторної аутентифікації", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не вдається увійти. Це може бути через те, що у вашій обліківці не ввімкнена двофакторна аутентифікація.", - "Invalid answer": "Неправильна відповідь", - "Invalid CAPTCHA": "Неправильна капча", - "CAPTCHA is a required field": "Необхідно пройти капчу", - "User ID is a required field": "Необхідно ввести ID користувача", - "Password is a required field": "Необхідно ввести пароль", - "Invalid username or password": "Неправильний логін чи пароль", - "Please sign in using 'Sign in with Google'": "Будь ласка, натисніть «Увійдіть через Google»", - "Password cannot be empty": "Пароль не може бути порожнім", - "Password cannot be longer than 55 characters": "Пароль не може бути довшим за 55 знаків", - "Please sign in": "Будь ласка, увійдіть", - "Invidious Private Feed for `x`": "Приватний поток відео Invidious для `x`", - "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал видалено або не знайдено", - "This channel does not exist.": "Такого каналу не існує.", - "Could not get channel info.": "Не вдається отримати інформацію щодо цього каналу.", - "Could not fetch comments": "Не вдається завантажити коментарі", - "View `x` replies": "Переглянути `x` відповідь / відповідей / відповіді", - "`x` ago": "`x` тому", - "Load more": "Завантажити більше", - "`x` points": "`x` очко / очок / очка", - "Could not create mix.": "Не вдається створити мікс.", - "Playlist is empty": "Плейлист порожній", - "Invalid playlist.": "Недійсний плейлист.", - "Playlist does not exist.": "Плейлист не існує.", - "Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "", - "Token is expired, please try again": "", - "English": "", - "English (auto-generated)": "", - "Afrikaans": "", - "Albanian": "", - "Amharic": "", - "Arabic": "", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "", - "Bosnian": "", - "Bulgarian": "", - "Burmese": "", - "Catalan": "", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "", - "Danish": "", - "Dutch": "", - "Esperanto": "", - "Estonian": "", - "Filipino": "", - "Finnish": "", - "French": "", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "", - "Icelandic": "", - "Igbo": "", - "Indonesian": "", - "Irish": "", - "Italian": "", - "Japanese": "", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "", - "Slovenian": "", - "Somali": "", - "Southern Sotho": "", - "Spanish": "", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "", - "Ukrainian": "", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "", - "`x` months": "", - "`x` weeks": "", - "`x` days": "", - "`x` hours": "", - "`x` minutes": "", - "`x` seconds": "", - "Fallback comments: ": "", - "Popular": "", - "Top": "", - "About": "", - "Rating: ": "", - "Language: ": "", - "Default": "", - "Music": "", - "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", - "%A %B %-d, %Y": "", - "(edited)": "", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "", - "Playlists": "", - "Current version: ": "" + "`x` subscribers": "`x` підписник / підписників / підписника", + "`x` videos": "`x` відео", + "LIVE": "ПРЯМИЙ ЕФІР", + "Shared `x` ago": "Розміщено `x` назад", + "Unsubscribe": "Відписатися", + "Subscribe": "Підписатися", + "Login to subscribe to `x`": "Увійдіть, щоб підписатися на `x`", + "View channel on YouTube": "Подивитися канал на YouTube", + "newest": "найновіше", + "oldest": "найстаріше", + "popular": "популярне", + "last": "останнє", + "Next page": "Наступна сторінка", + "Previous page": "Попередня сторінка", + "Clear watch history?": "Очистити історію переглядів?", + "Yes": "Так", + "No": "Ні", + "Import and Export Data": "Імпорт і експорт даних", + "Import": "Імпорт", + "Import Invidious data": "Імпортувати дані Invidious", + "Import YouTube subscriptions": "Імпортувати підписки з YouTube", + "Import FreeTube subscriptions (.db)": "Імпортувати підписки з FreeTube (.db)", + "Import NewPipe subscriptions (.json)": "Імпортувати підписки з NewPipe (.json)", + "Import NewPipe data (.zip)": "Імпортувати дані з NewPipe (.zip)", + "Export": "Експорт", + "Export subscriptions as OPML": "Експортувати підписки у форматі OPML", + "Export subscriptions as OPML (for NewPipe & FreeTube)": "Експортувати підписки у форматі OPML (для NewPipe та FreeTube)", + "Export data as JSON": "Експортувати дані у форматі JSON", + "Delete account?": "Видалити обліківку?", + "History": "Історія", + "An alternative front-end to YouTube": "Альтернативний фронтенд до YouTube", + "JavaScript license information": "Інформація щодо ліцензій JavaScript", + "source": "джерело", + "Login": "Увійти", + "Login/Register": "Увійти або зареєструватися", + "Login to Google": "Увійти через Google", + "User ID:": "ID користувача:", + "Password:": "Пароль:", + "Time (h:mm:ss):": "Час (г:мм:сс):", + "Text CAPTCHA": "Текст капчі", + "Image CAPTCHA": "Зображення капчі", + "Sign In": "Увійти", + "Register": "Зареєструватися", + "Email:": "Електронна пошта:", + "Google verification code:": "Код підтвердження Google:", + "Preferences": "Налаштування", + "Player preferences": "Налаштування програвача", + "Always loop: ": "Завжди повторювати: ", + "Autoplay: ": "Автовідтворення: ", + "Autoplay next video: ": "Автовідтворення наступного відео: ", + "Listen by default: ": "Режим «тільки звук» як усталений: ", + "Proxy videos? ": "Програвати відео через проксі? ", + "Default speed: ": "Усталена швидкість відео: ", + "Preferred video quality: ": "Пріорітетна якість відео: ", + "Player volume: ": "Гучність відео: ", + "Default comments: ": "Джерело коментарів: ", + "Default captions: ": "Основна мова субтитрів: ", + "Fallback captions: ": "Запасна мова субтитрів: ", + "Show related videos? ": "Показувати схожі відео? ", + "Visual preferences": "Налаштування сайту", + "Dark mode: ": "Темне оформлення: ", + "Thin mode: ": "Полегшене оформлення: ", + "Subscription preferences": "Налаштування підписок", + "Redirect homepage to feed: ": "Показувати відео з каналів, на які підписані, як головну сторінку: ", + "Number of videos shown in feed: ": "Кількість відео з каналів, на які підписані, у потоці: ", + "Sort videos by: ": "Сортувати відео: ", + "published": "за датою розміщення", + "published - reverse": "за датою розміщення в зворотному порядку", + "alphabetically": "за абеткою", + "alphabetically - reverse": "за абеткою в зворотному порядку", + "channel name": "за назвою каналу", + "channel name - reverse": "за назвою каналу в зворотному порядку", + "Only show latest video from channel: ": "Показувати тільки останнє відео з каналів: ", + "Only show latest unwatched video from channel: ": "Показувати тільки непереглянуті відео з каналів: ", + "Only show unwatched: ": "Показувати тільки непереглянуті відео: ", + "Only show notifications (if there are any): ": "Показувати лише сповіщення, якщо вони є: ", + "Data preferences": "Налаштування даних", + "Clear watch history": "Очистити історію переглядів", + "Import/Export data": "Імпорт і експорт даних", + "Manage subscriptions": "Керування підписками", + "Watch history": "Історія переглядів", + "Delete account": "Видалити обліківку", + "Administrator preferences": "Адміністраторські налаштування", + "Default homepage: ": "Усталена домашня сторінка: ", + "Feed menu: ": "Меню потоку з відео: ", + "Top enabled? ": "Увімкнути топ відео? ", + "CAPTCHA enabled? ": "Увімкнути капчу? ", + "Login enabled? ": "Увімкнути авторизацію? ", + "Registration enabled? ": "Увімкнути реєстрацію? ", + "Report statistics? ": "Повідомляти статистику? ", + "Save preferences": "Зберегти налаштування", + "Subscription manager": "Менеджер підписок", + "`x` subscriptions": "`x` підписка / підписок / підписки", + "Import/Export": "Імпорт і експорт", + "unsubscribe": "відписатися", + "Subscriptions": "Підписки", + "`x` unseen notifications": "`x` непереглянуте сповіщення / непереглянутих сповіщень / непереглянутих сповіщення", + "search": "пошук", + "Sign out": "Вийти", + "Released under the AGPLv3 by Omar Roth.": "Реалізовано Омаром Ротом за ліцензією AGPLv3.", + "Source available here.": "Програмний код доступний тут.", + "View JavaScript license information.": "Переглянути інформацію щодо ліцензії JavaScript.", + "View privacy policy.": "Переглянути політику приватності.", + "Trending": "У тренді", + "Unlisted": "", + "Watch video on Youtube": "Дивитися відео на YouTube", + "Genre: ": "Жанр: ", + "License: ": "Ліцензія: ", + "Family friendly? ": "Перегляд із родиною? ", + "Wilson score: ": "Рейтинг Вілсона: ", + "Engagement: ": "Залученість: ", + "Whitelisted regions: ": "Доступно у регіонах: ", + "Blacklisted regions: ": "Недоступно у регіонах: ", + "Shared `x`": "Розміщено `x`", + "Premieres in `x`": "Прем’єра через `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Схоже, у вас відключений JavaScript. Щоб побачити коментарі, натисніть сюда, але майте на увазі, що вони можуть завантажуватися трохи довше.", + "View YouTube comments": "Переглянути коментарі з YouTube", + "View more comments on Reddit": "Переглянути більше коментарів на Reddit", + "View `x` comments": "Переглянути `x` коментар / коментарів / коментаря", + "View Reddit comments": "Переглянути коментарі з Reddit", + "Hide replies": "Сховати відповіді", + "Show replies": "Показати відповіді", + "Incorrect password": "Неправильний пароль", + "Quota exceeded, try again in a few hours": "Ліміт перевищено, спробуйте знову за декілька годин", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Не вдається увійти. Перевірте, чи не ввімкнена двофакторна аутентифікація (за кодом чи смс).", + "Invalid TFA code": "Неправильний код двофакторної аутентифікації", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не вдається увійти. Це може бути через те, що у вашій обліківці не ввімкнена двофакторна аутентифікація.", + "Invalid answer": "Неправильна відповідь", + "Invalid CAPTCHA": "Неправильна капча", + "CAPTCHA is a required field": "Необхідно пройти капчу", + "User ID is a required field": "Необхідно ввести ID користувача", + "Password is a required field": "Необхідно ввести пароль", + "Invalid username or password": "Неправильний логін чи пароль", + "Please sign in using 'Sign in with Google'": "Будь ласка, натисніть «Увійдіть через Google»", + "Password cannot be empty": "Пароль не може бути порожнім", + "Password cannot be longer than 55 characters": "Пароль не може бути довшим за 55 знаків", + "Please sign in": "Будь ласка, увійдіть", + "Invidious Private Feed for `x`": "Приватний поток відео Invidious для `x`", + "channel:`x`": "канал: `x`", + "Deleted or invalid channel": "Канал видалено або не знайдено", + "This channel does not exist.": "Такого каналу не існує.", + "Could not get channel info.": "Не вдається отримати інформацію щодо цього каналу.", + "Could not fetch comments": "Не вдається завантажити коментарі", + "View `x` replies": "Переглянути `x` відповідь / відповідей / відповіді", + "`x` ago": "`x` тому", + "Load more": "Завантажити більше", + "`x` points": "`x` очко / очок / очка", + "Could not create mix.": "Не вдається створити мікс.", + "Playlist is empty": "Плейлист порожній", + "Invalid playlist.": "Недійсний плейлист.", + "Playlist does not exist.": "Плейлист не існує.", + "Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».", + "Hidden field \"challenge\" is a required field": "", + "Hidden field \"token\" is a required field": "", + "Invalid challenge": "", + "Invalid token": "", + "Invalid user": "", + "Token is expired, please try again": "", + "English": "", + "English (auto-generated)": "", + "Afrikaans": "", + "Albanian": "", + "Amharic": "", + "Arabic": "", + "Armenian": "", + "Azerbaijani": "", + "Bangla": "", + "Basque": "", + "Belarusian": "", + "Bosnian": "", + "Bulgarian": "", + "Burmese": "", + "Catalan": "", + "Cebuano": "", + "Chinese (Simplified)": "", + "Chinese (Traditional)": "", + "Corsican": "", + "Croatian": "", + "Czech": "", + "Danish": "", + "Dutch": "", + "Esperanto": "", + "Estonian": "", + "Filipino": "", + "Finnish": "", + "French": "", + "Galician": "", + "Georgian": "", + "German": "", + "Greek": "", + "Gujarati": "", + "Haitian Creole": "", + "Hausa": "", + "Hawaiian": "", + "Hebrew": "", + "Hindi": "", + "Hmong": "", + "Hungarian": "", + "Icelandic": "", + "Igbo": "", + "Indonesian": "", + "Irish": "", + "Italian": "", + "Japanese": "", + "Javanese": "", + "Kannada": "", + "Kazakh": "", + "Khmer": "", + "Korean": "", + "Kurdish": "", + "Kyrgyz": "", + "Lao": "", + "Latin": "", + "Latvian": "", + "Lithuanian": "", + "Luxembourgish": "", + "Macedonian": "", + "Malagasy": "", + "Malay": "", + "Malayalam": "", + "Maltese": "", + "Maori": "", + "Marathi": "", + "Mongolian": "", + "Nepali": "", + "Norwegian": "", + "Nyanja": "", + "Pashto": "", + "Persian": "", + "Polish": "", + "Portuguese": "", + "Punjabi": "", + "Romanian": "", + "Russian": "", + "Samoan": "", + "Scottish Gaelic": "", + "Serbian": "", + "Shona": "", + "Sindhi": "", + "Sinhala": "", + "Slovak": "", + "Slovenian": "", + "Somali": "", + "Southern Sotho": "", + "Spanish": "", + "Spanish (Latin America)": "", + "Sundanese": "", + "Swahili": "", + "Swedish": "", + "Tajik": "", + "Tamil": "", + "Telugu": "", + "Thai": "", + "Turkish": "", + "Ukrainian": "", + "Urdu": "", + "Uzbek": "", + "Vietnamese": "", + "Welsh": "", + "Western Frisian": "", + "Xhosa": "", + "Yiddish": "", + "Yoruba": "", + "Zulu": "", + "`x` years": "", + "`x` months": "", + "`x` weeks": "", + "`x` days": "", + "`x` hours": "", + "`x` minutes": "", + "`x` seconds": "", + "Fallback comments: ": "", + "Popular": "", + "Top": "", + "About": "", + "Rating: ": "", + "Language: ": "", + "Default": "", + "Music": "", + "Gaming": "", + "News": "", + "Movies": "", + "Download": "", + "Download as: ": "", + "%A %B %-d, %Y": "", + "(edited)": "", + "Youtube permalink of the comment": "", + "`x` marked it with a ❤": "", + "Audio mode": "", + "Video mode": "", + "Videos": "", + "Playlists": "", + "Current version: ": "" } From 2deb436ccdf5b0bdd86bac251d3ec3dab2664983 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 15 Apr 2019 10:45:00 -0500 Subject: [PATCH 062/210] Update placeholder text in new locales --- locales/eo.json | 8 ++++---- locales/eu.json | 4 ++-- locales/uk.json | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 0e122a13..b0e9a028 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -35,15 +35,15 @@ "Login": "Ensaluti", "Login/Register": "Ensaluti/Registriĝi", "Login to Google": "Ensaluti al Google", - "User ID:": "Uzula identigilo:", - "Password:": "Pasvorto:", + "User ID": "Uzula identigilo", + "Password": "Pasvorto", "Time (h:mm:ss):": "Horo (h:mm:ss):", "Text CAPTCHA": "Teksta CAPTCHA", "Image CAPTCHA": "Bilda CAPTCHA", "Sign In": "Ensaluti", "Register": "Registriĝi", - "Email:": "Retpoŝto:", - "Google verification code:": "Kontrolkodo de Google:", + "Email": "Retpoŝto", + "Google verification code": "Kontrolkodo de Google", "Preferences": "Agordoj", "Player preferences": "Spektilaj agordoj", "Always loop: ": "Ĉiam ripeti: ", diff --git a/locales/eu.json b/locales/eu.json index 39d5a223..667c6df3 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -41,8 +41,8 @@ "Image CAPTCHA": "Irudi CAPTCHA", "Sign In": "", "Register": "", - "Email:": "", - "Google verification code:": "", + "Email": "", + "Google verification code": "", "Preferences": "", "Player preferences": "", "Always loop: ": "", diff --git a/locales/uk.json b/locales/uk.json index 33236183..421a7ae9 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -35,15 +35,15 @@ "Login": "Увійти", "Login/Register": "Увійти або зареєструватися", "Login to Google": "Увійти через Google", - "User ID:": "ID користувача:", - "Password:": "Пароль:", + "User ID": "ID користувача", + "Password": "Пароль", "Time (h:mm:ss):": "Час (г:мм:сс):", "Text CAPTCHA": "Текст капчі", "Image CAPTCHA": "Зображення капчі", "Sign In": "Увійти", "Register": "Зареєструватися", - "Email:": "Електронна пошта:", - "Google verification code:": "Код підтвердження Google:", + "Email": "Електронна пошта", + "Google verification code": "Код підтвердження Google", "Preferences": "Налаштування", "Player preferences": "Налаштування програвача", "Always loop: ": "Завжди повторювати: ", From 3bcb98e644bfeb0ae4127a4749051b2f05fdba8b Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 15 Apr 2019 11:13:09 -0500 Subject: [PATCH 063/210] Add config option to cache annotations from IA --- README.md | 26 ++++++------ config/sql/annotations.sql | 12 ++++++ docker/entrypoint.postgres.sh | 1 + src/invidious.cr | 70 ++++++++++++++++++-------------- src/invidious/helpers/helpers.cr | 36 +++++++++++++++- 5 files changed, 101 insertions(+), 44 deletions(-) create mode 100644 config/sql/annotations.sql diff --git a/README.md b/README.md index 70f2c500..e9dfb5c9 100644 --- a/README.md +++ b/README.md @@ -103,12 +103,13 @@ $ sudo systemctl start postgresql $ sudo -i -u postgres $ psql -c "CREATE USER kemal WITH PASSWORD 'kemal';" # Change 'kemal' here to a stronger password, and update `password` in config/config.yml $ createdb -O kemal invidious -$ psql invidious < /home/invidious/invidious/config/sql/channels.sql -$ psql invidious < /home/invidious/invidious/config/sql/videos.sql -$ psql invidious < /home/invidious/invidious/config/sql/channel_videos.sql -$ psql invidious < /home/invidious/invidious/config/sql/users.sql -$ psql invidious < /home/invidious/invidious/config/sql/session_ids.sql -$ psql invidious < /home/invidious/invidious/config/sql/nonces.sql +$ psql invidious kemal < /home/invidious/invidious/config/sql/channels.sql +$ psql invidious kemal < /home/invidious/invidious/config/sql/videos.sql +$ psql invidious kemal < /home/invidious/invidious/config/sql/channel_videos.sql +$ psql invidious kemal < /home/invidious/invidious/config/sql/users.sql +$ psql invidious kemal < /home/invidious/invidious/config/sql/session_ids.sql +$ psql invidious kemal < /home/invidious/invidious/config/sql/nonces.sql +$ psql invidious kemal < /home/invidious/invidious/config/sql/annotations.sql $ exit ``` @@ -145,12 +146,13 @@ $ cd invidious $ brew services start postgresql $ psql -c "CREATE ROLE kemal WITH PASSWORD 'kemal';" # Change 'kemal' here to a stronger password, and update `password` in config/config.yml $ createdb -O kemal invidious -$ psql invidious < config/sql/channels.sql -$ psql invidious < config/sql/videos.sql -$ psql invidious < config/sql/channel_videos.sql -$ psql invidious < config/sql/users.sql -$ psql invidious < config/sql/session_ids.sql -$ psql invidious < config/sql/nonces.sql +$ psql invidious kemal < config/sql/channels.sql +$ psql invidious kemal < config/sql/videos.sql +$ psql invidious kemal < config/sql/channel_videos.sql +$ psql invidious kemal < config/sql/users.sql +$ psql invidious kemal < config/sql/session_ids.sql +$ psql invidious kemal < config/sql/nonces.sql +$ psql invidious kemal < config/sql/annotations.sql # Setup Invidious $ shards update && shards install diff --git a/config/sql/annotations.sql b/config/sql/annotations.sql new file mode 100644 index 00000000..4ea077e7 --- /dev/null +++ b/config/sql/annotations.sql @@ -0,0 +1,12 @@ +-- Table: public.annotations + +-- DROP TABLE public.annotations; + +CREATE TABLE public.annotations +( + id text NOT NULL, + annotations xml, + CONSTRAINT annotations_id_key UNIQUE (id) +); + +GRANT ALL ON TABLE public.annotations TO kemal; diff --git a/docker/entrypoint.postgres.sh b/docker/entrypoint.postgres.sh index 64bcfe48..8bc0e602 100755 --- a/docker/entrypoint.postgres.sh +++ b/docker/entrypoint.postgres.sh @@ -18,6 +18,7 @@ if [ ! -f /var/lib/postgresql/data/setupFinished ]; then su postgres -c 'psql invidious kemal < config/sql/users.sql' su postgres -c 'psql invidious kemal < config/sql/session_ids.sql' su postgres -c 'psql invidious kemal < config/sql/nonces.sql' + su postgres -c 'psql invidious kemal < config/sql/annotations.sql' touch /var/lib/postgresql/data/setupFinished echo "### invidious database setup finished" exit diff --git a/src/invidious.cr b/src/invidious.cr index f0d7fbf1..b0b512fb 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -105,13 +105,17 @@ end Kemal::CLI.new ARGV +# Check table integrity if CONFIG.check_tables - # Check table integrity analyze_table(PG_DB, logger, "channel_videos", ChannelVideo) analyze_table(PG_DB, logger, "nonces", Nonce) analyze_table(PG_DB, logger, "session_ids", SessionId) analyze_table(PG_DB, logger, "users", User) analyze_table(PG_DB, logger, "videos", Video) + + if CONFIG.cache_annotations + analyze_table(PG_DB, logger, "annotations", Annotation) + end end # Start jobs @@ -2938,37 +2942,43 @@ get "/api/v1/annotations/:id" do |env| case source when "archive" - index = CHARS_SAFE.index(id[0]).not_nil!.to_s.rjust(2, '0') + if CONFIG.cache_annotations && (cached_annotation = PG_DB.query_one?("SELECT * FROM annotations WHERE id = $1", id, as: Annotation)) + annotations = cached_annotation.annotations + else + index = CHARS_SAFE.index(id[0]).not_nil!.to_s.rjust(2, '0') - # IA doesn't handle leading hyphens, - # so we use https://archive.org/details/youtubeannotations_64 - if index == "62" - index = "64" - id = id.sub(/^-/, 'A') + # IA doesn't handle leading hyphens, + # so we use https://archive.org/details/youtubeannotations_64 + if index == "62" + index = "64" + id = id.sub(/^-/, 'A') + end + + file = URI.escape("#{id[0, 3]}/#{id}.xml") + + client = make_client(ARCHIVE_URL) + location = client.get("/download/youtubeannotations_#{index}/#{id[0, 2]}.tar/#{file}") + + if !location.headers["Location"]? + env.response.status_code = location.status_code + end + + response = HTTP::Client.get(URI.parse(location.headers["Location"])) + + if response.body.empty? + env.response.status_code = 404 + next + end + + if response.status_code != 200 + env.response.status_code = response.status_code + next + end + + annotations = response.body + + cache_annotation(PG_DB, id, annotations) end - - file = URI.escape("#{id[0, 3]}/#{id}.xml") - - client = make_client(ARCHIVE_URL) - location = client.get("/download/youtubeannotations_#{index}/#{id[0, 2]}.tar/#{file}") - - if !location.headers["Location"]? - env.response.status_code = location.status_code - end - - response = HTTP::Client.get(URI.parse(location.headers["Location"])) - - if response.body.empty? - env.response.status_code = 404 - next - end - - if response.status_code != 200 - env.response.status_code = response.status_code - next - end - - annotations = response.body when "youtube" client = make_client(YT_URL) diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index a2ea9f7a..ac16fc0e 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -15,6 +15,13 @@ struct SessionId }) end +struct Annotation + db_mapping({ + id: String, + annotations: String, + }) +end + struct ConfigPreferences module StringToArray def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder) @@ -114,8 +121,9 @@ user: String, default: Preferences.new(*ConfigPreferences.from_yaml("").to_tuple), converter: ConfigPreferencesConverter, }, - dmca_content: {type: Array(String), default: [] of String}, # For compliance with DMCA, disables download widget using list of video IDs - check_tables: {type: Bool, default: false}, # Check table integrity, automatically try to add any missing columns, create tables, etc. + dmca_content: {type: Array(String), default: [] of String}, # For compliance with DMCA, disables download widget using list of video IDs + check_tables: {type: Bool, default: false}, # Check table integrity, automatically try to add any missing columns, create tables, etc. + cache_annotations: {type: Bool, default: false}, # Cache annotations requested from IA, will not cache empty annotations or annotations that only contain cards }) end @@ -590,3 +598,27 @@ def get_column_array(db, table_name) return column_array end + +def cache_annotation(db, id, annotations) + if !CONFIG.cache_annotations + return + end + + body = XML.parse(annotations) + nodeset = body.xpath_nodes(%q(/document/annotations/annotation)) + + if nodeset == 0 + return + end + + has_legacy_annotations = false + nodeset.each do |node| + if !{"branding", "card", "drawer"}.includes? node["type"]? + has_legacy_annotations = true + break + end + end + + # TODO: Update on conflict? + db.exec("INSERT INTO annotations VALUES ($1, $2) ON CONFLICT DO NOTHING", id, annotations) +end From 698dfca3199396f59fcd881745f87e79d46f7b58 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 15 Apr 2019 11:17:23 -0500 Subject: [PATCH 064/210] Add migrate script for annotations.sql --- config/migrate-scripts/migrate-db-3bcb98e.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100755 config/migrate-scripts/migrate-db-3bcb98e.sh diff --git a/config/migrate-scripts/migrate-db-3bcb98e.sh b/config/migrate-scripts/migrate-db-3bcb98e.sh new file mode 100755 index 00000000..cb9fa6ab --- /dev/null +++ b/config/migrate-scripts/migrate-db-3bcb98e.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +psql invidious kemal < config/sql/annotations.sql From 26168a9520c8bc2a03f631eb0e53c3ee086d8c98 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 15 Apr 2019 23:23:40 -0500 Subject: [PATCH 065/210] Refactor CSRF tokens (using format in #473) --- src/invidious.cr | 169 +++++------ src/invidious/users.cr | 77 +++-- src/invidious/views/clear_watch_history.ecr | 3 +- src/invidious/views/components/item.ecr | 24 +- .../views/components/subscribe_widget.ecr | 18 +- .../components/subscribe_widget_script.ecr | 10 +- src/invidious/views/delete_account.ecr | 3 +- src/invidious/views/history.ecr | 27 +- src/invidious/views/login.ecr | 6 +- src/invidious/views/subscription_manager.ecr | 21 +- src/invidious/views/subscriptions.ecr | 9 +- src/invidious/views/template.ecr | 263 +++++++++--------- 12 files changed, 323 insertions(+), 307 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index b0b512fb..8ed4253f 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -195,39 +195,33 @@ before_all do |env| end if env.request.cookies.has_key? "SID" - headers = HTTP::Headers.new - headers["Cookie"] = env.request.headers["Cookie"] - sid = env.request.cookies["SID"].value # Invidious users only have SID if !env.request.cookies.has_key? "SSID" - email = PG_DB.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String) - - if email + if email = PG_DB.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String) user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User) - challenge, token = create_response(user.email, "sign_out", HMAC_KEY, PG_DB, 1.week) - - env.set "challenge", challenge - env.set "token", token + token = create_response(sid, {"signout", "watch_ajax", "subscription_ajax"}, HMAC_KEY, PG_DB, 1.week) preferences = user.preferences - env.set "user", user env.set "sid", sid + env.set "token", token + env.set "user", user end else + headers = HTTP::Headers.new + headers["Cookie"] = env.request.headers["Cookie"] + begin user, sid = get_user(sid, headers, PG_DB, false) - - challenge, token = create_response(user.email, "sign_out", HMAC_KEY, PG_DB, 1.week) - env.set "challenge", challenge - env.set "token", token + token = create_response(sid, {"signout", "watch_ajax", "subscription_ajax"}, HMAC_KEY, PG_DB, 1.week) preferences = user.preferences - env.set "user", user env.set "sid", sid + env.set "token", token + env.set "user", user rescue ex end end @@ -826,7 +820,7 @@ post "/login" do |env| when "google" tfa_code = env.params.body["tfa"]?.try &.lchop("G-") - # See https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/youtube.py#L79 + # See https://github.com/ytdl-org/youtube-dl/blob/2019.04.07/youtube_dl/extractor/youtube.py#L82 begin client = make_client(LOGIN_URL) headers = HTTP::Headers.new @@ -1091,8 +1085,7 @@ post "/login" do |env| next templated "login" end - challenges = env.params.body.select { |k, v| k.match(/^challenge\[\d+\]$/) } - tokens = env.params.body.select { |k, v| k.match(/^token\[\d+\]$/) } + tokens = env.params.body.select { |k, v| k.match(/^token\[\d+\]$/) }.map { |k, v| v } answer ||= "" captcha_type ||= "image" @@ -1102,11 +1095,8 @@ post "/login" do |env| answer = answer.lstrip('0') answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer) - challenge = env.params.body["challenge[0]"]? - token = env.params.body["token[0]"]? - begin - validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale) + validate_response(tokens[0], answer, env.request.path, HMAC_KEY, PG_DB, locale) rescue ex error_message = ex.message next templated "error" @@ -1117,11 +1107,9 @@ post "/login" do |env| found_valid_captcha = false error_message = translate(locale, "Invalid CAPTCHA") - challenges.each_with_index do |challenge, i| + tokens.each_with_index do |token, i| begin - challenge = challenge[1] - token = tokens[i][1] - validate_response(challenge, token, answer, "sign_in", HMAC_KEY, PG_DB, locale) + validate_response(token, answer, env.request.path, HMAC_KEY, PG_DB, locale) found_valid_captcha = true rescue ex error_message = ex.message @@ -1191,27 +1179,25 @@ post "/login" do |env| end end -get "/signout" do |env| +post "/signout" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env) if user user = user.as(User) - - challenge = env.params.query["challenge"]? - token = env.params.query["token"]? + sid = sid.as(String) + token = env.params.body["token"]? begin - validate_response(challenge, token, user.email, "sign_out", HMAC_KEY, PG_DB, locale) + validate_response(token, sid, env.request.path, HMAC_KEY, PG_DB, locale) rescue ex error_message = ex.message next templated "error" end - user = env.get("user").as(User) - sid = env.get("sid").as(String) PG_DB.exec("DELETE FROM session_ids * WHERE id = $1", sid) env.request.cookies.each do |cookie| @@ -1426,55 +1412,62 @@ get "/toggle_theme" do |env| env.redirect referer end -get "/mark_watched" do |env| +post "/watch_ajax" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env, "/feed/subscriptions") + redirect = env.params.query["redirect"]? + redirect ||= "true" + redirect = redirect == "true" + + if !user + next env.redirect referer + end + + user = user.as(User) + sid = sid.as(String) + token = env.params.body["token"]? + id = env.params.query["id"]? if !id env.response.status_code = 400 next end - redirect = env.params.query["redirect"]? - redirect ||= "false" - redirect = redirect == "true" + user = user.as(User) + sid = sid.as(String) + token = env.params.body["token"]? - if user - user = user.as(User) - if !user.watched.includes? id - PG_DB.exec("UPDATE users SET watched = watched || $1 WHERE email = $2", [id], user.email) + begin + validate_response(token, sid, env.request.path, HMAC_KEY, PG_DB, locale) + rescue ex + if redirect + error_message = ex.message + next templated "error" + else + error_message = {"error" => ex.message}.to_json + env.response.status_code = 500 + next error_message end end - if redirect - env.redirect referer + if env.params.query["action_mark_watched"]? + action = "action_mark_watched" + elsif env.params.query["action_mark_unwatched"]? + action = "action_mark_unwatched" else - env.response.content_type = "application/json" - "{}" - end -end - -get "/mark_unwatched" do |env| - locale = LOCALES[env.get("preferences").as(Preferences).locale]? - - user = env.get? "user" - referer = get_referer(env, "/feed/history") - - id = env.params.query["id"]? - if !id - env.response.status_code = 400 - next + next env.redirect referer end - redirect = env.params.query["redirect"]? - redirect ||= "false" - redirect = redirect == "true" - - if user - user = user.as(User) + case action + when "action_mark_watched" + if !user.watched.includes? id + PG_DB.exec("UPDATE users SET watched = watched || $1 WHERE email = $2", [id], user.email) + end + when "action_mark_unwatched" PG_DB.exec("UPDATE users SET watched = array_remove(watched, $1) WHERE email = $2", id, user.email) end @@ -1561,8 +1554,7 @@ get "/modify_notifications" do |env| end end -# TODO: Add CSRF -get "/subscription_ajax" do |env| +post "/subscription_ajax" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" @@ -1570,14 +1562,29 @@ get "/subscription_ajax" do |env| referer = get_referer(env, "/") redirect = env.params.query["redirect"]? - redirect ||= "false" + redirect ||= "true" redirect = redirect == "true" - if !user && !sid + if !user next env.redirect referer end user = user.as(User) + sid = sid.as(String) + token = env.params.body["token"]? + + begin + validate_response(token, sid, env.request.path, HMAC_KEY, PG_DB, locale) + rescue ex + if redirect + error_message = ex.message + next templated "error" + else + error_message = {"error" => ex.message}.to_json + env.response.status_code = 500 + next error_message + end + end if env.params.query["action_create_subscription_to_channel"]? action = "action_create_subscription_to_channel" @@ -1653,7 +1660,7 @@ get "/subscription_manager" do |env| user = env.get? "user" sid = env.get? "sid" - referer = get_referer(env, "/") + referer = get_referer(env, "/subscription_manager") if !user && !sid next env.redirect referer @@ -1843,12 +1850,13 @@ get "/delete_account" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env) if user user = user.as(User) - - challenge, token = create_response(user.email, "delete_account", HMAC_KEY, PG_DB) + sid = sid.as(String) + token = create_response(sid, {"delete_account"}, HMAC_KEY, PG_DB) templated "delete_account" else @@ -1860,16 +1868,16 @@ post "/delete_account" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env) if user user = user.as(User) - - challenge = env.params.body["challenge"]? + sid = sid.as(String) token = env.params.body["token"]? begin - validate_response(challenge, token, user.email, "delete_account", HMAC_KEY, PG_DB, locale) + validate_response(token, sid, env.request.path, HMAC_KEY, PG_DB, locale) rescue ex error_message = ex.message next templated "error" @@ -1893,12 +1901,13 @@ get "/clear_watch_history" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env) if user user = user.as(User) - - challenge, token = create_response(user.email, "clear_watch_history", HMAC_KEY, PG_DB) + sid = sid.as(String) + token = create_response(sid, {"clear_watch_history"}, HMAC_KEY, PG_DB) templated "clear_watch_history" else @@ -1910,16 +1919,16 @@ post "/clear_watch_history" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env) if user user = user.as(User) - - challenge = env.params.body["challenge"]? + sid = sid.as(String) token = env.params.body["token"]? begin - validate_response(challenge, token, user.email, "clear_watch_history", HMAC_KEY, PG_DB, locale) + validate_response(token, sid, env.request.path, HMAC_KEY, PG_DB, locale) rescue ex error_message = ex.message next templated "error" diff --git a/src/invidious/users.cr b/src/invidious/users.cr index f34d14ab..bad2b5c3 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -197,84 +197,79 @@ def create_user(sid, email, password) return user, sid end -def create_response(user_id, operation, key, db, expire = 6.hours) +def create_response(session, scopes, key, db, expire = 6.hours, use_nonce = false) expire = Time.now + expire - nonce = Random::Secure.hex(16) - db.exec("INSERT INTO nonces VALUES ($1, $2) ON CONFLICT DO NOTHING", nonce, expire) - challenge = "#{expire.to_unix}-#{nonce}-#{user_id}-#{operation}" - token = OpenSSL::HMAC.digest(:sha256, key, challenge) + token = { + "session" => session, + "expire" => expire.to_unix, + "scopes" => scopes, + } - challenge = Base64.urlsafe_encode(challenge) - token = Base64.urlsafe_encode(token) + if use_nonce + nonce = Random::Secure.hex(16) + db.exec("INSERT INTO nonces VALUES ($1, $2) ON CONFLICT DO NOTHING", nonce, expire) + token["nonce"] = nonce + end - return challenge, token + token["signature"] = sign_token(key, token) + + return token.to_json end def sign_token(key, hash) string_to_sign = [] of String + hash.each do |key, value| if key == "signature" next end + if value.is_a?(JSON::Any) + case value + when .as_a? + value = value.as_a.map { |item| item.as_s } + end + end + case value when Array string_to_sign << "#{key}=#{value.sort.join(",")}" + when Tuple + string_to_sign << "#{key}=#{value.to_a.sort.join(",")}" else string_to_sign << "#{key}=#{value}" end end string_to_sign = string_to_sign.sort.join("\n") - return Base64.encode(OpenSSL::HMAC.digest(:sha256, key, string_to_sign)).strip + return Base64.urlsafe_encode(OpenSSL::HMAC.digest(:sha256, key, string_to_sign)).strip end -def validate_response(challenge, token, user_id, operation, key, db, locale) - if !challenge - raise translate(locale, "Hidden field \"challenge\" is a required field") - end - +def validate_response(token, session, scope, key, db, locale) if !token raise translate(locale, "Hidden field \"token\" is a required field") end - challenge = Base64.decode_string(challenge) - if challenge.split("-").size == 4 - expire, nonce, challenge_user_id, challenge_operation = challenge.split("-") + token = JSON.parse(URI.unescape(token)).as_h - expire = expire.to_i? - expire ||= 0 - else - raise translate(locale, "Invalid challenge") + if token["signature"]? != sign_token(key, token) + raise translate(locale, "Invalid token") end - challenge = OpenSSL::HMAC.digest(:sha256, key, challenge) - challenge = Base64.urlsafe_encode(challenge) - - if nonce = db.query_one?("SELECT * FROM nonces WHERE nonce = $1", nonce, as: {String, Time}) + if token["nonce"]? && (nonce = db.query_one?("SELECT * FROM nonces WHERE nonce = $1", token["nonce"], as: {String, Time})) if nonce[1] > Time.now db.exec("UPDATE nonces SET expire = $1 WHERE nonce = $2", Time.new(1990, 1, 1), nonce[0]) else raise translate(locale, "Invalid token") end - else + end + + if !token["scopes"].as_a.includes? scope.strip("/") raise translate(locale, "Invalid token") end - if challenge != token - raise translate(locale, "Invalid token") - end - - if challenge_operation != operation - raise translate(locale, "Invalid token") - end - - if challenge_user_id != user_id - raise translate(locale, "Invalid token") - end - - if expire < Time.now.to_unix + if token["expire"].as_i < Time.now.to_unix raise translate(locale, "Token is expired, please try again") end end @@ -331,7 +326,7 @@ def generate_captcha(key, db) return { question: image, - tokens: [create_response(answer, "sign_in", key, db)], + tokens: {create_response(answer, {"login"}, key, db, use_nonce: true)}, } end @@ -340,7 +335,7 @@ def generate_text_captcha(key, db) response = JSON.parse(response) tokens = response["a"].as_a.map do |answer| - create_response(answer.as_s, "sign_in", key, db) + create_response(answer.as_s, {"login"}, key, db, use_nonce: true) end return { diff --git a/src/invidious/views/clear_watch_history.ecr b/src/invidious/views/clear_watch_history.ecr index ede5e287..55555bee 100644 --- a/src/invidious/views/clear_watch_history.ecr +++ b/src/invidious/views/clear_watch_history.ecr @@ -19,7 +19,6 @@
- - +
diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 2dc0bea4..ae1ccb96 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -85,17 +85,19 @@
<% if env.get? "show_watched" %> -

- - - - -

+
" method="post"> + "> +

+ + + +

+
<% end %> <% if item.responds_to?(:live_now) && item.live_now %>

<%= translate(locale, "LIVE") %>

diff --git a/src/invidious/views/components/subscribe_widget.ecr b/src/invidious/views/components/subscribe_widget.ecr index df16658d..59849c1a 100644 --- a/src/invidious/views/components/subscribe_widget.ecr +++ b/src/invidious/views/components/subscribe_widget.ecr @@ -1,17 +1,21 @@ <% if user %> <% if subscriptions.includes? ucid %>

- "> - <%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %> - +

" method="post"> + "> + + | <%= sub_count_text %>"> + +

<% else %>

- "> - <%= translate(locale, "Subscribe") %> | <%= sub_count_text %> +

" method="post"> + "> + + | <%= sub_count_text %>"> +

<% end %> <% else %> diff --git a/src/invidious/views/components/subscribe_widget_script.ecr b/src/invidious/views/components/subscribe_widget_script.ecr index 9bfd8ebb..8fe0a653 100644 --- a/src/invidious/views/components/subscribe_widget_script.ecr +++ b/src/invidious/views/components/subscribe_widget_script.ecr @@ -15,8 +15,9 @@ function subscribe(timeouts = 0) { var xhr = new XMLHttpRequest(); xhr.responseType = "json"; xhr.timeout = 20000; - xhr.open("GET", url, true); - xhr.send(); + xhr.open("POST", url, true); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send("token=<%= URI.escape(env.get?("token").try &.as(String) || "") %>"); var fallback = subscribe_button.innerHTML; subscribe_button.onclick = unsubscribe; @@ -50,8 +51,9 @@ function unsubscribe(timeouts = 0) { var xhr = new XMLHttpRequest(); xhr.responseType = "json"; xhr.timeout = 20000; - xhr.open("GET", url, true); - xhr.send(); + xhr.open("POST", url, true); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.send("token=<%= URI.escape(env.get?("token").try &.as(String) || "") %>"); var fallback = subscribe_button.innerHTML; subscribe_button.onclick = subscribe; diff --git a/src/invidious/views/delete_account.ecr b/src/invidious/views/delete_account.ecr index 7cc8de9b..f49eaab9 100644 --- a/src/invidious/views/delete_account.ecr +++ b/src/invidious/views/delete_account.ecr @@ -19,7 +19,6 @@
- - +
diff --git a/src/invidious/views/history.ecr b/src/invidious/views/history.ecr index 9be40a0d..15a24f4d 100644 --- a/src/invidious/views/history.ecr +++ b/src/invidious/views/history.ecr @@ -28,14 +28,16 @@ <% else %>
-

- - - -

+
" method="post"> + "> +

+ + + +

+

<% end %> @@ -48,17 +50,18 @@ \ No newline at end of file From 7a8d5a391a29c3f4c6e326a667f80c2f4ff965bc Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 18 Apr 2019 19:17:50 -0500 Subject: [PATCH 071/210] Fix downcasting with usernames --- src/invidious.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 33f35afa..4e48fab0 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -814,7 +814,7 @@ post "/login" do |env| next templated "error" end - email = env.params.body["email"]? + email = env.params.body["email"]?.try &.downcase password = env.params.body["password"]? account_type = env.params.query["type"]? @@ -1024,7 +1024,7 @@ post "/login" do |env| next templated "error" end - user = PG_DB.query_one?("SELECT * FROM users WHERE LOWER(email) = LOWER($1)", email, as: User) + user = PG_DB.query_one?("SELECT * FROM users WHERE email = $1", email, as: User) if user if !user.password From 0372ff0c2cd2741647db878aa395bb0e59441bce Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 19 Apr 2019 08:49:08 -0500 Subject: [PATCH 072/210] Update shard.yml --- shard.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/shard.yml b/shard.yml index 6b8bd7aa..fdff7460 100644 --- a/shard.yml +++ b/shard.yml @@ -15,7 +15,6 @@ dependencies: github: will/crystal-pg sqlite3: github: crystal-lang/crystal-sqlite3 - branch: crystal/0.28.0 crystal: 0.28.0 From 15aa2498b5864468550f02c5ba16bb286bc8aebc Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Mon, 15 Apr 2019 03:58:14 +0000 Subject: [PATCH 073/210] Update Esperanto translation --- locales/eo.json | 80 ++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index b0e9a028..a6a83f28 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -102,19 +102,19 @@ "Source available here.": "Fonto havebla ĉi tie.", "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", "View privacy policy.": "Vidi regularon pri privateco.", - "Trending": "", - "Unlisted": "", + "Trending": "Tendencoj", + "Unlisted": "Ne listigita", "Watch video on Youtube": "Vidi videon en Youtube", "Genre: ": "Ĝenro: ", "License: ": "Licenco: ", - "Family friendly? ": "", - "Wilson score: ": "", - "Engagement: ": "", - "Whitelisted regions: ": "", - "Blacklisted regions: ": "", + "Family friendly? ": "Ĉu familie amika? ", + "Wilson score: ": "Poentaro de Wilson: ", + "Engagement: ": "Intereso: ", + "Whitelisted regions: ": "Regionoj listigitaj en blanka listo: ", + "Blacklisted regions: ": "Regionoj listigitaj en nigra listo: ", "Shared `x`": "Konigita `x`", - "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", + "Premieres in `x`": "Premieras en `x`", + "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Saluton! Ŝajnas, ke vi havas Ĝavoskripton malebligitan. Klaku ĉi tie por vidi komentojn, memoru, ke la ŝargado povus daŭri iom pli.", "View YouTube comments": "Vidi komentojn de YouTube", "View more comments on Reddit": "Vidi pli komentoj en Reddit", "View `x` comments": "Vidi `x` komentojn", @@ -122,41 +122,41 @@ "Hide replies": "Kaŝi respondojn", "Show replies": "Montri respondojn", "Incorrect password": "Malbona pasvorto", - "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", - "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", + "Quota exceeded, try again in a few hours": "Kvoto transpasita, provu denove post iuj horoj", + "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Ne povas ensaluti, certigu, ke dufaktora aŭtentigo (Authenticator aŭ SMS) estas ebligita.", + "Invalid TFA code": "Nevalida TFA-kodo", + "Login failed. This may be because two-factor authentication is not enabled on your account.": "Ensalutado fiaskis. Eble ĉar la dufaktora aŭtentigo estas malebligita en via konto.", "Invalid answer": "Nevalida respondo", "Invalid CAPTCHA": "Nevalida CAPTCHA", - "CAPTCHA is a required field": "", - "User ID is a required field": "", - "Password is a required field": "", + "CAPTCHA is a required field": "CAPTCHA estas deviga kampo", + "User ID is a required field": "Uzula identigilo estas deviga kampo", + "Password is a required field": "Pasvorto estas deviga kampo", "Invalid username or password": "Nevalida uzantnomo aŭ pasvorto", "Please sign in using 'Sign in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", - "Password cannot be empty": "", - "Password cannot be longer than 55 characters": "", + "Password cannot be empty": "Pasvorto ne povas esti malplena", + "Password cannot be longer than 55 characters": "Pasvorto ne povas esti pli longa ol 55 signoj", "Please sign in": "Bonvolu ensaluti", - "Invidious Private Feed for `x`": "", + "Invidious Private Feed for `x`": "Privata Fluo de Invidious por `x`", "channel:`x`": "kanalo:`x`", "Deleted or invalid channel": "Forigita aŭ nevalida kanalo", "This channel does not exist.": "Ĉi tiu kanalo ne ekzistas.", - "Could not get channel info.": "", - "Could not fetch comments": "", + "Could not get channel info.": "Ne povis havigi kanalan informon.", + "Could not fetch comments": "Ne povis venigi komentojn", "View `x` replies": "Vidi `x` respondojn", "`x` ago": "antaŭ `x`", "Load more": "Ŝarĝi pli", "`x` points": "`x` poentoj", - "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", - "Playlist does not exist.": "", - "Could not pull trending pages.": "", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", + "Could not create mix.": "Ne povis krei mikson.", + "Playlist is empty": "Ludlisto estas malplena", + "Invalid playlist.": "Nevalida ludlisto.", + "Playlist does not exist.": "Ludlisto ne ekzistas.", + "Could not pull trending pages.": "Ne povis venigi tendencajn paĝojn.", + "Hidden field \"challenge\" is a required field": "Kaŝita kampo \"challenge\" estas deviga kampo", + "Hidden field \"token\" is a required field": "Kaŝita kampo \"token\" estas deviga kampo", + "Invalid challenge": "Nevalida defio", + "Invalid token": "Nevalida ĵetono", "Invalid user": "Nevalida uzanto", - "Token is expired, please try again": "", + "Token is expired, please try again": "Ĵetono senvalidiĝis, bonvolu provi denove", "English": "Angla", "English (auto-generated)": "Angla (aŭtomate generita)", "Afrikaans": "Afrikansa", @@ -270,26 +270,26 @@ "`x` hours": "`x` horoj", "`x` minutes": "`x` minutoj", "`x` seconds": "`x` sekundoj", - "Fallback comments: ": "", + "Fallback comments: ": "Retrodefaŭltaj komentoj: ", "Popular": "Popularaj", "Top": "Supraj", "About": "Pri", - "Rating: ": "", + "Rating: ": "Takso: ", "Language: ": "Lingvo: ", "Default": "Defaŭlte", "Music": "Musiko", - "Gaming": "", + "Gaming": "Komputiloludoj", "News": "Novaĵoj", "Movies": "Filmoj", "Download": "Elŝuti", "Download as: ": "Elŝuti kiel: ", - "%A %B %-d, %Y": "", + "%A %B %-d, %Y": "%A %-d de %B %Y", "(edited)": "(redaktita)", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", + "Youtube permalink of the comment": "Fiksligilo de la komento en YouTube", + "`x` marked it with a ❤": "`x` markis ĝin per ❤", + "Audio mode": "Aŭda reĝimo", + "Video mode": "Videa reĝimo", "Videos": "Videoj", - "Playlists": "", + "Playlists": "Ludlistoj", "Current version: ": "Nuna versio: " } From b4aecb5b7434571cf1bf4a7ce192dc8b5408ddee Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Mon, 15 Apr 2019 03:58:34 +0000 Subject: [PATCH 074/210] Update Spanish translation --- locales/es.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/es.json b/locales/es.json index e8a8e7d2..1f248e5f 100644 --- a/locales/es.json +++ b/locales/es.json @@ -102,7 +102,7 @@ "View JavaScript license information.": "Ver información de licencia de JavaScript.", "View privacy policy.": "Ver la política de privacidad.", "Trending": "Tendencias", - "Unlisted": "", + "Unlisted": "No listado", "Watch video on Youtube": "Ver el vídeo en Youtube", "Genre: ": "Género: ", "License: ": "Licencia: ", @@ -112,8 +112,8 @@ "Whitelisted regions: ": "Regiones permitidas: ", "Blacklisted regions: ": "Regiones bloqueadas: ", "Shared `x`": "Compartido `x`", - "`x` views": "", - "Premieres in `x`": "", + "`x` views": "`x` visualizaciones", + "Premieres in `x`": "Se estrena en `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.", "View YouTube comments": "Ver los comentarios de YouTube", "View more comments on Reddit": "Ver más comentarios en Reddit", @@ -141,7 +141,7 @@ "Deleted or invalid channel": "El canal no es válido o ha sido borrado", "This channel does not exist.": "El canal no existe.", "Could not get channel info.": "No se ha podido obtener información del canal.", - "Could not fetch comments": "No se han podido recuperar los comentarios.", + "Could not fetch comments": "No se han podido recuperar los comentarios", "View `x` replies": "Ver `x` respuestas", "`x` ago": "hace `x`", "Load more": "Cargar más", @@ -276,7 +276,7 @@ "About": "Acerca de", "Rating: ": "Valoración: ", "Language: ": "Idioma: ", - "View as playlist": "", + "View as playlist": "Ver como lista de reproducción", "Default": "Por defecto", "Music": "Música", "Gaming": "Videojuegos", From a2533af1165f8178125285ab1796477cdc5b91e4 Mon Sep 17 00:00:00 2001 From: Anne Onyme 017 Date: Mon, 15 Apr 2019 18:03:26 +0000 Subject: [PATCH 075/210] Update French translation --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 6a7cb658..ed5dea9b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -55,7 +55,7 @@ "Player volume: ": "Volume du lecteur : ", "Default comments: ": "Source des commentaires : ", "Default captions: ": "Sous-titres par défaut : ", - "Fallback captions: ": "Fallback captions: ", + "Fallback captions: ": "Sous-titres par défaut : ", "Show related videos? ": "Voir les vidéos liées ? ", "Visual preferences": "Préférences du site", "Dark mode: ": "Mode Sombre : ", @@ -100,7 +100,7 @@ "Released under the AGPLv3 by Omar Roth.": "Publié sous licence AGPLv3 par Omar Roth.", "Source available here.": "Code Source.", "View JavaScript license information.": "Voir les informations des licences JavaScript.", - "View privacy policy.": "Politique de confidentialité", + "View privacy policy.": "Consulter la politique de confidentialité.", "Trending": "Tendances", "Unlisted": "Non répertoriée", "Watch video on Youtube": "Voir la vidéo sur Youtube", From 9693363c7689455a7ac44205e39a86e6e91c5b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Wed, 17 Apr 2019 18:28:23 +0000 Subject: [PATCH 076/210] =?UTF-8?q?Update=20Norwegian=20Bokm=C3=A5l=20tran?= =?UTF-8?q?slation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/nb_NO.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 9ce52477..445b53e7 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -112,7 +112,7 @@ "Whitelisted regions: ": "Hvitlistede regioner: ", "Blacklisted regions: ": "Svartelistede regioner: ", "Shared `x`": "Delt `x`", - "`x` views": "", + "`x` views": "`x` visninger", "Premieres in `x`": "Premiere om `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", "View YouTube comments": "Vis YouTube-kommentarer", @@ -276,7 +276,7 @@ "About": "Om", "Rating: ": "Vurdering: ", "Language: ": "Språk: ", - "View as playlist": "", + "View as playlist": "Vis som spilleliste", "Default": "Forvalg", "Music": "Musikk", "Gaming": "Spill", From 2953159f8b6e0785b3f171a31edfbf1be7b15b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 16 Apr 2019 18:02:43 +0000 Subject: [PATCH 077/210] Update Polish translation --- locales/pl.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/locales/pl.json b/locales/pl.json index c39c32b9..e1e7f5d6 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -94,7 +94,7 @@ "Import/Export": "Import/Eksport", "unsubscribe": "odsubskrybuj", "Subscriptions": "Subskrybcje", - "`x` unseen notifications": "`x` niewidzianych powiadomień", + "`x` unseen notifications": "`x` nowych powiadomień", "search": "szukaj", "Sign out": "Wyloguj", "Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.", @@ -112,8 +112,8 @@ "Whitelisted regions: ": "Dostępny na obszarach: ", "Blacklisted regions: ": "Niedostępny na obszarach: ", "Shared `x`": "Udostępniono `x`", - "`x` views": "", - "Premieres in `x`": "", + "`x` views": "`x` wyświetleń", + "Premieres in `x`": "Publikacja za `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", "View YouTube comments": "Wyświetl komentarze z YouTube", "View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie", @@ -276,7 +276,7 @@ "About": "Informacje", "Rating: ": "Ocena: ", "Language: ": "Język: ", - "View as playlist": "", + "View as playlist": "Obejrzyj w playliście", "Default": "Domyślnie", "Music": "Muzyka", "Gaming": "Gry", From e3a0ae8a4b14253774a5ba818b5fa462af30e9e5 Mon Sep 17 00:00:00 2001 From: Tolstovka Date: Tue, 16 Apr 2019 23:05:44 +0000 Subject: [PATCH 078/210] Update Russian translation --- locales/ru.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 26a452f3..14bf8e63 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -114,7 +114,7 @@ "Whitelisted regions: ": "Доступно для: ", "Blacklisted regions: ": "Недоступно для: ", "Shared `x`": "Опубликовано `x`", - "`x` views": "", + "`x` views": "`x` просмотров / просмотр / просмотра", "Premieres in `x`": "Премьера через `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", "View YouTube comments": "Смотреть комментарии с YouTube", @@ -278,7 +278,7 @@ "About": "О сайте", "Rating: ": "Рейтинг: ", "Language: ": "Язык: ", - "View as playlist": "", + "View as playlist": "Смотреть как плейлист", "Default": "По-умолчанию", "Music": "Музыка", "Gaming": "Игры", From 7ac00258cc4803ce4c0c20795d88022538697ba5 Mon Sep 17 00:00:00 2001 From: Tolstovka Date: Wed, 17 Apr 2019 00:34:58 +0000 Subject: [PATCH 079/210] Update Ukrainian translation --- locales/uk.json | 284 ++++++++++++++++++++++++------------------------ 1 file changed, 142 insertions(+), 142 deletions(-) diff --git a/locales/uk.json b/locales/uk.json index 421a7ae9..d7bb5374 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -103,7 +103,7 @@ "View JavaScript license information.": "Переглянути інформацію щодо ліцензії JavaScript.", "View privacy policy.": "Переглянути політику приватності.", "Trending": "У тренді", - "Unlisted": "", + "Unlisted": "Відсутнє у листі", "Watch video on Youtube": "Дивитися відео на YouTube", "Genre: ": "Жанр: ", "License: ": "Ліцензія: ", @@ -151,145 +151,145 @@ "Invalid playlist.": "Недійсний плейлист.", "Playlist does not exist.": "Плейлист не існує.", "Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».", - "Hidden field \"challenge\" is a required field": "", - "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "", - "Token is expired, please try again": "", - "English": "", - "English (auto-generated)": "", - "Afrikaans": "", - "Albanian": "", - "Amharic": "", - "Arabic": "", - "Armenian": "", - "Azerbaijani": "", - "Bangla": "", - "Basque": "", - "Belarusian": "", - "Bosnian": "", - "Bulgarian": "", - "Burmese": "", - "Catalan": "", - "Cebuano": "", - "Chinese (Simplified)": "", - "Chinese (Traditional)": "", - "Corsican": "", - "Croatian": "", - "Czech": "", - "Danish": "", - "Dutch": "", - "Esperanto": "", - "Estonian": "", - "Filipino": "", - "Finnish": "", - "French": "", - "Galician": "", - "Georgian": "", - "German": "", - "Greek": "", - "Gujarati": "", - "Haitian Creole": "", - "Hausa": "", - "Hawaiian": "", - "Hebrew": "", - "Hindi": "", - "Hmong": "", - "Hungarian": "", - "Icelandic": "", - "Igbo": "", - "Indonesian": "", - "Irish": "", - "Italian": "", - "Japanese": "", - "Javanese": "", - "Kannada": "", - "Kazakh": "", - "Khmer": "", - "Korean": "", - "Kurdish": "", - "Kyrgyz": "", - "Lao": "", - "Latin": "", - "Latvian": "", - "Lithuanian": "", - "Luxembourgish": "", - "Macedonian": "", - "Malagasy": "", - "Malay": "", - "Malayalam": "", - "Maltese": "", - "Maori": "", - "Marathi": "", - "Mongolian": "", - "Nepali": "", - "Norwegian": "", - "Nyanja": "", - "Pashto": "", - "Persian": "", - "Polish": "", - "Portuguese": "", - "Punjabi": "", - "Romanian": "", - "Russian": "", - "Samoan": "", - "Scottish Gaelic": "", - "Serbian": "", - "Shona": "", - "Sindhi": "", - "Sinhala": "", - "Slovak": "", - "Slovenian": "", - "Somali": "", - "Southern Sotho": "", - "Spanish": "", - "Spanish (Latin America)": "", - "Sundanese": "", - "Swahili": "", - "Swedish": "", - "Tajik": "", - "Tamil": "", - "Telugu": "", - "Thai": "", - "Turkish": "", - "Ukrainian": "", - "Urdu": "", - "Uzbek": "", - "Vietnamese": "", - "Welsh": "", - "Western Frisian": "", - "Xhosa": "", - "Yiddish": "", - "Yoruba": "", - "Zulu": "", - "`x` years": "", - "`x` months": "", - "`x` weeks": "", - "`x` days": "", - "`x` hours": "", - "`x` minutes": "", - "`x` seconds": "", - "Fallback comments: ": "", - "Popular": "", - "Top": "", - "About": "", - "Rating: ": "", - "Language: ": "", - "Default": "", - "Music": "", - "Gaming": "", - "News": "", - "Movies": "", - "Download": "", - "Download as: ": "", - "%A %B %-d, %Y": "", - "(edited)": "", - "Youtube permalink of the comment": "", - "`x` marked it with a ❤": "", - "Audio mode": "", - "Video mode": "", - "Videos": "", - "Playlists": "", - "Current version: ": "" + "Hidden field \"challenge\" is a required field": "Необхідно заповнити приховане поле «challenge»", + "Hidden field \"token\" is a required field": "Необхідно заповнити приховане поле «token»", + "Invalid challenge": "Неправильна відповідь у «challenge»", + "Invalid token": "Недійсний токен", + "Invalid user": "Недопустиме ім’я користувача", + "Token is expired, please try again": "Термін дії токена закінчився, спробуйте пізніше", + "English": "Англійська", + "English (auto-generated)": "Англійська (сгенеровано автоматично)", + "Afrikaans": "Африкаанс", + "Albanian": "Албанська", + "Amharic": "Амхарська", + "Arabic": "Арабська", + "Armenian": "Вірменська", + "Azerbaijani": "Азербайджанська", + "Bangla": "Бенгальска", + "Basque": "Баскська", + "Belarusian": "Білоруська", + "Bosnian": "Боснійська", + "Bulgarian": "Болгарська", + "Burmese": "Бірманська", + "Catalan": "Каталонська", + "Cebuano": "Себуанська", + "Chinese (Simplified)": "Китайська (спрощена)", + "Chinese (Traditional)": "Китайська (традиційна)", + "Corsican": "Корсиканська", + "Croatian": "Хорватська", + "Czech": "Чеська", + "Danish": "Данська", + "Dutch": "Нідерландська", + "Esperanto": "Есперанто", + "Estonian": "Естонська", + "Filipino": "Філіппінська", + "Finnish": "Фінська", + "French": "Французька", + "Galician": "Галісійська", + "Georgian": "Грузинська", + "German": "Німецька", + "Greek": "Грецька", + "Gujarati": "Гуджаратська", + "Haitian Creole": "Гаїтянська креольська", + "Hausa": "Хауса", + "Hawaiian": "Гавайська", + "Hebrew": "Іврит", + "Hindi": "Гінді", + "Hmong": "Хмонгська", + "Hungarian": "Угорська", + "Icelandic": "Ісландська", + "Igbo": "Ігбо", + "Indonesian": "Індонезійська", + "Irish": "Ірландська", + "Italian": "Італійська", + "Japanese": "Японська", + "Javanese": "Яванська", + "Kannada": "Каннада", + "Kazakh": "Казахська", + "Khmer": "Кхмерська", + "Korean": "Корейська", + "Kurdish": "Курдська", + "Kyrgyz": "Киргизька", + "Lao": "Лаоська", + "Latin": "Латинська", + "Latvian": "Латиська", + "Lithuanian": "Литовська", + "Luxembourgish": "Люксембурзька", + "Macedonian": "Македонська", + "Malagasy": "Малагасійська", + "Malay": "Малайська", + "Malayalam": "Малаялам", + "Maltese": "Мальтійська", + "Maori": "Маорі", + "Marathi": "Маратхі", + "Mongolian": "Монгольська", + "Nepali": "Непальська", + "Norwegian": "Норвезька", + "Nyanja": "Ньянджа", + "Pashto": "Пушту", + "Persian": "Перська", + "Polish": "Польська", + "Portuguese": "Португальська", + "Punjabi": "Пенджабська", + "Romanian": "Румунська", + "Russian": "Російська", + "Samoan": "Самоанська", + "Scottish Gaelic": "Шотландська ґельська", + "Serbian": "Сербська", + "Shona": "Шона", + "Sindhi": "Сіндгі", + "Sinhala": "Сингальська", + "Slovak": "Словацька", + "Slovenian": "Словенська", + "Somali": "Сомалійська", + "Southern Sotho": "Сесото (південна сото)", + "Spanish": "Іспанська", + "Spanish (Latin America)": "Испанська (Латинська Америка)", + "Sundanese": "Сунданська", + "Swahili": "Суахілі", + "Swedish": "Шведська", + "Tajik": "Таджицька", + "Tamil": "Тамільська", + "Telugu": "Телугу", + "Thai": "Тайська", + "Turkish": "Турецька", + "Ukrainian": "Українська", + "Urdu": "Урду", + "Uzbek": "Узбецька", + "Vietnamese": "В’єтнамська", + "Welsh": "Валлійська", + "Western Frisian": "Західнофризька", + "Xhosa": "Коса", + "Yiddish": "Їдиш", + "Yoruba": "Йоруба", + "Zulu": "Зулу", + "`x` years": "`x` років / рік / роки", + "`x` months": "`x` місяців / місяць / місяці", + "`x` weeks": "`x` тижнів / тиждень / тижні", + "`x` days": "`x` днів / день / дні", + "`x` hours": "`x` годин / година / години", + "`x` minutes": "`x` хвилин / хвилина / хвилини", + "`x` seconds": "`x` секунд / секунду / секунди", + "Fallback comments: ": "Резервні коментарі: ", + "Popular": "Популярне", + "Top": "Топ", + "About": "Про сайт", + "Rating: ": "Рейтинг: ", + "Language: ": "Мова: ", + "Default": "Усталено", + "Music": "Музика", + "Gaming": "Ігри", + "News": "Новини", + "Movies": "Фільми", + "Download": "Завантажити", + "Download as: ": "Завантажити як: ", + "%A %B %-d, %Y": "%-d %B %Y, %A", + "(edited)": "(змінено)", + "Youtube permalink of the comment": "Пряме посилання на коментар в YouTube", + "`x` marked it with a ❤": "❤ цьому від каналу `x`", + "Audio mode": "Аудіорежим", + "Video mode": "Відеорежим", + "Videos": "Відео", + "Playlists": "Плейлисти", + "Current version: ": "Поточна версія: " } From eac0a52f10f2c4d351d7b4a253402eb951ffcf80 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 19 Apr 2019 09:20:41 -0500 Subject: [PATCH 080/210] Fix shiftKey for player hotkeys --- src/invidious/views/components/player.ecr | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index b9158e39..30f5294f 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -99,7 +99,7 @@ var player = videojs("player", options, function() { return e.which === 74; }, handler: function(player, options, e) { - player.currentTime(player.currentTime() - 5); + player.currentTime(player.currentTime() - 10); } }, // Go forward 5 seconds @@ -108,13 +108,13 @@ var player = videojs("player", options, function() { return e.which === 76; }, handler: function(player, options, e) { - player.currentTime(player.currentTime() + 5); + player.currentTime(player.currentTime() + 10); } }, // Increase speed increase_speed: { key: function(e) { - return e.which === 190; + return (e.which === 190 && e.shiftKey); }, handler: function(player, _, e) { size = options.playbackRates.length; @@ -125,7 +125,7 @@ var player = videojs("player", options, function() { // Decrease speed decrease_speed: { key: function(e) { - return e.which === 188; + return (e.which === 188 && e.shiftKey); }, handler: function(player, _, e) { size = options.playbackRates.length; From bb5a1ad5136d9c85dd89def312921ae61bd4f856 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 19 Apr 2019 09:38:27 -0500 Subject: [PATCH 081/210] Add 'continue_autoplay' preference --- src/invidious.cr | 9 +++++++-- src/invidious/helpers/helpers.cr | 1 + src/invidious/users.cr | 1 + src/invidious/videos.cr | 5 +++++ src/invidious/views/embed.ecr | 16 ++++++++-------- src/invidious/views/preferences.ecr | 7 ++++++- src/invidious/views/watch.ecr | 24 ++++++++++++------------ 7 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 4e48fab0..57db7e28 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1150,8 +1150,8 @@ post "/login" do |env| view_name = "subscriptions_#{sha256(user.email)}" PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ - SELECT * FROM channel_videos WHERE \ - ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ + SELECT * FROM channel_videos WHERE \ + ucid = ANY ((SELECT subscriptions FROM users WHERE email = E'#{user.email.gsub("'", "\\'")}')::text[]) \ ORDER BY published DESC;") if Kemal.config.ssl || config.https_only @@ -1241,6 +1241,10 @@ post "/preferences" do |env| continue ||= "off" continue = continue == "on" + continue_autoplay = env.params.body["continue_autoplay"]?.try &.as(String) + continue_autoplay ||= "off" + continue_autoplay = continue_autoplay == "on" + listen = env.params.body["listen"]?.try &.as(String) listen ||= "off" listen = listen == "on" @@ -1309,6 +1313,7 @@ post "/preferences" do |env| "video_loop" => video_loop, "autoplay" => autoplay, "continue" => continue, + "continue_autoplay" => continue_autoplay, "listen" => listen, "local" => local, "speed" => speed, diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index 9b29210d..14b7719b 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -63,6 +63,7 @@ struct ConfigPreferences captions: {type: Array(String), default: ["", "", ""], converter: StringToArray}, comments: {type: Array(String), default: ["youtube", ""], converter: StringToArray}, continue: {type: Bool, default: false}, + continue_autoplay: {type: Bool, default: true}, dark_mode: {type: Bool, default: false}, latest_only: {type: Bool, default: false}, listen: {type: Bool, default: false}, diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 43a55eac..2e9ec1e5 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -88,6 +88,7 @@ struct Preferences captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: StringToArray}, comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: StringToArray}, continue: {type: Bool, default: CONFIG.default_user_preferences.continue}, + continue_autoplay: {type: Bool, default: CONFIG.default_user_preferences.continue_autoplay}, dark_mode: {type: Bool, default: CONFIG.default_user_preferences.dark_mode}, latest_only: {type: Bool, default: CONFIG.default_user_preferences.latest_only}, listen: {type: Bool, default: CONFIG.default_user_preferences.listen}, diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index bdd21a48..2ad6cdaa 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -1017,6 +1017,7 @@ end def process_video_params(query, preferences) autoplay = query["autoplay"]?.try &.to_i? continue = query["continue"]?.try &.to_i? + continue_autoplay = query["continue_autoplay"]?.try &.to_i? listen = query["listen"]? && (query["listen"] == "true" || query["listen"] == "1").to_unsafe local = query["local"]? && (query["local"] == "true").to_unsafe preferred_captions = query["subtitles"]?.try &.split(",").map { |a| a.downcase } @@ -1031,6 +1032,7 @@ def process_video_params(query, preferences) # region ||= preferences.region autoplay ||= preferences.autoplay.to_unsafe continue ||= preferences.continue.to_unsafe + continue_autoplay ||= preferences.continue_autoplay.to_unsafe listen ||= preferences.listen.to_unsafe local ||= preferences.local.to_unsafe preferred_captions ||= preferences.captions @@ -1043,6 +1045,7 @@ def process_video_params(query, preferences) autoplay ||= CONFIG.default_user_preferences.autoplay.to_unsafe continue ||= CONFIG.default_user_preferences.continue.to_unsafe + continue_autoplay ||= CONFIG.default_user_preferences.continue_autoplay.to_unsafe listen ||= CONFIG.default_user_preferences.listen.to_unsafe local ||= CONFIG.default_user_preferences.local.to_unsafe preferred_captions ||= CONFIG.default_user_preferences.captions @@ -1054,6 +1057,7 @@ def process_video_params(query, preferences) autoplay = autoplay == 1 continue = continue == 1 + continue_autoplay = continue_autoplay == 1 listen = listen == 1 local = local == 1 related_videos = related_videos == 1 @@ -1087,6 +1091,7 @@ def process_video_params(query, preferences) params = { autoplay: autoplay, continue: continue, + continue_autoplay: continue_autoplay, controls: controls, listen: listen, local: local, diff --git a/src/invidious/views/embed.ecr b/src/invidious/views/embed.ecr index ac69512f..8ae46114 100644 --- a/src/invidious/views/embed.ecr +++ b/src/invidious/views/embed.ecr @@ -55,13 +55,13 @@ function get_playlist(timeouts = 0) { location.assign("/embed/" + xhr.response.nextVideo + "?list=<%= plid %>" - <% if params[:listen] %> - + "&listen=1" + <% if params[:listen] != preferences.listen %> + + "&listen=<%= params[:listen] %>" <% end %> - <% if params[:autoplay] %> + <% if params[:autoplay] || params[:continue_autoplay] %> + "&autoplay=1" <% end %> - <% if params[:speed] %> + <% if params[:speed] != preferences.speed %> + "&speed=<%= params[:speed] %>" <% end %> ); @@ -85,13 +85,13 @@ player.on('ended', function() { <% if !video_series.empty? %> + "?playlist=<%= video_series.join(",") %>" <% end %> - <% if params[:listen] %> - + "&listen=1" + <% if params[:listen] != preferences.listen %> + + "&listen=<%= params[:listen] %>" <% end %> - <% if params[:autoplay] %> + <% if params[:autoplay] || params[:continue_autoplay] %> + "&autoplay=1" <% end %> - <% if params[:speed] %> + <% if params[:speed] != preferences.speed %> + "&speed=<%= params[:speed] %>" <% end %> ); diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr index 5dfe779b..3984a4ab 100644 --- a/src/invidious/views/preferences.ecr +++ b/src/invidious/views/preferences.ecr @@ -24,10 +24,15 @@ function update_value(element) {
- + checked<% end %>>
+
+ + checked<% end %>> +
+
checked<% end %>> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index c5f4e2ba..accbca52 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -202,13 +202,13 @@ player.on('ended', function() { location.assign("/watch?v=" + "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>" + "&continue=1" - <% if params[:listen] %> - + "&listen=1" + <% if params[:listen] != preferences.listen %> + + "&listen=<%= params[:listen] %>" <% end %> - <% if params[:autoplay] %> + <% if params[:autoplay] || params[:continue_autoplay] %> + "&autoplay=1" <% end %> - <% if params[:speed] %> + <% if params[:speed] != preferences.speed %> + "&speed=<%= params[:speed] %>" <% end %> ); @@ -221,13 +221,13 @@ function continue_autoplay(target) { location.assign("/watch?v=" + "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>" + "&continue=1" - <% if params[:listen] %> - + "&listen=1" + <% if params[:listen] != preferences.listen %> + + "&listen=<%= params[:listen] %>" <% end %> - <% if params[:autoplay] %> + <% if params[:autoplay] || params[:continue_autoplay] %> + "&autoplay=1" <% end %> - <% if params[:speed] %> + <% if params[:speed] != preferences.speed %> + "&speed=<%= params[:speed] %>" <% end %> ); @@ -287,13 +287,13 @@ function get_playlist(timeouts = 0) { location.assign("/watch?v=" + xhr.response.nextVideo + "&list=<%= plid %>" - <% if params[:listen] %> - + "&listen=1" + <% if params[:listen] != preferences.listen %> + + "&listen=<%= params[:listen] %>" <% end %> - <% if params[:autoplay] %> + <% if params[:autoplay] || params[:continue_autoplay] %> + "&autoplay=1" <% end %> - <% if params[:speed] %> + <% if params[:speed] != preferences.speed %> + "&speed=<%= params[:speed] %>" <% end %> ); From 486e47f9853f96bf7024bf5df492306f719dc42e Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 19 Apr 2019 10:28:12 -0500 Subject: [PATCH 082/210] Add missing text to locales --- locales/ar.json | 10 +++++++++- locales/de.json | 8 ++++++++ locales/en-US.json | 10 ++++++++++ locales/eo.json | 13 ++++++++++++- locales/es.json | 10 ++++++++++ locales/eu.json | 12 +++++++++++- locales/fr.json | 10 ++++++++++ locales/it.json | 10 ++++++++++ locales/nb_NO.json | 10 ++++++++++ locales/nl.json | 10 ++++++++++ locales/pl.json | 10 ++++++++++ locales/ru.json | 8 ++++++++ locales/uk.json | 13 ++++++++++++- 13 files changed, 130 insertions(+), 4 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 6221862e..6888496e 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -13,6 +13,8 @@ "Next page": "الصفحة الثانية", "Previous page": "الصفحة السابقة", "Clear watch history?": "مسح السجل ؟", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "نعم", "No": "لا", "Import and Export Data": "استخراج و إضافة البيانات", @@ -47,6 +49,7 @@ "Player preferences": "التفضيلات المشغل", "Always loop: ": "كرر الفيديو دائما: ", "Autoplay: ": "تشغيل تلقائى: ", + "Play next by default: ": "", "Autoplay next video: ": "شغل الفيديو التالى تلقائى: ", "Listen by default: ": "تشغيل النسخة السمعية تلقائى: ", "Proxy videos? ": "عرض الفيديوهات عن طريق الوكيل(proxy) ؟", @@ -80,6 +83,7 @@ "Clear watch history": "حذف سجل المشاهدة", "Import/Export data": "إضافة\\إستخراج البيانات", "Manage subscriptions": "إدارة المشتركين", + "Manage tokens": "", "Watch history": "سجل المشاهدة", "Delete account": "حذف الحساب", "Administrator preferences": "إعدادات المدير", @@ -92,9 +96,13 @@ "Report statistics? ": "إبلاغ الإحصائيات", "Save preferences": "حفظ التفضيلات", "Subscription manager": "مدير الإشتراكات", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` مشتركين", + "`x` tokens": "", "Import/Export": "إضافة\\إستخراج", "unsubscribe": "إلغاء الإشتراك", + "revoke": "", "Subscriptions": "الإشتراكات", "`x` unseen notifications": "`x` إشعارات لم تشاهدها بعد ", "search": "بحث", @@ -284,8 +292,8 @@ "Gaming": "الألعاب", "News": "الأخبار", "Movies": "الأفلام", - "Download as: ": "تحميل كـ", "Download": "تحميل", + "Download as: ": "تحميل كـ", "%A %B %-d, %Y": "", "(edited)": "(تم تعديلة)", "Youtube permalink of the comment": "رابط التعليق على اليوتيوب", diff --git a/locales/de.json b/locales/de.json index fd8c40f6..099ba6ba 100644 --- a/locales/de.json +++ b/locales/de.json @@ -13,6 +13,8 @@ "Next page": "Nächste Seite", "Previous page": "Vorherige Seite", "Clear watch history?": "Verlauf löschen?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Ja", "No": "Nein", "Import and Export Data": "Import und Export Daten", @@ -47,6 +49,7 @@ "Player preferences": "Playereinstellungen", "Always loop: ": "Immer wiederholen: ", "Autoplay: ": "Automatisch abspielen: ", + "Play next by default: ": "", "Autoplay next video: ": "nächstes Video automatisch abspielen: ", "Listen by default: ": "Nur Ton als Standard: ", "Proxy videos? ": "", @@ -80,6 +83,7 @@ "Clear watch history": "Verlauf löschen", "Import/Export data": "Daten im- exportieren", "Manage subscriptions": "Abonnements verwalten", + "Manage tokens": "", "Watch history": "Verlauf", "Delete account": "Account löschen", "Administrator preferences": "", @@ -92,9 +96,13 @@ "Report statistics? ": "", "Save preferences": "Einstellungen speichern", "Subscription manager": "Abonnementverwaltung", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` Abonnements", + "`x` tokens": "", "Import/Export": "Importieren/Exportieren", "unsubscribe": "abbestellen", + "revoke": "", "Subscriptions": "Abonnements", "`x` unseen notifications": "`x` ungesehene Benachrichtigungen", "search": "Suchen", diff --git a/locales/en-US.json b/locales/en-US.json index 4fbdbcc9..265acb60 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -13,6 +13,8 @@ "Next page": "Next page", "Previous page": "Previous page", "Clear watch history?": "Clear watch history?", + "Authorize token?": "Authorize token?", + "Authorize token for `x`?": "Authorize token for `x`?", "Yes": "Yes", "No": "No", "Import and Export Data": "Import and Export Data", @@ -47,6 +49,7 @@ "Player preferences": "Player preferences", "Always loop: ": "Always loop: ", "Autoplay: ": "Autoplay: ", + "Play next by default: ": "Play next by default: ", "Autoplay next video: ": "Autoplay next video: ", "Listen by default: ": "Listen by default: ", "Proxy videos? ": "Proxy videos? ", @@ -54,6 +57,8 @@ "Preferred video quality: ": "Preferred video quality: ", "Player volume: ": "Player volume: ", "Default comments: ": "Default comments: ", + "youtube": "youtube", + "reddit": "reddit", "Default captions: ": "Default captions: ", "Fallback captions: ": "Fallback captions: ", "Show related videos? ": "Show related videos? ", @@ -78,6 +83,7 @@ "Clear watch history": "Clear watch history", "Import/Export data": "Import/Export data", "Manage subscriptions": "Manage subscriptions", + "Manage tokens": "Manage tokens", "Watch history": "Watch history", "Delete account": "Delete account", "Administrator preferences": "Administrator preferences", @@ -90,9 +96,13 @@ "Report statistics? ": "Report statistics? ", "Save preferences": "Save preferences", "Subscription manager": "Subscription manager", + "Token manager": "Token manager", + "Token": "Token", "`x` subscriptions": "`x` subscriptions", + "`x` tokens": "`x` tokens", "Import/Export": "Import/Export", "unsubscribe": "unsubscribe", + "revoke": "revoke", "Subscriptions": "Subscriptions", "`x` unseen notifications": "`x` unseen notifications", "search": "search", diff --git a/locales/eo.json b/locales/eo.json index b0e9a028..f33d65bb 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Konigita antaŭ `x`", "Unsubscribe": "Malaboni", "Subscribe": "Aboni", - "Login to subscribe to `x`": "Ensaluti por aboni je `x`", "View channel on YouTube": "Vidi kanalon en YouTube", "newest": "pli novaj", "oldest": "pli malnovaj", @@ -14,6 +13,8 @@ "Next page": "Sekva paĝo", "Previous page": "Antaŭa paĝo", "Clear watch history?": "Ĉu forigi vidohistorion?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Jes", "No": "Ne", "Import and Export Data": "Importi kaj Eksporti Datumojn", @@ -48,6 +49,7 @@ "Player preferences": "Spektilaj agordoj", "Always loop: ": "Ĉiam ripeti: ", "Autoplay: ": "Aŭtomate ludi: ", + "Play next by default: ": "", "Autoplay next video: ": "Aŭtomate ludi sekvan videon: ", "Listen by default: ": "Aŭskulti defaŭlte: ", "Proxy videos? ": "Ĉu uzi prokuran servilon por videoj? ", @@ -55,6 +57,8 @@ "Preferred video quality: ": "Preferita videkvalito: ", "Player volume: ": "Ludila sonforteco: ", "Default comments: ": "Defaŭltaj komentoj: ", + "youtube": "", + "reddit": "", "Default captions: ": "Defaŭltaj subtekstoj: ", "Fallback captions: ": "Retrodefaŭltaj subtekstoj: ", "Show related videos? ": "Ĉu montri rilatajn videojn? ", @@ -79,6 +83,7 @@ "Clear watch history": "Forigi vidohistorion", "Import/Export data": "Importi/Eksporti datumojn", "Manage subscriptions": "Administri abonojn", + "Manage tokens": "", "Watch history": "Vidohistorio", "Delete account": "Forigi konton", "Administrator preferences": "Agordoj de administranto", @@ -91,9 +96,13 @@ "Report statistics? ": "Ĉu raporti statistikojn? ", "Save preferences": "Konservi agordojn", "Subscription manager": "Administrilo de abonoj", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` abonoj", + "`x` tokens": "", "Import/Export": "Importi/Eksporti", "unsubscribe": "malaboni", + "revoke": "", "Subscriptions": "Abonoj", "`x` unseen notifications": "`x` neviditaj sciigoj", "search": "serĉi", @@ -113,6 +122,7 @@ "Whitelisted regions: ": "", "Blacklisted regions: ": "", "Shared `x`": "Konigita `x`", + "`x` views": "", "Premieres in `x`": "", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", "View YouTube comments": "Vidi komentojn de YouTube", @@ -276,6 +286,7 @@ "About": "Pri", "Rating: ": "", "Language: ": "Lingvo: ", + "View as playlist": "", "Default": "Defaŭlte", "Music": "Musiko", "Gaming": "", diff --git a/locales/es.json b/locales/es.json index e8a8e7d2..2ee58815 100644 --- a/locales/es.json +++ b/locales/es.json @@ -13,6 +13,8 @@ "Next page": "Página siguiente", "Previous page": "Página anterior", "Clear watch history?": "¿Quiere borrar el historial de reproducción?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Sí", "No": "No", "Import and Export Data": "Importación y exportación de datos", @@ -47,6 +49,7 @@ "Player preferences": "Preferencias del reproductor", "Always loop: ": "Repetir siempre: ", "Autoplay: ": "Reproducción automática: ", + "Play next by default: ": "", "Autoplay next video: ": "Reproducir automáticamente el vídeo siguiente: ", "Listen by default: ": "Activar el sonido por defecto: ", "Proxy videos? ": "¿Usar un proxy para los vídeos? ", @@ -54,6 +57,8 @@ "Preferred video quality: ": "Calidad de vídeo preferida: ", "Player volume: ": "Volumen del reproductor: ", "Default comments: ": "Comentarios por defecto: ", + "youtube": "", + "reddit": "", "Default captions: ": "Subtítulos por defecto: ", "Fallback captions: ": "Subtítulos alternativos: ", "Show related videos? ": "¿Mostrar vídeos relacionados? ", @@ -78,6 +83,7 @@ "Clear watch history": "Borrar el historial de reproducción", "Import/Export data": "Importar/Exportar datos", "Manage subscriptions": "Gestionar las suscripciones", + "Manage tokens": "", "Watch history": "Historial de reproducción", "Delete account": "Borrar cuenta", "Administrator preferences": "Preferencias de administrador", @@ -90,9 +96,13 @@ "Report statistics? ": "¿Enviar estadísticas? ", "Save preferences": "Guardar las preferencias", "Subscription manager": "Gestor de suscripciones", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` suscripciones", + "`x` tokens": "", "Import/Export": "Importar/Exportar", "unsubscribe": "Desuscribirse", + "revoke": "", "Subscriptions": "Suscripciones", "`x` unseen notifications": "`x` notificaciones sin ver", "search": "buscar", diff --git a/locales/eu.json b/locales/eu.json index 667c6df3..96e29b46 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -13,6 +13,8 @@ "Next page": "Hurrengo orria", "Previous page": "Aurreko orria", "Clear watch history?": "Garbitu ikusitakoen historia?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Bai", "No": "Ez", "Import and Export Data": "Datuak inportatu eta esportatu", @@ -47,6 +49,7 @@ "Player preferences": "", "Always loop: ": "", "Autoplay: ": "", + "Play next by default: ": "", "Autoplay next video: ": "", "Listen by default: ": "", "Proxy videos? ": "", @@ -54,6 +57,8 @@ "Preferred video quality: ": "", "Player volume: ": "", "Default comments: ": "", + "youtube": "", + "reddit": "", "Default captions: ": "", "Fallback captions: ": "", "Show related videos? ": "", @@ -78,6 +83,7 @@ "Clear watch history": "", "Import/Export data": "", "Manage subscriptions": "", + "Manage tokens": "", "Watch history": "", "Delete account": "", "Administrator preferences": "", @@ -90,9 +96,13 @@ "Report statistics? ": "", "Save preferences": "", "Subscription manager": "", + "Token manager": "", + "Token": "", "`x` subscriptions": "", + "`x` tokens": "", "Import/Export": "", "unsubscribe": "", + "revoke": "", "Subscriptions": "", "`x` unseen notifications": "", "search": "", @@ -101,8 +111,8 @@ "Source available here.": "", "View JavaScript license information.": "", "View privacy policy.": "", - "Unlisted": "", "Trending": "", + "Unlisted": "", "Watch video on Youtube": "", "Genre: ": "", "License: ": "", diff --git a/locales/fr.json b/locales/fr.json index 6a7cb658..d5205597 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -13,6 +13,8 @@ "Next page": "Page suivante", "Previous page": "Page précédente", "Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Oui", "No": "Non", "Import and Export Data": "Importer et exporter des données", @@ -47,6 +49,7 @@ "Player preferences": "Préférences du lecteur", "Always loop: ": "Lire en boucle : ", "Autoplay: ": "Lire automatiquement : ", + "Play next by default: ": "", "Autoplay next video: ": "Lire automatiquement la vidéo suivante : ", "Listen by default: ": "Audio uniquement : ", "Proxy videos? ": "Charger les vidéos à travers un proxy ? ", @@ -54,6 +57,8 @@ "Preferred video quality: ": "Qualité vidéo souhaitée : ", "Player volume: ": "Volume du lecteur : ", "Default comments: ": "Source des commentaires : ", + "youtube": "", + "reddit": "", "Default captions: ": "Sous-titres par défaut : ", "Fallback captions: ": "Fallback captions: ", "Show related videos? ": "Voir les vidéos liées ? ", @@ -78,6 +83,7 @@ "Clear watch history": "Supprimer l'historique des vidéos regardées", "Import/Export data": "Importer/exporter les données", "Manage subscriptions": "Gérer les abonnements", + "Manage tokens": "", "Watch history": "Historique de visionnage", "Delete account": "Supprimer votre compte", "Administrator preferences": "Préferences d'Administrateur", @@ -90,9 +96,13 @@ "Report statistics? ": "Télémétrie activé ? ", "Save preferences": "Enregistrer les préférences", "Subscription manager": "Gestionnaire d'abonnement", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` abonnements", + "`x` tokens": "", "Import/Export": "Importer/Exporter", "unsubscribe": "se désabonner", + "revoke": "", "Subscriptions": "Abonnements", "`x` unseen notifications": "`x` notifications non vues", "search": "Rechercher", diff --git a/locales/it.json b/locales/it.json index 97b7c8a4..66939bd1 100644 --- a/locales/it.json +++ b/locales/it.json @@ -13,6 +13,8 @@ "Next page": "Pagina successiva", "Previous page": "Pagina precedente", "Clear watch history?": "Sei sicuro di voler cancellare la cronologia dei video guardati?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Si", "No": "No", "Import and Export Data": "Importazione ed esportazione dati", @@ -47,6 +49,7 @@ "Player preferences": "Preferenze del riproduttore", "Always loop: ": "Ripeti sempre: ", "Autoplay: ": "Riproduzione automatica: ", + "Play next by default: ": "", "Autoplay next video: ": "Riproduci automaticamente il prossimo video: ", "Listen by default: ": "Modalità solo audio come predefinita: ", "Proxy videos? ": "", @@ -54,6 +57,8 @@ "Preferred video quality: ": "Preferenza sulla qualità video: ", "Player volume: ": "Volume di riproduzione: ", "Default comments: ": "Origine dei commenti: ", + "youtube": "", + "reddit": "", "Default captions: ": "Sottotitoli predefiniti: ", "Fallback captions: ": "Sottotitoli alternativi: ", "Show related videos? ": "Mostra video correlati? ", @@ -78,6 +83,7 @@ "Clear watch history": "Cancella la cronologia dei video guardati", "Import/Export data": "Importazione/esportazione dati", "Manage subscriptions": "Gestisci le iscrizioni", + "Manage tokens": "", "Watch history": "Cronologia dei video", "Delete account": "Elimina l'account", "Administrator preferences": "", @@ -90,9 +96,13 @@ "Report statistics? ": "", "Save preferences": "Salva le preferenze", "Subscription manager": "Gestisci le iscrizioni", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` iscrizioni", + "`x` tokens": "", "Import/Export": "Importa/esporta", "unsubscribe": "disiscriviti", + "revoke": "", "Subscriptions": "Iscrizioni", "`x` unseen notifications": "`x` notifiche non visualizzate", "search": "Cerca", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 9ce52477..41086189 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -13,6 +13,8 @@ "Next page": "Neste side", "Previous page": "Forrige side", "Clear watch history?": "Tøm visningshistorikk?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Ja", "No": "Nei", "Import and Export Data": "Importer- og eksporter data", @@ -47,6 +49,7 @@ "Player preferences": "Avspillerinnstillinger", "Always loop: ": "Alltid gjenta: ", "Autoplay: ": "Autoavspilling: ", + "Play next by default: ": "", "Autoplay next video: ": "Autospill neste video: ", "Listen by default: ": "Lytt som forvalg: ", "Proxy videos? ": "Mellomtjen videoer? ", @@ -54,6 +57,8 @@ "Preferred video quality: ": "Foretrukket videokvalitet: ", "Player volume: ": "Avspillerlydstyrke: ", "Default comments: ": "Forvalgte kommentarer: ", + "youtube": "", + "reddit": "", "Default captions: ": "Forvalgte undertitler: ", "Fallback captions: ": "Tilbakefallsundertitler: ", "Show related videos? ": "Vis relaterte videoer? ", @@ -78,6 +83,7 @@ "Clear watch history": "Tøm visningshistorikk", "Import/Export data": "Importer/eksporter data", "Manage subscriptions": "Behandle abonnementer", + "Manage tokens": "", "Watch history": "Visningshistorikk", "Delete account": "Slett konto", "Administrator preferences": "Administratorinnstillinger", @@ -90,9 +96,13 @@ "Report statistics? ": "Innrapporter statistikk? ", "Save preferences": "Lagre innstillinger", "Subscription manager": "Abonnementsbehandler", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` abonnementer", + "`x` tokens": "", "Import/Export": "Importer/eksporter", "unsubscribe": "opphev abonnement", + "revoke": "", "Subscriptions": "Abonnement", "`x` unseen notifications": "`x` usette merknader", "search": "søk", diff --git a/locales/nl.json b/locales/nl.json index 05708939..51ece697 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -13,6 +13,8 @@ "Next page": "Volgende pagina", "Previous page": "Vorige pagina", "Clear watch history?": "Kijk geschiedenis wissen?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Ja", "No": "Nee", "Import and Export Data": "Importeer en Exporteer Gegevens", @@ -47,6 +49,7 @@ "Player preferences": "Afspeler voorkeuren", "Always loop: ": "Altijd herhalen: ", "Autoplay: ": "Automatisch afspelen: ", + "Play next by default: ": "", "Autoplay next video: ": "Automatisch volgende video afspelen: ", "Listen by default: ": "Standaard luisteren: ", "Proxy videos? ": "", @@ -54,6 +57,8 @@ "Preferred video quality: ": "Video kwaliteit voorkeur: ", "Player volume: ": "Afspeler volume: ", "Default comments: ": "Standaard reacties: ", + "youtube": "", + "reddit": "", "Default captions: ": "Standaard ondertitels: ", "Fallback captions: ": "Alternatieve ondertitels: ", "Show related videos? ": "Laat gerelateerde videos zien? ", @@ -78,6 +83,7 @@ "Clear watch history": "Kijkgeschiedenis wissen", "Import/Export data": "Importeer/Exporteer gegevens", "Manage subscriptions": "Abonnees beheren", + "Manage tokens": "", "Watch history": "Kijkgeschiedenis", "Delete account": "Account verwijderen", "Administrator preferences": "", @@ -90,9 +96,13 @@ "Report statistics? ": "", "Save preferences": "Opslaan voorkeuren", "Subscription manager": "Abonnees beheerder", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` abonnees", + "`x` tokens": "", "Import/Export": "Importeer/Exporteer", "unsubscribe": "abonnement opzeggen", + "revoke": "", "Subscriptions": "Abonnees", "`x` unseen notifications": "`x` onbekeken notificaties", "search": "zoeken", diff --git a/locales/pl.json b/locales/pl.json index c39c32b9..f5532eb6 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -13,6 +13,8 @@ "Next page": "Następna strona", "Previous page": "Poprzednia strona", "Clear watch history?": "Wyczyścić historię?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Tak", "No": "Nie", "Import and Export Data": "Import i eksport danych", @@ -47,6 +49,7 @@ "Player preferences": "Ustawienia odtwarzacza", "Always loop: ": "Zawsze zapętlaj: ", "Autoplay: ": "Autoodtwarzanie: ", + "Play next by default: ": "", "Autoplay next video: ": "Odtwórz następny film: ", "Listen by default: ": "Tryb dźwiękowy: ", "Proxy videos? ": "Filmy przez proxy? ", @@ -54,6 +57,8 @@ "Preferred video quality: ": "Preferowana jakość filmów: ", "Player volume: ": "Głośność odtwarzacza: ", "Default comments: ": "Domyślne komentarze: ", + "youtube": "", + "reddit": "", "Default captions: ": "Domyślne napisy: ", "Fallback captions: ": "Zastępcze napisy: ", "Show related videos? ": "Pokaż powiązane filmy? ", @@ -78,6 +83,7 @@ "Clear watch history": "Wyczyść historię", "Import/Export data": "Import/Eksport danych", "Manage subscriptions": "Organizuj subskrybcje", + "Manage tokens": "", "Watch history": "Historia", "Delete account": "Usuń konto", "Administrator preferences": "Preferencje administratora", @@ -90,9 +96,13 @@ "Report statistics? ": "Raportować statystyki? ", "Save preferences": "Zapisz preferencje", "Subscription manager": "Manager subskrybcji", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` subskrybcji", + "`x` tokens": "", "Import/Export": "Import/Eksport", "unsubscribe": "odsubskrybuj", + "revoke": "", "Subscriptions": "Subskrybcje", "`x` unseen notifications": "`x` niewidzianych powiadomień", "search": "szukaj", diff --git a/locales/ru.json b/locales/ru.json index 26a452f3..93c7c106 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -13,6 +13,8 @@ "Next page": "Следующая страница", "Previous page": "Предыдущая страница", "Clear watch history?": "Очистить историю просмотров?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Да", "No": "Нет", "Import and Export Data": "Импорт и экспорт данных", @@ -47,6 +49,7 @@ "Player preferences": "Настройки проигрывателя", "Always loop: ": "Всегда повторять: ", "Autoplay: ": "Автовоспроизведение: ", + "Play next by default: ": "", "Autoplay next video: ": "Автовоспроизведение следующего видео: ", "Listen by default: ": "Режим \"только аудио\" по-умолчанию: ", "Proxy videos? ": "Проксировать видео? ", @@ -80,6 +83,7 @@ "Clear watch history": "Очистить историю просмотра", "Import/Export data": "Импорт/Экспорт данных", "Manage subscriptions": "Управление подписками", + "Manage tokens": "", "Watch history": "История просмотров", "Delete account": "Удалить аккаунт", "Administrator preferences": "Настройки администратора", @@ -92,9 +96,13 @@ "Report statistics? ": "Отображать статистику? ", "Save preferences": "Сохранить настройки", "Subscription manager": "Менеджер подписок", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` подписок", + "`x` tokens": "", "Import/Export": "Импорт/Экспорт", "unsubscribe": "отписаться", + "revoke": "", "Subscriptions": "Подписки", "`x` unseen notifications": "`x` новых оповещений", "search": "поиск", diff --git a/locales/uk.json b/locales/uk.json index 421a7ae9..1fd8185c 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -5,7 +5,6 @@ "Shared `x` ago": "Розміщено `x` назад", "Unsubscribe": "Відписатися", "Subscribe": "Підписатися", - "Login to subscribe to `x`": "Увійдіть, щоб підписатися на `x`", "View channel on YouTube": "Подивитися канал на YouTube", "newest": "найновіше", "oldest": "найстаріше", @@ -14,6 +13,8 @@ "Next page": "Наступна сторінка", "Previous page": "Попередня сторінка", "Clear watch history?": "Очистити історію переглядів?", + "Authorize token?": "", + "Authorize token for `x`?": "", "Yes": "Так", "No": "Ні", "Import and Export Data": "Імпорт і експорт даних", @@ -48,6 +49,7 @@ "Player preferences": "Налаштування програвача", "Always loop: ": "Завжди повторювати: ", "Autoplay: ": "Автовідтворення: ", + "Play next by default: ": "", "Autoplay next video: ": "Автовідтворення наступного відео: ", "Listen by default: ": "Режим «тільки звук» як усталений: ", "Proxy videos? ": "Програвати відео через проксі? ", @@ -55,6 +57,8 @@ "Preferred video quality: ": "Пріорітетна якість відео: ", "Player volume: ": "Гучність відео: ", "Default comments: ": "Джерело коментарів: ", + "youtube": "", + "reddit": "", "Default captions: ": "Основна мова субтитрів: ", "Fallback captions: ": "Запасна мова субтитрів: ", "Show related videos? ": "Показувати схожі відео? ", @@ -79,6 +83,7 @@ "Clear watch history": "Очистити історію переглядів", "Import/Export data": "Імпорт і експорт даних", "Manage subscriptions": "Керування підписками", + "Manage tokens": "", "Watch history": "Історія переглядів", "Delete account": "Видалити обліківку", "Administrator preferences": "Адміністраторські налаштування", @@ -91,9 +96,13 @@ "Report statistics? ": "Повідомляти статистику? ", "Save preferences": "Зберегти налаштування", "Subscription manager": "Менеджер підписок", + "Token manager": "", + "Token": "", "`x` subscriptions": "`x` підписка / підписок / підписки", + "`x` tokens": "", "Import/Export": "Імпорт і експорт", "unsubscribe": "відписатися", + "revoke": "", "Subscriptions": "Підписки", "`x` unseen notifications": "`x` непереглянуте сповіщення / непереглянутих сповіщень / непереглянутих сповіщення", "search": "пошук", @@ -113,6 +122,7 @@ "Whitelisted regions: ": "Доступно у регіонах: ", "Blacklisted regions: ": "Недоступно у регіонах: ", "Shared `x`": "Розміщено `x`", + "`x` views": "", "Premieres in `x`": "Прем’єра через `x`", "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Схоже, у вас відключений JavaScript. Щоб побачити коментарі, натисніть сюда, але майте на увазі, що вони можуть завантажуватися трохи довше.", "View YouTube comments": "Переглянути коментарі з YouTube", @@ -276,6 +286,7 @@ "About": "", "Rating: ": "", "Language: ": "", + "View as playlist": "", "Default": "", "Music": "", "Gaming": "", From aa10a9d899c694fb8652d190039c386534516d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Fri, 19 Apr 2019 18:14:11 +0200 Subject: [PATCH 083/210] Language fixes (#366) * Language fixes --- locales/ar.json | 52 ++++++++++---------- locales/de.json | 48 +++++++++--------- locales/en-US.json | 48 +++++++++--------- locales/eo.json | 48 +++++++++--------- locales/es.json | 48 +++++++++--------- locales/eu.json | 52 ++++++++++---------- locales/fr.json | 48 +++++++++--------- locales/it.json | 48 +++++++++--------- locales/nb_NO.json | 48 +++++++++--------- locales/nl.json | 48 +++++++++--------- locales/pl.json | 48 +++++++++--------- locales/ru.json | 48 +++++++++--------- locales/uk.json | 48 +++++++++--------- src/invidious.cr | 10 ++-- src/invidious/comments.cr | 2 +- src/invidious/helpers/tokens.cr | 4 +- src/invidious/playlists.cr | 4 +- src/invidious/videos.cr | 2 +- src/invidious/views/login.ecr | 10 ++-- src/invidious/views/preferences.ecr | 2 +- src/invidious/views/subscription_manager.ecr | 2 +- src/invidious/views/template.ecr | 4 +- src/invidious/views/watch.ecr | 4 +- 23 files changed, 337 insertions(+), 339 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 6888496e..adb8c649 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "البديل الكامل لموقع يوتيوب", "JavaScript license information": "معلومات ترخيص JavaScript", "source": "المصدر", - "Login": "تسجيل الدخول", - "Login/Register": "تسجيل الدخول\\إنشاء حساب", - "Login to Google": "تسجيل الدخول بإستخدام جوجل", + "Log in": "تسجيل الدخول", + "Log in/register": "تسجيل الدخول\\إنشاء حساب", + "Log in with Google": "تسجيل الدخول بإستخدام جوجل", "User ID": "إسم المستخدم", "Password": "الرقم السرى", "Time (h:mm:ss):": "(يجب ان يكتب مثل هذا التنسيق) الوقت (h(ساعات):mm(دقائق):ss(ثوانى)):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "CAPTCHA صورية", "Sign In": "تسجيل الدخول", "Register": "انشاء الحساب", - "Email": "الإيميل", + "E-mail": "الإيميل", "Google verification code": "رمز تحقق جوجل", "Preferences": "التفضيلات", "Player preferences": "التفضيلات المشغل", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "إظهار الإشعارات فقط (إذا كان هناك أي): ", "Data preferences": "إعدادات التفضيلات", "Clear watch history": "حذف سجل المشاهدة", - "Import/Export data": "إضافة\\إستخراج البيانات", + "Import/export data": "إضافة\\إستخراج البيانات", "Manage subscriptions": "إدارة المشتركين", "Manage tokens": "", "Watch history": "سجل المشاهدة", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` مشتركين", "`x` tokens": "", - "Import/Export": "إضافة\\إستخراج", + "Import/export": "إضافة\\إستخراج", "unsubscribe": "إلغاء الإشتراك", "revoke": "", "Subscriptions": "الإشتراكات", "`x` unseen notifications": "`x` إشعارات لم تشاهدها بعد ", "search": "بحث", - "Sign out": "تسجيل الخروج", + "Log out": "تسجيل الخروج", "Released under the AGPLv3 by Omar Roth.": "تم الإنشاء تحت AGPLv3 بواسطة عمر روث.", "Source available here.": "الأكواد متوفرة هنا.", "View JavaScript license information.": "مشاهدة معلومات حول تراخيص الجافاسكريبت.", "View privacy policy.": "عرض سياسة الخصوصية", "Trending": "الشائع", "Unlisted": "غير مصنف", - "Watch video on Youtube": "مشاهدة الفيديو على اليوتيوب", + "Watch on YouTube": "مشاهدة الفيديو على اليوتيوب", "Genre: ": "النوع: ", "License: ": "التراخيص: ", "Family friendly? ": "محتوى عائلى? ", @@ -124,7 +124,7 @@ "Shared `x`": "شارك منذ `x`", "`x` views": "", "Premieres in `x`": "يعرض فى 'x'", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.", "View YouTube comments": "عرض تعليقات اليوتيوب", "View more comments on Reddit": "عرض المزيد من التعليقات على\\من موقع Reddit", "View `x` comments": "عرض `x` تعليقات", @@ -133,19 +133,19 @@ "Show replies": "عرض الردود", "Incorrect password": "الرقم السرى غير صحيح", "Quota exceeded, try again in a few hours": "تم تجاوز عدد المرات المسموح بها, حاول مرة اخرى بعد عدة ساعات", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "غير قادر على تسجيل الدخول, تأكد من تشغيل المصادقة الثنائية 2FA.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "غير قادر على تسجيل الدخول, تأكد من تشغيل المصادقة الثنائية 2FA.", "Invalid TFA code": "كود مصادقة ثنائية 2FA غير صحيح", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "لم يتم تسجيل الدخول. هذا ربما بسبب ان المصادقة الثنائية 2FA معطلة فى حسابك.", - "Invalid answer": "إجابة خاطئة", - "Invalid CAPTCHA": "الكابتشا CAPTCHA غير صاحلة", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "لم يتم تسجيل الدخول. هذا ربما بسبب ان المصادقة الثنائية 2FA معطلة فى حسابك.", + "Wrong answer": "إجابة خاطئة", + "Erroneous CAPTCHA": "الكابتشا CAPTCHA غير صاحلة", "CAPTCHA is a required field": "مكان الكابتشا CAPTCHA مطلوب", "User ID is a required field": "مكان إسم المستخدم مطلوب", "Password is a required field": "مكان الرقم السرى مطلوب", - "Invalid username or password": "إسم المستخدم او الرقم السرى غير صحيح", - "Please sign in using 'Sign in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'", + "Wrong username or password": "إسم المستخدم او الرقم السرى غير صحيح", + "Please sign in using 'Log in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'", "Password cannot be empty": "الرقم السرى لايمكن ان يكون فارغ", "Password cannot be longer than 55 characters": "الرقم السرى لا يتعدى 55 حرف", - "Please sign in": "الرجاء تسجيل الدخول", + "Please log in": "الرجاء تسجيل الدخول", "Invidious Private Feed for `x`": "صفحة Invidious للمشتركين الخاصة\\مخفية لـ `x`", "channel:`x`": "قناة:`x`", "Deleted or invalid channel": "قناة ممسوحة او غير صالحة", @@ -157,15 +157,15 @@ "Load more": "عرض المزيد", "`x` points": "`x` نقاط", "Could not create mix.": "لم يستطع عمل خلط.", - "Playlist is empty": "قائمة التشغيل فارغة", - "Invalid playlist.": "قائمة التشغيل غير صالحة.", + "Empty playlist": "قائمة التشغيل فارغة", + "Not a playlist.": "قائمة التشغيل غير صالحة.", "Playlist does not exist.": "قائمة التشغيل غير موجودة.", "Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.", "Hidden field \"challenge\" is a required field": "مكان مخفى \"تحدى\" مكان مطلوب", "Hidden field \"token\" is a required field": "مكان مخفى \"رمز\" مكان مطلوب", - "Invalid challenge": "تحدى غير صالح", - "Invalid token": "روز غير صالح", - "Invalid user": "مستخدم غير صالح", + "Erroneous challenge": "تحدى غير صالح", + "Erroneous token": "روز غير صالح", + "No such user": "مستخدم غير صالح", "Token is expired, please try again": "الرمز منتهى الصلاحية , الرجاء المحاولة مرة اخرى", "English": "إنجليزى", "English (auto-generated)": "إنجليزى (تم إنشائة تلقائى)", @@ -234,7 +234,7 @@ "Marathi": "المهاراتية", "Mongolian": "المنغولية", "Nepali": "النيبالية", - "Norwegian": "النرويجية", + "Norwegian Bokmål": "النرويجية", "Nyanja": "نيانجا", "Pashto": "الباشتو", "Persian": "الفارسية", @@ -292,15 +292,15 @@ "Gaming": "الألعاب", "News": "الأخبار", "Movies": "الأفلام", - "Download": "تحميل", - "Download as: ": "تحميل كـ", + "Download": "تحميل كـ", + "Download as: ": "تحميل", "%A %B %-d, %Y": "", "(edited)": "(تم تعديلة)", - "Youtube permalink of the comment": "رابط التعليق على اليوتيوب", + "YouTube comment permalink": "رابط التعليق على اليوتيوب", "`x` marked it with a ❤": "'x' اعجب بهذا", "Audio mode": "الوضع الصوتى", "Video mode": "وضع الفيديو", "Videos": "الفيديوهات", "Playlists": "قوائم التشغيل", "Current version: ": "الإصدار الحالى" -} +} \ No newline at end of file diff --git a/locales/de.json b/locales/de.json index 099ba6ba..cffe8b95 100644 --- a/locales/de.json +++ b/locales/de.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Eine alternative Oberfläche für YouTube", "JavaScript license information": "JavaScript Lizenzinformationen", "source": "Quelle", - "Login": "Einloggen", - "Login/Register": "Einloggen/Registrieren", - "Login to Google": "In Google einloggen", + "Log in": "Einloggen", + "Log in/register": "Einloggen/Registrieren", + "Log in with Google": "In Google einloggen", "User ID": "Benutzer ID", "Password": "Passwort", "Time (h:mm:ss):": "Zeit (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Image CAPTCHA", "Sign In": "Einloggen", "Register": "Registrieren", - "Email": "Email", + "E-mail": "Email", "Google verification code": "Google Bestätigungscode", "Preferences": "Einstellungen", "Player preferences": "Playereinstellungen", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Nur Benachrichtigungen anzeigen (wenn es welche gibt): ", "Data preferences": "Dateneinstellungen", "Clear watch history": "Verlauf löschen", - "Import/Export data": "Daten im- exportieren", + "Import/export data": "Daten im- exportieren", "Manage subscriptions": "Abonnements verwalten", "Manage tokens": "", "Watch history": "Verlauf", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` Abonnements", "`x` tokens": "", - "Import/Export": "Importieren/Exportieren", + "Import/export": "Importieren/Exportieren", "unsubscribe": "abbestellen", "revoke": "", "Subscriptions": "Abonnements", "`x` unseen notifications": "`x` ungesehene Benachrichtigungen", "search": "Suchen", - "Sign out": "Abmelden", + "Log out": "Abmelden", "Released under the AGPLv3 by Omar Roth.": "Veröffentlicht unter AGPLv3 von Omar Roth.", "Source available here.": "Quellcode verfügbar hier.", "View JavaScript license information.": "Javascript Lizenzinformationen anzeigen.", "View privacy policy.": "", "Trending": "Trending", "Unlisted": "", - "Watch video on Youtube": "Video auf YouTube ansehen", + "Watch on YouTube": "Video auf YouTube ansehen", "Genre: ": "Genre: ", "License: ": "Lizenz: ", "Family friendly? ": "Familienfreundlich? ", @@ -124,7 +124,7 @@ "Shared `x`": "Geteilt `x`", "`x` views": "", "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hallo! Anscheinend haben Sie JavaScript deaktiviert. Klicken Sie hier um Kommentare anzuzeigen, beachten sie dass es etwas länger dauern kann um sie zu laden.", "View YouTube comments": "YouTube Kommentare anzeigen", "View more comments on Reddit": "Mehr Kommentare auf Reddit anzeigen", "View `x` comments": "`x` Kommentare anzeigen", @@ -133,19 +133,19 @@ "Show replies": "Antworten anzeigen", "Incorrect password": "Falsches Passwort", "Quota exceeded, try again in a few hours": "Kontingent überschritten, versuche es in ein paar Stunden erneut", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Login nicht möglich, stellen Sie sicher dass two-factor Authentifikation (Authentifizierung oder SMS) aktiviert ist.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Login nicht möglich, stellen Sie sicher dass two-factor Authentifikation (Authentifizierung oder SMS) aktiviert ist.", "Invalid TFA code": "Ungültiger TFA Code", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login fehlgeschlagen. Das kann daran liegen dass two-factor Authentifizierung in ihrem Account nicht aktiviert ist.", - "Invalid answer": "Ungültige Antwort", - "Invalid CAPTCHA": "Ungültiges CAPTCHA", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Login fehlgeschlagen. Das kann daran liegen dass two-factor Authentifizierung in ihrem Account nicht aktiviert ist.", + "Wrong answer": "Ungültige Antwort", + "Erroneous CAPTCHA": "Ungültiges CAPTCHA", "CAPTCHA is a required field": "CAPTCHA ist eine erforderliche Eingabe", "User ID is a required field": "Benutzer ID ist eine erforderliche Eingabe", "Password is a required field": "Passwort ist eine erforderliche Eingabe", - "Invalid username or password": "Ungültiger Benutzername oder Passwort", - "Please sign in using 'Sign in with Google'": "Bitte melden sie sich mit 'Mit Google anmelden' an", + "Wrong username or password": "Ungültiger Benutzername oder Passwort", + "Please sign in using 'Log in with Google'": "Bitte melden sie sich mit 'Mit Google anmelden' an", "Password cannot be empty": "Passwort darf nicht leer sein", "Password cannot be longer than 55 characters": "Passwort darf nicht länger als 55 Zeichen sein", - "Please sign in": "Bitte anmelden", + "Please log in": "Bitte anmelden", "Invidious Private Feed for `x`": "Invidious Persönlicher Feed für `x`", "channel:`x`": "Kanal:`x`", "Deleted or invalid channel": "Gelöschter oder ungültiger Kanal", @@ -157,15 +157,15 @@ "Load more": "Mehr laden", "`x` points": "`x` Punkte", "Could not create mix.": "Mix konnte nicht erstellt werden.", - "Playlist is empty": "Playlist ist leer", - "Invalid playlist.": "Ungültige Playlist.", + "Empty playlist": "Playlist ist leer", + "Not a playlist.": "Ungültige Playlist.", "Playlist does not exist.": "Playlist existiert nicht.", "Could not pull trending pages.": "Trending Seiten konnten nicht geladen werden.", "Hidden field \"challenge\" is a required field": "Verstecktes Feld \"challenge\" ist eine erforderliche Eingabe", "Hidden field \"token\" is a required field": "Verstecktes Feld \"token\" ist eine erforderliche Eingabe", - "Invalid challenge": "Ungültiger Test", - "Invalid token": "Ungöltige Marke", - "Invalid user": "Ungültiger Benutzer", + "Erroneous challenge": "Ungültiger Test", + "Erroneous token": "Ungöltige Marke", + "No such user": "Ungültiger Benutzer", "Token is expired, please try again": "Marke ist abgelaufen, bitte erneut versuchen", "English": "Englisch", "English (auto-generated)": "Englisch (automatisch erzeugt)", @@ -234,7 +234,7 @@ "Marathi": "Marathi", "Mongolian": "Mongolisch", "Nepali": "Nepalesisch", - "Norwegian": "Norwegisch", + "Norwegian Bokmål": "Norwegisch", "Nyanja": "Nyanja", "Pashto": "Paschtunisch", "Persian": "Persisch", @@ -296,11 +296,11 @@ "Download as: ": "", "%A %B %-d, %Y": "", "(edited)": "", - "Youtube permalink of the comment": "", + "YouTube comment permalink": "", "`x` marked it with a ❤": "", "Audio mode": "", "Video mode": "", "Videos": "", "Playlists": "", "Current version: ": "" -} +} \ No newline at end of file diff --git a/locales/en-US.json b/locales/en-US.json index 265acb60..9cfce711 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "An alternative front-end to YouTube", "JavaScript license information": "JavaScript license information", "source": "source", - "Login": "Login", - "Login/Register": "Login/Register", - "Login to Google": "Login to Google", + "Log in": "Log in", + "Log in/register": "Log in/register", + "Log in with Google": "Log in with Google", "User ID": "User ID", "Password": "Password", "Time (h:mm:ss):": "Time (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Image CAPTCHA", "Sign In": "Sign In", "Register": "Register", - "Email": "Email", + "E-mail": "E-mail", "Google verification code": "Google verification code", "Preferences": "Preferences", "Player preferences": "Player preferences", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Only show notifications (if there are any): ", "Data preferences": "Data preferences", "Clear watch history": "Clear watch history", - "Import/Export data": "Import/Export data", + "Import/export data": "Import/export data", "Manage subscriptions": "Manage subscriptions", "Manage tokens": "Manage tokens", "Watch history": "Watch history", @@ -100,20 +100,20 @@ "Token": "Token", "`x` subscriptions": "`x` subscriptions", "`x` tokens": "`x` tokens", - "Import/Export": "Import/Export", + "Import/export": "Import/export", "unsubscribe": "unsubscribe", "revoke": "revoke", "Subscriptions": "Subscriptions", "`x` unseen notifications": "`x` unseen notifications", "search": "search", - "Sign out": "Sign out", + "Log out": "Log out", "Released under the AGPLv3 by Omar Roth.": "Released under the AGPLv3 by Omar Roth.", "Source available here.": "Source available here.", "View JavaScript license information.": "View JavaScript license information.", "View privacy policy.": "View privacy policy.", "Trending": "Trending", "Unlisted": "Unlisted", - "Watch video on Youtube": "Watch video on Youtube", + "Watch on YouTube": "Watch on YouTube", "Genre: ": "Genre: ", "License: ": "License: ", "Family friendly? ": "Family friendly? ", @@ -124,7 +124,7 @@ "Shared `x`": "Shared `x`", "`x` views": "`x` views", "Premieres in `x`": "Premieres in `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.", "View YouTube comments": "View YouTube comments", "View more comments on Reddit": "View more comments on Reddit", "View `x` comments": "View `x` comments", @@ -133,19 +133,19 @@ "Show replies": "Show replies", "Incorrect password": "Incorrect password", "Quota exceeded, try again in a few hours": "Quota exceeded, try again in a few hours", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.", "Invalid TFA code": "Invalid TFA code", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login failed. This may be because two-factor authentication is not enabled on your account.", - "Invalid answer": "Invalid answer", - "Invalid CAPTCHA": "Invalid CAPTCHA", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Login failed. This may be because two-factor authentication is not turned on for your account.", + "Wrong answer": "Wrong answer", + "Erroneous CAPTCHA": "Erroneous CAPTCHA", "CAPTCHA is a required field": "CAPTCHA is a required field", "User ID is a required field": "User ID is a required field", "Password is a required field": "Password is a required field", - "Invalid username or password": "Invalid username or password", - "Please sign in using 'Sign in with Google'": "Please sign in using 'Sign in with Google'", + "Wrong username or password": "Wrong username or password", + "Please sign in using 'Log in with Google'": "Please sign in using 'Log in with Google'", "Password cannot be empty": "Password cannot be empty", "Password cannot be longer than 55 characters": "Password cannot be longer than 55 characters", - "Please sign in": "Please sign in", + "Please log in": "Please log in", "Invidious Private Feed for `x`": "Invidious Private Feed for `x`", "channel:`x`": "channel:`x`", "Deleted or invalid channel": "Deleted or invalid channel", @@ -157,15 +157,15 @@ "Load more": "Load more", "`x` points": "`x` points", "Could not create mix.": "Could not create mix.", - "Playlist is empty": "Playlist is empty", - "Invalid playlist.": "Invalid playlist.", + "Empty playlist": "Empty playlist", + "Not a playlist.": "Not a playlist.", "Playlist does not exist.": "Playlist does not exist.", "Could not pull trending pages.": "Could not pull trending pages.", "Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field", "Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field", - "Invalid challenge": "Invalid challenge", - "Invalid token": "Invalid token", - "Invalid user": "Invalid user", + "Erroneous challenge": "Erroneous challenge", + "Erroneous token": "Erroneous token", + "No such user": "No such user", "Token is expired, please try again": "Token is expired, please try again", "English": "English", "English (auto-generated)": "English (auto-generated)", @@ -234,7 +234,7 @@ "Marathi": "Marathi", "Mongolian": "Mongolian", "Nepali": "Nepali", - "Norwegian": "Norwegian", + "Norwegian Bokmål": "Norwegian Bokmål", "Nyanja": "Nyanja", "Pashto": "Pashto", "Persian": "Persian", @@ -296,11 +296,11 @@ "Download as: ": "Download as: ", "%A %B %-d, %Y": "%A %B %-d, %Y", "(edited)": "(edited)", - "Youtube permalink of the comment": "Youtube permalink of the comment", + "YouTube comment permalink": "YouTube comment permalink", "`x` marked it with a ❤": "`x` marked it with a ❤", "Audio mode": "Audio mode", "Video mode": "Video mode", "Videos": "Videos", "Playlists": "Playlists", "Current version: ": "Current version: " -} +} \ No newline at end of file diff --git a/locales/eo.json b/locales/eo.json index 859a3036..317dc3ba 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Alternativa fasado al YouTube", "JavaScript license information": "Ĝavoskripta licenca informo", "source": "fonto", - "Login": "Ensaluti", - "Login/Register": "Ensaluti/Registriĝi", - "Login to Google": "Ensaluti al Google", + "Log in": "Ensaluti", + "Log in/register": "Ensaluti/Registriĝi", + "Log in with Google": "Ensaluti al Google", "User ID": "Uzula identigilo", "Password": "Pasvorto", "Time (h:mm:ss):": "Horo (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Bilda CAPTCHA", "Sign In": "Ensaluti", "Register": "Registriĝi", - "Email": "Retpoŝto", + "E-mail": "Retpoŝto", "Google verification code": "Kontrolkodo de Google", "Preferences": "Agordoj", "Player preferences": "Spektilaj agordoj", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Nur montri sciigojn (se estas): ", "Data preferences": "Datumagordoj", "Clear watch history": "Forigi vidohistorion", - "Import/Export data": "Importi/Eksporti datumojn", + "Import/export data": "Importi/Eksporti datumojn", "Manage subscriptions": "Administri abonojn", "Manage tokens": "", "Watch history": "Vidohistorio", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` abonoj", "`x` tokens": "", - "Import/Export": "Importi/Eksporti", + "Import/export": "Importi/Eksporti", "unsubscribe": "malaboni", "revoke": "", "Subscriptions": "Abonoj", "`x` unseen notifications": "`x` neviditaj sciigoj", "search": "serĉi", - "Sign out": "Elsaluti", + "Log out": "Elsaluti", "Released under the AGPLv3 by Omar Roth.": "Eldonita sub la AGPLv3 de Omar Roth.", "Source available here.": "Fonto havebla ĉi tie.", "View JavaScript license information.": "Vidi Ĝavoskriptan licencan informon.", "View privacy policy.": "Vidi regularon pri privateco.", "Trending": "Tendencoj", "Unlisted": "Ne listigita", - "Watch video on Youtube": "Vidi videon en Youtube", + "Watch on YouTube": "Vidi videon en Youtube", "Genre: ": "Ĝenro: ", "License: ": "Licenco: ", "Family friendly? ": "Ĉu familie amika? ", @@ -124,7 +124,7 @@ "Shared `x`": "Konigita `x`", "`x` views": "", "Premieres in `x`": "Premieras en `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Saluton! Ŝajnas, ke vi havas Ĝavoskripton malebligitan. Klaku ĉi tie por vidi komentojn, memoru, ke la ŝargado povus daŭri iom pli.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Saluton! Ŝajnas, ke vi havas Ĝavoskripton malebligitan. Klaku ĉi tie por vidi komentojn, memoru, ke la ŝargado povus daŭri iom pli.", "View YouTube comments": "Vidi komentojn de YouTube", "View more comments on Reddit": "Vidi pli komentoj en Reddit", "View `x` comments": "Vidi `x` komentojn", @@ -133,19 +133,19 @@ "Show replies": "Montri respondojn", "Incorrect password": "Malbona pasvorto", "Quota exceeded, try again in a few hours": "Kvoto transpasita, provu denove post iuj horoj", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Ne povas ensaluti, certigu, ke dufaktora aŭtentigo (Authenticator aŭ SMS) estas ebligita.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Ne povas ensaluti, certigu, ke dufaktora aŭtentigo (Authenticator aŭ SMS) estas ebligita.", "Invalid TFA code": "Nevalida TFA-kodo", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Ensalutado fiaskis. Eble ĉar la dufaktora aŭtentigo estas malebligita en via konto.", - "Invalid answer": "Nevalida respondo", - "Invalid CAPTCHA": "Nevalida CAPTCHA", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Ensalutado fiaskis. Eble ĉar la dufaktora aŭtentigo estas malebligita en via konto.", + "Wrong answer": "Nevalida respondo", + "Erroneous CAPTCHA": "Nevalida CAPTCHA", "CAPTCHA is a required field": "CAPTCHA estas deviga kampo", "User ID is a required field": "Uzula identigilo estas deviga kampo", "Password is a required field": "Pasvorto estas deviga kampo", - "Invalid username or password": "Nevalida uzantnomo aŭ pasvorto", - "Please sign in using 'Sign in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", + "Wrong username or password": "Nevalida uzantnomo aŭ pasvorto", + "Please sign in using 'Log in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'", "Password cannot be empty": "Pasvorto ne povas esti malplena", "Password cannot be longer than 55 characters": "Pasvorto ne povas esti pli longa ol 55 signoj", - "Please sign in": "Bonvolu ensaluti", + "Please log in": "Bonvolu ensaluti", "Invidious Private Feed for `x`": "Privata Fluo de Invidious por `x`", "channel:`x`": "kanalo:`x`", "Deleted or invalid channel": "Forigita aŭ nevalida kanalo", @@ -157,15 +157,15 @@ "Load more": "Ŝarĝi pli", "`x` points": "`x` poentoj", "Could not create mix.": "Ne povis krei mikson.", - "Playlist is empty": "Ludlisto estas malplena", - "Invalid playlist.": "Nevalida ludlisto.", + "Empty playlist": "Ludlisto estas malplena", + "Not a playlist.": "Nevalida ludlisto.", "Playlist does not exist.": "Ludlisto ne ekzistas.", "Could not pull trending pages.": "Ne povis venigi tendencajn paĝojn.", "Hidden field \"challenge\" is a required field": "Kaŝita kampo \"challenge\" estas deviga kampo", "Hidden field \"token\" is a required field": "Kaŝita kampo \"token\" estas deviga kampo", - "Invalid challenge": "Nevalida defio", - "Invalid token": "Nevalida ĵetono", - "Invalid user": "Nevalida uzanto", + "Erroneous challenge": "Nevalida defio", + "Erroneous token": "Nevalida ĵetono", + "No such user": "Nevalida uzanto", "Token is expired, please try again": "Ĵetono senvalidiĝis, bonvolu provi denove", "English": "Angla", "English (auto-generated)": "Angla (aŭtomate generita)", @@ -234,7 +234,7 @@ "Marathi": "Marata", "Mongolian": "Mongola", "Nepali": "Nepala", - "Norwegian": "Norvega", + "Norwegian Bokmål": "Norvega", "Nyanja": "Njanĝa", "Pashto": "Paŝtuna", "Persian": "Persa", @@ -296,11 +296,11 @@ "Download as: ": "Elŝuti kiel: ", "%A %B %-d, %Y": "%A %-d de %B %Y", "(edited)": "(redaktita)", - "Youtube permalink of the comment": "Fiksligilo de la komento en YouTube", + "YouTube comment permalink": "Fiksligilo de la komento en YouTube", "`x` marked it with a ❤": "`x` markis ĝin per ❤", "Audio mode": "Aŭda reĝimo", "Video mode": "Videa reĝimo", "Videos": "Videoj", "Playlists": "Ludlistoj", "Current version: ": "Nuna versio: " -} +} \ No newline at end of file diff --git a/locales/es.json b/locales/es.json index 845b5e5b..4c6f4f39 100644 --- a/locales/es.json +++ b/locales/es.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Una interfaz alternativa para YouTube", "JavaScript license information": "Información de licencia de JavaScript", "source": "código fuente", - "Login": "Iniciar sesión", - "Login/Register": "Iniciar sesión/Registrarse", - "Login to Google": "Iniciar sesión en Google", + "Log in": "Iniciar sesión", + "Log in/register": "Iniciar sesión/Registrarse", + "Log in with Google": "Iniciar sesión en Google", "User ID": "Nombre", "Password": "Contraseña", "Time (h:mm:ss):": "Hora (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "CAPTCHA en imagen", "Sign In": "Iniciar sesión", "Register": "Registrarse", - "Email": "Correo", + "E-mail": "Correo", "Google verification code": "Código de verificación de Google", "Preferences": "Preferencias", "Player preferences": "Preferencias del reproductor", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Mostrar solo notificaciones (si hay alguna): ", "Data preferences": "Preferencias de los datos", "Clear watch history": "Borrar el historial de reproducción", - "Import/Export data": "Importar/Exportar datos", + "Import/export data": "Importar/Exportar datos", "Manage subscriptions": "Gestionar las suscripciones", "Manage tokens": "", "Watch history": "Historial de reproducción", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` suscripciones", "`x` tokens": "", - "Import/Export": "Importar/Exportar", + "Import/export": "Importar/Exportar", "unsubscribe": "Desuscribirse", "revoke": "", "Subscriptions": "Suscripciones", "`x` unseen notifications": "`x` notificaciones sin ver", "search": "buscar", - "Sign out": "Cerrar la sesión", + "Log out": "Cerrar la sesión", "Released under the AGPLv3 by Omar Roth.": "Publicado bajo licencia AGPLv3 por Omar Roth.", "Source available here.": "Código fuente disponible aquí.", "View JavaScript license information.": "Ver información de licencia de JavaScript.", "View privacy policy.": "Ver la política de privacidad.", "Trending": "Tendencias", "Unlisted": "No listado", - "Watch video on Youtube": "Ver el vídeo en Youtube", + "Watch on YouTube": "Ver el vídeo en Youtube", "Genre: ": "Género: ", "License: ": "Licencia: ", "Family friendly? ": "¿Filtrar contenidos? ", @@ -124,7 +124,7 @@ "Shared `x`": "Compartido `x`", "`x` views": "`x` visualizaciones", "Premieres in `x`": "Se estrena en `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.", "View YouTube comments": "Ver los comentarios de YouTube", "View more comments on Reddit": "Ver más comentarios en Reddit", "View `x` comments": "Ver `x` comentarios", @@ -133,19 +133,19 @@ "Show replies": "Mostrar las respuestas", "Incorrect password": "Contraseña incorrecta", "Quota exceeded, try again in a few hours": "Cuota excedida, pruebe otra vez en unas horas", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "No se puede iniciar sesión, asegúrese de que la autentificación de dos factores (autentificador o SMS) esté habilitada.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "No se puede iniciar sesión, asegúrese de que la autentificación de dos factores (autentificador o SMS) esté habilitada.", "Invalid TFA code": "Código TFA no válido", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Error de inicio de sesion. Puede deberse a que la autentificación de dos factores no está habilitada en su cuenta.", - "Invalid answer": "Respuesta no válida", - "Invalid CAPTCHA": "CAPTCHA no válido", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Error de inicio de sesion. Puede deberse a que la autentificación de dos factores no está habilitada en su cuenta.", + "Wrong answer": "Respuesta no válida", + "Erroneous CAPTCHA": "CAPTCHA no válido", "CAPTCHA is a required field": "El CAPTCHA es un campo obligatorio", "User ID is a required field": "El nombre es un campo obligatorio", "Password is a required field": "La contraseña es un campo obligatorio", - "Invalid username or password": "Nombre o contraseña incorrecto", - "Please sign in using 'Sign in with Google'": "Inicie sesión con «Iniciar sesión con Google»", + "Wrong username or password": "Nombre o contraseña incorrecto", + "Please sign in using 'Log in with Google'": "Inicie sesión con «Iniciar sesión con Google»", "Password cannot be empty": "La contraseña no puede estar en blanco", "Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres", - "Please sign in": "Inicie sesión, por favor", + "Please log in": "Inicie sesión, por favor", "Invidious Private Feed for `x`": "Fuente privada de Invidious para `x`", "channel:`x`": "canal: `x`", "Deleted or invalid channel": "El canal no es válido o ha sido borrado", @@ -157,15 +157,15 @@ "Load more": "Cargar más", "`x` points": "`x` puntos", "Could not create mix.": "No se ha podido crear la mezcla.", - "Playlist is empty": "La lista de reproducción está vacía", - "Invalid playlist.": "Lista de reproducción no válida.", + "Empty playlist": "La lista de reproducción está vacía", + "Not a playlist.": "Lista de reproducción no válida.", "Playlist does not exist.": "La lista de reproducción no existe.", "Could not pull trending pages.": "No se han podido obtener las páginas de tendencias.", "Hidden field \"challenge\" is a required field": "El campo oculto «desafío» es un campo obligatorio", "Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio", - "Invalid challenge": "Desafío no válido", - "Invalid token": "Símbolo no válido", - "Invalid user": "Usuario no válido", + "Erroneous challenge": "Desafío no válido", + "Erroneous token": "Símbolo no válido", + "No such user": "Usuario no válido", "Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo", "English": "Inglés", "English (auto-generated)": "Inglés (autogenerado)", @@ -234,7 +234,7 @@ "Marathi": "Maratí", "Mongolian": "Mongol", "Nepali": "Nepalí", - "Norwegian": "Noruego", + "Norwegian Bokmål": "Noruego", "Nyanja": "Chichewa", "Pashto": "Pastún", "Persian": "Persa", @@ -296,11 +296,11 @@ "Download as: ": "Descargar como: ", "%A %B %-d, %Y": "%A %B %-d, %Y", "(edited)": "(editado)", - "Youtube permalink of the comment": "Enlace permanente de YouTube del comentario", + "YouTube comment permalink": "Enlace permanente de YouTube del comentario", "`x` marked it with a ❤": "`x` lo ha marcado con un ❤", "Audio mode": "Modo de audio", "Video mode": "Modo de vídeo", "Videos": "Vídeos", "Playlists": "Listas de reproducción", "Current version: ": "Versión actual: " -} +} \ No newline at end of file diff --git a/locales/eu.json b/locales/eu.json index 96e29b46..9abeb684 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "YouTuberako interfaze alternatibo bat", "JavaScript license information": "JavaScript lizentzia informazioa", "source": "iturburua", - "Login": "Saioa hasi", - "Login/Register": "Saioa hasi/Izena eman", - "Login to Google": "Googlekin hasi saioa", + "Log in": "Saioa hasi", + "Log in/register": "Saioa hasi/Izena eman", + "Log in with Google": "Googlekin hasi saioa", "User ID": "Erabiltzaile IDa", "Password": "Pasahitza", "Time (h:mm:ss):": "Denbora (o:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Irudi CAPTCHA", "Sign In": "", "Register": "", - "Email": "", + "E-mail": "", "Google verification code": "", "Preferences": "", "Player preferences": "", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "", "Data preferences": "", "Clear watch history": "", - "Import/Export data": "", + "Import/export data": "", "Manage subscriptions": "", "Manage tokens": "", "Watch history": "", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "", "`x` tokens": "", - "Import/Export": "", + "Import/export": "", "unsubscribe": "", "revoke": "", "Subscriptions": "", "`x` unseen notifications": "", "search": "", - "Sign out": "", + "Log out": "", "Released under the AGPLv3 by Omar Roth.": "", "Source available here.": "", "View JavaScript license information.": "", "View privacy policy.": "", "Trending": "", "Unlisted": "", - "Watch video on Youtube": "", + "Watch on YouTube": "", "Genre: ": "", "License: ": "", "Family friendly? ": "", @@ -124,7 +124,7 @@ "Shared `x`": "", "`x` views": "", "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "", "View YouTube comments": "", "View more comments on Reddit": "", "View `x` comments": "", @@ -133,19 +133,19 @@ "Show replies": "", "Incorrect password": "", "Quota exceeded, try again in a few hours": "", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "", "Invalid TFA code": "", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "", - "Invalid answer": "", - "Invalid CAPTCHA": "", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "", + "Wrong answer": "", + "Erroneous CAPTCHA": "", "CAPTCHA is a required field": "", "User ID is a required field": "", "Password is a required field": "", - "Invalid username or password": "", - "Please sign in using 'Sign in with Google'": "", + "Wrong username or password": "", + "Please sign in using 'Log in with Google'": "", "Password cannot be empty": "", "Password cannot be longer than 55 characters": "", - "Please sign in": "", + "Please log in": "", "Invidious Private Feed for `x`": "", "channel:`x`": "", "Deleted or invalid channel": "", @@ -157,15 +157,15 @@ "Load more": "", "`x` points": "", "Could not create mix.": "", - "Playlist is empty": "", - "Invalid playlist.": "", + "Empty playlist": "", + "Not a playlist.": "", "Playlist does not exist.": "", "Could not pull trending pages.": "", "Hidden field \"challenge\" is a required field": "", "Hidden field \"token\" is a required field": "", - "Invalid challenge": "", - "Invalid token": "", - "Invalid user": "", + "Erroneous challenge": "", + "Erroneous token": "", + "No such user": "", "Token is expired, please try again": "", "English": "", "English (auto-generated)": "", @@ -234,7 +234,7 @@ "Marathi": "", "Mongolian": "", "Nepali": "", - "Norwegian": "", + "Norwegian Bokmål": "", "Nyanja": "", "Pashto": "", "Persian": "", @@ -296,11 +296,9 @@ "Download as: ": "", "%A %B %-d, %Y": "", "(edited)": "", - "Youtube permalink of the comment": "", + "YouTube comment permalink": "", "`x` marked it with a ❤": "", "Audio mode": "", "Video mode": "", - "Videos": "", - "Playlists": "", - "Current version: ": "" -} + "Videos": "" +} \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index bd6beac8..e94c0d1c 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Un front-end alternatif à YouTube", "JavaScript license information": "Informations sur les licences JavaScript", "source": "source", - "Login": "Se connecter", - "Login/Register": "Se connecter/Créer un compte", - "Login to Google": "Se connecter avec Google", + "Log in": "Se connecter", + "Log in/register": "Se connecter/Créer un compte", + "Log in with Google": "Se connecter avec Google", "User ID": "Identifiant utilisateur", "Password": "Mot de passe", "Time (h:mm:ss):": "Heure (h:mm:ss) :", @@ -43,7 +43,7 @@ "Image CAPTCHA": "CAPTCHA Image", "Sign In": "Se connecter", "Register": "S'inscrire", - "Email": "E-mail", + "E-mail": "E-mail", "Google verification code": "Code de vérification Google", "Preferences": "Préférences", "Player preferences": "Préférences du lecteur", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Afficher uniquement les notifications (s'il y en a) : ", "Data preferences": "Préférences liées aux données", "Clear watch history": "Supprimer l'historique des vidéos regardées", - "Import/Export data": "Importer/exporter les données", + "Import/export data": "Importer/exporter les données", "Manage subscriptions": "Gérer les abonnements", "Manage tokens": "", "Watch history": "Historique de visionnage", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` abonnements", "`x` tokens": "", - "Import/Export": "Importer/Exporter", + "Import/export": "Importer/Exporter", "unsubscribe": "se désabonner", "revoke": "", "Subscriptions": "Abonnements", "`x` unseen notifications": "`x` notifications non vues", "search": "Rechercher", - "Sign out": "Déconnexion", + "Log out": "Déconnexion", "Released under the AGPLv3 by Omar Roth.": "Publié sous licence AGPLv3 par Omar Roth.", "Source available here.": "Code Source.", "View JavaScript license information.": "Voir les informations des licences JavaScript.", "View privacy policy.": "Consulter la politique de confidentialité.", "Trending": "Tendances", "Unlisted": "Non répertoriée", - "Watch video on Youtube": "Voir la vidéo sur Youtube", + "Watch on YouTube": "Voir la vidéo sur Youtube", "Genre: ": "Genre : ", "License: ": "Licence : ", "Family friendly? ": "Tout Public ? ", @@ -124,7 +124,7 @@ "Shared `x`": "Ajoutée le `x`", "`x` views": "", "Premieres in `x`": "Première dans `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Il semblerait que JavaScript soit désactivé. Cliquez ici pour voir les commentaires sans. Gardez à l'esprit que le chargement peut prendre plus de temps.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Il semblerait que JavaScript soit désactivé. Cliquez ici pour voir les commentaires sans. Gardez à l'esprit que le chargement peut prendre plus de temps.", "View YouTube comments": "Voir les commentaires YouTube", "View more comments on Reddit": "Voir plus de commentaires sur Reddit", "View `x` comments": "Voir `x` commentaires", @@ -133,19 +133,19 @@ "Show replies": "Afficher les réponses", "Incorrect password": "Mot de passe incorrect", "Quota exceeded, try again in a few hours": "Nombre de tentative de connexion dépassé, réessayez dans quelques heures", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Si vous ne parvenez pas à vous connecter, assurez-vous que l'authentification à deux facteurs (Authenticator ou SMS) est activée.", "Invalid TFA code": "Code d'authentification à deux facteurs invalide", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.", - "Invalid answer": "Réponse invalide", - "Invalid CAPTCHA": "CAPTCHA invalide", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "La connexion a échoué. Cela peut être dû au fait que l'authentification à deux facteurs n'est pas activée sur votre compte.", + "Wrong answer": "Réponse invalide", + "Erroneous CAPTCHA": "CAPTCHA invalide", "CAPTCHA is a required field": "Veuillez entrer un CAPTCHA", "User ID is a required field": "Veuillez entrer un Identifiant Utilisateur", "Password is a required field": "Veuillez entrer un Mot de passe", - "Invalid username or password": "Nom d'utilisateur ou mot de passe invalide", - "Please sign in using 'Sign in with Google'": "Veuillez vous connecter en utilisant \"Se connecter avec Google\"", + "Wrong username or password": "Nom d'utilisateur ou mot de passe invalide", + "Please sign in using 'Log in with Google'": "Veuillez vous connecter en utilisant \"Se connecter avec Google\"", "Password cannot be empty": "Le mot de passe ne peut pas être vide", "Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères", - "Please sign in": "Veuillez vous connecter", + "Please log in": "Veuillez vous connecter", "Invidious Private Feed for `x`": "Flux RSS privé pour `x`", "channel:`x`": "chaîne:`x`", "Deleted or invalid channel": "Chaîne supprimée ou invalide", @@ -157,15 +157,15 @@ "Load more": "Charger plus", "`x` points": "`x` points", "Could not create mix.": "Impossible de charger cette liste de lecture.", - "Playlist is empty": "La liste de lecture est vide", - "Invalid playlist.": "Liste de lecture invalide.", + "Empty playlist": "La liste de lecture est vide", + "Not a playlist.": "Liste de lecture invalide.", "Playlist does not exist.": "La liste de lecture n'existe pas.", "Could not pull trending pages.": "Impossible de charger les pages de tendances.", "Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field", "Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field", - "Invalid challenge": "Invalid challenge", - "Invalid token": "Invalid token", - "Invalid user": "Invalid user", + "Erroneous challenge": "Erroneous challenge", + "Erroneous token": "Erroneous token", + "No such user": "No such user", "Token is expired, please try again": "Token is expired, please try again", "English": "Anglais", "English (auto-generated)": "Anglais (générés automatiquement)", @@ -234,7 +234,7 @@ "Marathi": "Marathi", "Mongolian": "Mongol", "Nepali": "Népalais", - "Norwegian": "Norvégien", + "Norwegian Bokmål": "Norvégien", "Nyanja": "Nyanja", "Pashto": "Pachtou", "Persian": "Persan", @@ -296,11 +296,11 @@ "Download as: ": "Télécharger en : ", "%A %B %-d, %Y": "%A %-d %B %Y", "(edited)": "(modifié)", - "Youtube permalink of the comment": "Lien YouTube permanent vers le commentaire", + "YouTube comment permalink": "Lien YouTube permanent vers le commentaire", "`x` marked it with a ❤": "`x` l'a marqué d'un ❤", "Audio mode": "Mode Audio", "Video mode": "Mode Vidéo", "Videos": "Vidéos", "Playlists": "Liste de lecture", "Current version: ": "Version :" -} +} \ No newline at end of file diff --git a/locales/it.json b/locales/it.json index 66939bd1..05700de8 100644 --- a/locales/it.json +++ b/locales/it.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Un'interfaccia alternativa per YouTube", "JavaScript license information": "Info licenze JavaScript", "source": "sorgente", - "Login": "Entra", - "Login/Register": "Entra/Registrati", - "Login to Google": "Entra con Google", + "Log in": "Entra", + "Log in/register": "Entra/Registrati", + "Log in with Google": "Entra con Google", "User ID": "ID utente", "Password": "Password", "Time (h:mm:ss):": "Orario (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Immagine CAPTCHA", "Sign In": "Entra", "Register": "Registrati", - "Email": "Email", + "E-mail": "Email", "Google verification code": "Codice di verifica Google", "Preferences": "Preferenze", "Player preferences": "Preferenze del riproduttore", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Mostra solo le notifiche (se presenti): ", "Data preferences": "Preferenze dati", "Clear watch history": "Cancella la cronologia dei video guardati", - "Import/Export data": "Importazione/esportazione dati", + "Import/export data": "Importazione/esportazione dati", "Manage subscriptions": "Gestisci le iscrizioni", "Manage tokens": "", "Watch history": "Cronologia dei video", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` iscrizioni", "`x` tokens": "", - "Import/Export": "Importa/esporta", + "Import/export": "Importa/esporta", "unsubscribe": "disiscriviti", "revoke": "", "Subscriptions": "Iscrizioni", "`x` unseen notifications": "`x` notifiche non visualizzate", "search": "Cerca", - "Sign out": "Esci", + "Log out": "Esci", "Released under the AGPLv3 by Omar Roth.": "Pubblicato con licenza AGPLv3 da Omar Roth.", "Source available here.": "Codice sorgente.", "View JavaScript license information.": "Guarda le informazioni di licenza del codice JavaScript.", "View privacy policy.": "", "Trending": "Tendenze", "Unlisted": "", - "Watch video on Youtube": "Guarda il video su YouTube", + "Watch on YouTube": "Guarda il video su YouTube", "Genre: ": "Genere: ", "License: ": "Licenza: ", "Family friendly? ": "Per tutti? ", @@ -124,7 +124,7 @@ "Shared `x`": "Condiviso `x`", "`x` views": "", "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Ciao! Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti. Considera che potrebbe volerci più tempo.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Ciao! Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti. Considera che potrebbe volerci più tempo.", "View YouTube comments": "Visualizza i commenti da YouTube", "View more comments on Reddit": "Visualizza più commenti su Reddit", "View `x` comments": "Visualizza `x` commenti", @@ -133,19 +133,19 @@ "Show replies": "Mostra le risposte", "Incorrect password": "Password sbagliata", "Quota exceeded, try again in a few hours": "Limite superato, prova di nuovo fra qualche ora", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Impossibile autenticarsi, controlla che l'autenticazione in due passaggi (Authenticator o SMS) sia attiva.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Impossibile autenticarsi, controlla che l'autenticazione in due passaggi (Authenticator o SMS) sia attiva.", "Invalid TFA code": "Codice di autenticazione a due fattori non valido", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Login fallito. L'errore potrebbe essere causato dal fatto che la verifica in due passaggi non è attiva sul tuo account.", - "Invalid answer": "Risposta errata", - "Invalid CAPTCHA": "CAPTCHA errato", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Login fallito. L'errore potrebbe essere causato dal fatto che la verifica in due passaggi non è attiva sul tuo account.", + "Wrong answer": "Risposta errata", + "Erroneous CAPTCHA": "CAPTCHA errato", "CAPTCHA is a required field": "Il CAPTCHA è un campo obbligatorio", "User ID is a required field": "L'ID utente è obbligatorio", "Password is a required field": "La password è un campo obbligatorio", - "Invalid username or password": "Nome utente o password errati", - "Please sign in using 'Sign in with Google'": "Per favore accedi con \"Entra con Google\"", + "Wrong username or password": "Nome utente o password errati", + "Please sign in using 'Log in with Google'": "Per favore accedi con \"Entra con Google\"", "Password cannot be empty": "La password non può essere vuota", "Password cannot be longer than 55 characters": "La password non può contenere più di 55 caratteri", - "Please sign in": "Per favore, entra", + "Please log in": "Per favore, entra", "Invidious Private Feed for `x`": "Feed privato Invidious per `x`", "channel:`x`": "canale:`x`", "Deleted or invalid channel": "Canale cancellato o invalido", @@ -157,15 +157,15 @@ "Load more": "Carica altro", "`x` points": "`x` punti", "Could not create mix.": "Impossibile creare il mix.", - "Playlist is empty": "Playlist vuota", - "Invalid playlist.": "Playlist invalida.", + "Empty playlist": "Playlist vuota", + "Not a playlist.": "Playlist invalida.", "Playlist does not exist.": "Playlist inesistente.", "Could not pull trending pages.": "Impossibile recuperare le tendenze.", "Hidden field \"challenge\" is a required field": "Il campo nascosto \"challenge\" è obbligatorio", "Hidden field \"token\" is a required field": "Il campo nascosto \"token\" è obbligatorio", - "Invalid challenge": "Campo \"challenge\" invalido", - "Invalid token": "Campo \"token\" invalido", - "Invalid user": "Utente invalido", + "Erroneous challenge": "Campo \"challenge\" invalido", + "Erroneous token": "Campo \"token\" invalido", + "No such user": "Utente invalido", "Token is expired, please try again": "Token scaduto, riprova", "English": "Inglese", "English (auto-generated)": "Inglese (generati automaticamente)", @@ -234,7 +234,7 @@ "Marathi": "Marathi", "Mongolian": "Mongolo", "Nepali": "Nepalese", - "Norwegian": "Norvegese", + "Norwegian Bokmål": "Norvegese", "Nyanja": "Nyanja", "Pashto": "Lingua pashtu", "Persian": "Persiano", @@ -296,11 +296,11 @@ "Download as: ": "Scarica come: ", "%A %B %-d, %Y": "%A %-d %B %Y", "(edited)": "(modificato)", - "Youtube permalink of the comment": "Link permanente al commento di YouTube", + "YouTube comment permalink": "Link permanente al commento di YouTube", "`x` marked it with a ❤": "`x` l'ha contrassegnato con un ❤", "Audio mode": "Modalità audio", "Video mode": "Modalità video", "Videos": "", "Playlists": "", "Current version: ": "" -} +} \ No newline at end of file diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 99f1b4bc..382a951b 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "En alternativ grenseflate for YouTube", "JavaScript license information": "JavaScript-lisensinformasjon", "source": "kilde", - "Login": "Logg inn", - "Login/Register": "Logg inn/registrer", - "Login to Google": "Logg inn med Google", + "Log in": "Logg inn", + "Log in/register": "Logg inn/registrer", + "Log in with Google": "Logg inn med Google", "User ID": "Bruker-ID", "Password": "Passord", "Time (h:mm:ss):": "Tid (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Bilde-CAPTCHA", "Sign In": "Innlogging", "Register": "Registrer", - "Email": "E-post", + "E-mail": "E-post", "Google verification code": "Google-bekreftelseskode", "Preferences": "Innstillinger", "Player preferences": "Avspillerinnstillinger", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Kun vis merknader (hvis det er noen): ", "Data preferences": "Datainnstillinger", "Clear watch history": "Tøm visningshistorikk", - "Import/Export data": "Importer/eksporter data", + "Import/export data": "Importer/eksporter data", "Manage subscriptions": "Behandle abonnementer", "Manage tokens": "", "Watch history": "Visningshistorikk", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` abonnementer", "`x` tokens": "", - "Import/Export": "Importer/eksporter", + "Import/export": "Importer/eksporter", "unsubscribe": "opphev abonnement", "revoke": "", "Subscriptions": "Abonnement", "`x` unseen notifications": "`x` usette merknader", "search": "søk", - "Sign out": "Logg ut", + "Log out": "Logg ut", "Released under the AGPLv3 by Omar Roth.": "Utgitt med AGPLv3+lisens av Omar Roth.", "Source available here.": "Kildekode tilgjengelig her.", "View JavaScript license information.": "Vis JavaScript-lisensinfo.", "View privacy policy.": "Vis personvernspraksis.", "Trending": "Trendsettende", "Unlisted": "Ulistet", - "Watch video on Youtube": "Vis video på YouTube", + "Watch on YouTube": "Vis video på YouTube", "Genre: ": "Sjanger: ", "License: ": "Lisens: ", "Family friendly? ": "Familievennlig? ", @@ -124,7 +124,7 @@ "Shared `x`": "Delt `x`", "`x` views": "`x` visninger", "Premieres in `x`": "Premiere om `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hei. Det ser ut til at du har JavaScript avslått. Klikk her for å vise kommentarer, ha i minnet at innlasting tar lengre tid.", "View YouTube comments": "Vis YouTube-kommentarer", "View more comments on Reddit": "Vis flere kommenterer på Reddit", "View `x` comments": "Vis `x` kommentarer", @@ -133,19 +133,19 @@ "Show replies": "Vis svar", "Incorrect password": "Feil passord", "Quota exceeded, try again in a few hours": "Kvote overskredet, prøv igjen om et par timer", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Kunne ikke logge inn, forsikre deg om at tofaktor-identitetsbekreftelse (Authenticator eller SMS) er skrudd på.", "Invalid TFA code": "Ugyldig tofaktorkode", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", - "Invalid answer": "Ugyldig svar", - "Invalid CAPTCHA": "Ugyldig CAPTCHA", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Innlogging mislyktes. Dette kan være fordi tofaktor-identitetsbekreftelse er skrudd av på kontoen din.", + "Wrong answer": "Ugyldig svar", + "Erroneous CAPTCHA": "Ugyldig CAPTCHA", "CAPTCHA is a required field": "CAPTCHA er et påkrevd felt", "User ID is a required field": "Bruker-ID er et påkrevd felt", "Password is a required field": "Passord er et påkrevd felt", - "Invalid username or password": "Ugyldig brukernavn eller passord", - "Please sign in using 'Sign in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", + "Wrong username or password": "Ugyldig brukernavn eller passord", + "Please sign in using 'Log in with Google'": "Logg inn ved bruk av \"Google-innlogging\"", "Password cannot be empty": "Passordet kan ikke være tomt", "Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn", - "Please sign in": "Logg inn", + "Please log in": "Logg inn", "Invidious Private Feed for `x`": "Ugyldig privat flyt for `x`", "channel:`x`": "kanal `x`", "Deleted or invalid channel": "Slettet eller ugyldig kanal", @@ -157,15 +157,15 @@ "Load more": "Last inn flere", "`x` points": "`x` poeng", "Could not create mix.": "Kunne ikke opprette miks.", - "Playlist is empty": "Spillelisten er tom", - "Invalid playlist.": "Ugyldig spilleliste.", + "Empty playlist": "Spillelisten er tom", + "Not a playlist.": "Ugyldig spilleliste.", "Playlist does not exist.": "Spillelisten finnes ikke.", "Could not pull trending pages.": "Kunne ikke hente trendsettende sider.", "Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt", "Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt", - "Invalid challenge": "Ugyldig utfordring", - "Invalid token": "Ugyldig symbol", - "Invalid user": "Ugyldig bruker", + "Erroneous challenge": "Ugyldig utfordring", + "Erroneous token": "Ugyldig symbol", + "No such user": "Ugyldig bruker", "Token is expired, please try again": "Symbol utløpt, prøv igjen", "English": "Engelsk", "English (auto-generated)": "Engelsk (auto-generert)", @@ -234,7 +234,7 @@ "Marathi": "", "Mongolian": "", "Nepali": "", - "Norwegian": "Norsk bokmål", + "Norwegian Bokmål": "Norsk bokmål", "Nyanja": "", "Pashto": "", "Persian": "", @@ -296,11 +296,11 @@ "Download as: ": "Last ned som: ", "%A %B %-d, %Y": "", "(edited)": "(redigert)", - "Youtube permalink of the comment": "Permanent YouTube-lenke til innholdet", + "YouTube comment permalink": "Permanent YouTube-lenke til innholdet", "`x` marked it with a ❤": "`x` levnet et ❤", "Audio mode": "Lydmodus", "Video mode": "Video-modus", "Videos": "Videoer", "Playlists": "Spillelister", "Current version: ": "Nåværende versjon: " -} +} \ No newline at end of file diff --git a/locales/nl.json b/locales/nl.json index 51ece697..9d9dac9e 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Een alternatieve front-end voor YouTube", "JavaScript license information": "JavaScript licentie informatie", "source": "bron", - "Login": "Inloggen", - "Login/Register": "Inloggen/Registreren", - "Login to Google": "Inloggen op Google", + "Log in": "Inloggen", + "Log in/register": "Inloggen/Registreren", + "Log in with Google": "Inloggen op Google", "User ID": "Gebruiker ID", "Password": "Wachtwoord", "Time (h:mm:ss):": "Tijd (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Afbeelding CAPTCHA", "Sign In": "Aanmelden", "Register": "Registreren", - "Email": "Email", + "E-mail": "Email", "Google verification code": "Google verificatie code", "Preferences": "Voorkeuren", "Player preferences": "Afspeler voorkeuren", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Laat alleen notificaties zien (als die er zijn): ", "Data preferences": "Gegevens voorkeuren", "Clear watch history": "Kijkgeschiedenis wissen", - "Import/Export data": "Importeer/Exporteer gegevens", + "Import/export data": "Importeer/Exporteer gegevens", "Manage subscriptions": "Abonnees beheren", "Manage tokens": "", "Watch history": "Kijkgeschiedenis", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` abonnees", "`x` tokens": "", - "Import/Export": "Importeer/Exporteer", + "Import/export": "Importeer/Exporteer", "unsubscribe": "abonnement opzeggen", "revoke": "", "Subscriptions": "Abonnees", "`x` unseen notifications": "`x` onbekeken notificaties", "search": "zoeken", - "Sign out": "Afmelden", + "Log out": "Afmelden", "Released under the AGPLv3 by Omar Roth.": "Uitgegeven onder AGPLv3 door Omar Roth.", "Source available here.": "Bron beschikbaar hier.", "View JavaScript license information.": "Bekijk JavaScript licentie informatie.", "View privacy policy.": "", "Trending": "Trending", "Unlisted": "", - "Watch video on Youtube": "Bekijk video op Youtube", + "Watch on YouTube": "Bekijk video op Youtube", "Genre: ": "Genre: ", "License: ": "Licentie: ", "Family friendly? ": "Gezinsvriendelijk? ", @@ -124,7 +124,7 @@ "Shared `x`": "`x` gedeeld", "`x` views": "", "Premieres in `x`": "", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Hoi! Het lijkt erop dat je JavaScript uit hebt staan. Klik hier om de reacties te bekijken, hou er rekening mee dat het wat langer duurt om te laden.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hoi! Het lijkt erop dat je JavaScript uit hebt staan. Klik hier om de reacties te bekijken, hou er rekening mee dat het wat langer duurt om te laden.", "View YouTube comments": "Bekijk YouTube reacties", "View more comments on Reddit": "Bekijk meer reacties op Reddit", "View `x` comments": "`x` reacties zien", @@ -133,19 +133,19 @@ "Show replies": "Laat antwoorden zien", "Incorrect password": "Onjuist wachtwoord", "Quota exceeded, try again in a few hours": "Quota overschreden, probeer het over een paar uur opnieuw", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Niet in staat om in te loggen, zorg ervoor dat two-factor authentication (Authenticator of SMS) is ingeschakeld.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Niet in staat om in te loggen, zorg ervoor dat two-factor authentication (Authenticator of SMS) is ingeschakeld.", "Invalid TFA code": "Onjuiste TFA code", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Aanmelden mislukt. Dit kan zijn omdat two-factor authentication niet is ingeschakeld voor uw account.", - "Invalid answer": "Onjuist antwoord", - "Invalid CAPTCHA": "Onjuiste CAPTCHA", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Aanmelden mislukt. Dit kan zijn omdat two-factor authentication niet is ingeschakeld voor uw account.", + "Wrong answer": "Onjuist antwoord", + "Erroneous CAPTCHA": "Onjuiste CAPTCHA", "CAPTCHA is a required field": "CAPTCHA is een vereist veld", "User ID is a required field": "Gebruiker ID is een vereist veld", "Password is a required field": "Wachtwoord is een vereist veld", - "Invalid username or password": "Ongeldige gebruikersnaam of wachtwoord", - "Please sign in using 'Sign in with Google'": "Meld u aan met 'Aanmelden met Google'", + "Wrong username or password": "Ongeldige gebruikersnaam of wachtwoord", + "Please sign in using 'Log in with Google'": "Meld u aan met 'Aanmelden met Google'", "Password cannot be empty": "Wachtwoord mag niet leeg zijn", "Password cannot be longer than 55 characters": "Wachtwoord mag niet langer dan 55 tekens zijn", - "Please sign in": "Meld u aan", + "Please log in": "Meld u aan", "Invidious Private Feed for `x`": "Invidious Privé Feed voor `x`", "channel:`x`": "kanaal:`x`", "Deleted or invalid channel": "Verwijderd of ongeldig kanaal", @@ -157,15 +157,15 @@ "Load more": "Meer laden", "`x` points": "`x` punten", "Could not create mix.": "Kon mix niet maken.", - "Playlist is empty": "Afspeellijst is leeg", - "Invalid playlist.": "Ongeldige afspeellijst.", + "Empty playlist": "Afspeellijst is leeg", + "Not a playlist.": "Ongeldige afspeellijst.", "Playlist does not exist.": "Afspeellijst bestaat niet.", "Could not pull trending pages.": "Kon trending paginas niet verkrijgen.", "Hidden field \"challenge\" is a required field": "Verborgen veld \"uitdaging\" is een vereist veld", "Hidden field \"token\" is a required field": "Verborgen veld \"token\" is een vereist veld", - "Invalid challenge": "Ongeldige uitdaging", - "Invalid token": "Ongeldige token", - "Invalid user": "Ongeldige gebruiker", + "Erroneous challenge": "Ongeldige uitdaging", + "Erroneous token": "Ongeldige token", + "No such user": "Ongeldige gebruiker", "Token is expired, please try again": "Token is verlopen, probeer het opnieuw", "English": "", "English (auto-generated)": "", @@ -234,7 +234,7 @@ "Marathi": "", "Mongolian": "", "Nepali": "", - "Norwegian": "", + "Norwegian Bokmål": "", "Nyanja": "", "Pashto": "", "Persian": "", @@ -296,11 +296,11 @@ "Download as: ": "", "%A %B %-d, %Y": "", "(edited)": "", - "Youtube permalink of the comment": "", + "YouTube comment permalink": "", "`x` marked it with a ❤": "", "Audio mode": "", "Video mode": "", "Videos": "", "Playlists": "", "Current version: ": "" -} +} \ No newline at end of file diff --git a/locales/pl.json b/locales/pl.json index 618fa312..d970f8c9 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Alternatywny front-end dla YouTube", "JavaScript license information": "Informacja o licencji JavaScript", "source": "źródło", - "Login": "Zaloguj", - "Login/Register": "Zaloguj/Zarejestruj", - "Login to Google": "Zaloguj do Google", + "Log in": "Zaloguj", + "Log in/register": "Zaloguj/Zarejestruj", + "Log in with Google": "Zaloguj do Google", "User ID": "ID użytkownika", "Password": "Hasło", "Time (h:mm:ss):": "Godzina (h:mm:ss):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Obraz CAPTCHA", "Sign In": "Zaloguj się", "Register": "Zarejestruj się", - "Email": "Email", + "E-mail": "Email", "Google verification code": "Kod weryfikacyjny Google", "Preferences": "Preferencje", "Player preferences": "Ustawienia odtwarzacza", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Pokazuj tylko powiadomienia (jeśli są): ", "Data preferences": "Preferencje danych", "Clear watch history": "Wyczyść historię", - "Import/Export data": "Import/Eksport danych", + "Import/export data": "Import/Eksport danych", "Manage subscriptions": "Organizuj subskrybcje", "Manage tokens": "", "Watch history": "Historia", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` subskrybcji", "`x` tokens": "", - "Import/Export": "Import/Eksport", + "Import/export": "Import/Eksport", "unsubscribe": "odsubskrybuj", "revoke": "", "Subscriptions": "Subskrybcje", "`x` unseen notifications": "`x` nowych powiadomień", "search": "szukaj", - "Sign out": "Wyloguj", + "Log out": "Wyloguj", "Released under the AGPLv3 by Omar Roth.": "Wydano na licencji AGPLv3 przez Omar Roth.", "Source available here.": "Kod źródłowy dostępny tutaj.", "View JavaScript license information.": "Wyświetl informację o licencji JavaScript.", "View privacy policy.": "Polityka prywatności.", "Trending": "Na czasie", "Unlisted": "", - "Watch video on Youtube": "Zobacz film na YouTube", + "Watch on YouTube": "Zobacz film na YouTube", "Genre: ": "Gatunek: ", "License: ": "Licencja: ", "Family friendly? ": "Przyjazny rodzinie? ", @@ -124,7 +124,7 @@ "Shared `x`": "Udostępniono `x`", "`x` views": "`x` wyświetleń", "Premieres in `x`": "Publikacja za `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Cześć! Wygląda na to, że masz wyłączoną obsługę JavaScriptu. Kliknij tutaj, żeby zobaczyć komentarze. Pamiętaj, że wczytywanie może potrwać dłużej.", "View YouTube comments": "Wyświetl komentarze z YouTube", "View more comments on Reddit": "Wyświetl więcej komentarzy na Reddicie", "View `x` comments": "Wyświetl `x` komentarzy", @@ -133,19 +133,19 @@ "Show replies": "Pokaż odpowiedzi", "Incorrect password": "Niepoprawne hasło", "Quota exceeded, try again in a few hours": "Przekroczony limit zapytań, spróbuj ponownie za kilka godzin", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Nie udało się zalogować, upewnij się, że dwuetapowe uwierzytelnianie (Autentykator lub SMS) jest aktywne.", "Invalid TFA code": "Niepoprawny kod TFA", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.", - "Invalid answer": "Niepoprawna odpowiedź", - "Invalid CAPTCHA": "CAPTCHA wykonane błędnie", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Nie udało się zalogować. To może być spowodowane wyłączoną dwustopniową autoryzacją na twoim koncie.", + "Wrong answer": "Niepoprawna odpowiedź", + "Erroneous CAPTCHA": "CAPTCHA wykonane błędnie", "CAPTCHA is a required field": "CAPTCHA jest polem wymaganym", "User ID is a required field": "ID użytkownika jest polem wymaganym", "Password is a required field": "Hasło jest polem wymaganym", - "Invalid username or password": "Niepoprawny login lub hasło", - "Please sign in using 'Sign in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"", + "Wrong username or password": "Niepoprawny login lub hasło", + "Please sign in using 'Log in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"", "Password cannot be empty": "Hasło nie może być puste", "Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków", - "Please sign in": "Proszę się zalogować", + "Please log in": "Proszę się zalogować", "Invidious Private Feed for `x`": "", "channel:`x`": "kanał:`x", "Deleted or invalid channel": "Usunięty lub niepoprawny kanał", @@ -157,15 +157,15 @@ "Load more": "Wczytaj więcej", "`x` points": "`x` punktów", "Could not create mix.": "Nie udało się utworzyć miksu.", - "Playlist is empty": "Lista odtwarzania jest pusta", - "Invalid playlist.": "Niepoprawna lista.", + "Empty playlist": "Lista odtwarzania jest pusta", + "Not a playlist.": "Niepoprawna lista.", "Playlist does not exist.": "Lista odtwarzania nie istnieje.", "Could not pull trending pages.": "Nie udało się pobrać strony na czasie.", "Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym", "Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym", - "Invalid challenge": "Niepoprawne wyzwanie", - "Invalid token": "Niepoprawny token", - "Invalid user": "Niepoprawny użytkownik", + "Erroneous challenge": "Niepoprawne wyzwanie", + "Erroneous token": "Niepoprawny token", + "No such user": "Niepoprawny użytkownik", "Token is expired, please try again": "Token wygasł, spróbuj ponownie", "English": "angielski", "English (auto-generated)": "angielski (automatycznie generowane)", @@ -234,7 +234,7 @@ "Marathi": "marathi", "Mongolian": "mongolski", "Nepali": "nepalski", - "Norwegian": "norweski", + "Norwegian Bokmål": "norweski", "Nyanja": "njandża", "Pashto": "paszto", "Persian": "perski", @@ -296,11 +296,11 @@ "Download as: ": "Pobierz jako: ", "%A %B %-d, %Y": "", "(edited)": "(edytowany)", - "Youtube permalink of the comment": "Odnośnik bezpośredni do komentarza na YouTube", + "YouTube comment permalink": "Odnośnik bezpośredni do komentarza na YouTube", "`x` marked it with a ❤": "'x' oznaczonych ❤", "Audio mode": "Tryb audio", "Video mode": "Tryb wideo", "Videos": "Filmy", "Playlists": "Playlisty", "Current version: ": "Aktualna wersja: " -} +} \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index f64578af..49a94436 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", "JavaScript license information": "Лицензии JavaScript", "source": "источник", - "Login": "Войти", - "Login/Register": "Войти/Регистрация", - "Login to Google": "Войти через Google", + "Log in": "Войти", + "Log in/register": "Войти/Регистрация", + "Log in with Google": "Войти через Google", "User ID": "ID пользователя", "Password": "Пароль", "Time (h:mm:ss):": "Время (ч:мм:сс):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Изображение капчи", "Sign In": "Войти", "Register": "Регистрация", - "Email": "Эл. почта", + "E-mail": "Эл. почта", "Google verification code": "Код подтверждения Google", "Preferences": "Настройки", "Player preferences": "Настройки проигрывателя", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Отображать только оповещения (если есть): ", "Data preferences": "Настройки данных", "Clear watch history": "Очистить историю просмотра", - "Import/Export data": "Импорт/Экспорт данных", + "Import/export data": "Импорт/Экспорт данных", "Manage subscriptions": "Управление подписками", "Manage tokens": "", "Watch history": "История просмотров", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` подписок", "`x` tokens": "", - "Import/Export": "Импорт/Экспорт", + "Import/export": "Импорт/Экспорт", "unsubscribe": "отписаться", "revoke": "", "Subscriptions": "Подписки", "`x` unseen notifications": "`x` новых оповещений", "search": "поиск", - "Sign out": "Выйти", + "Log out": "Выйти", "Released under the AGPLv3 by Omar Roth.": "Распространяется Omar Roth по AGPLv3.", "Source available here.": "Исходный код доступен здесь.", "View JavaScript license information.": "Посмотреть лицензии JavaScript кода.", "View privacy policy.": "См. политику конфиденциальности.", "Trending": "В тренде", "Unlisted": "Доступно по ссылке", - "Watch video on Youtube": "Смотреть на YouTube", + "Watch on YouTube": "Смотреть на YouTube", "Genre: ": "Жанр: ", "License: ": "Лицензия: ", "Family friendly? ": "Семейный просмотр: ", @@ -124,7 +124,7 @@ "Shared `x`": "Опубликовано `x`", "`x` views": "`x` просмотров / просмотр / просмотра", "Premieres in `x`": "Премьера через `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Похоже, что у Вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии (учтите, что они могут загружаться дольше).", "View YouTube comments": "Смотреть комментарии с YouTube", "View more comments on Reddit": "Больше комментариев на Reddit", "View `x` comments": "Показать `x` комментариев", @@ -133,19 +133,19 @@ "Show replies": "Показать ответы", "Incorrect password": "Неправильный пароль", "Quota exceeded, try again in a few hours": "Превышена квота, попробуйте снова через несколько часов", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Вход не выполнен, проверьте, не включена ли двухфакторная аутентификация.", "Invalid TFA code": "Неправильный TFA код", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", - "Invalid answer": "Неверный ответ", - "Invalid CAPTCHA": "Неверная капча", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Не удалось войти. Это может быть из-за того, что в вашем аккаунте не включена двухфакторная аутентификация.", + "Wrong answer": "Неверный ответ", + "Erroneous CAPTCHA": "Неверная капча", "CAPTCHA is a required field": "Необходимо ввести капчу", "User ID is a required field": "Необходимо ввести идентификатор пользователя", "Password is a required field": "Необходимо ввести пароль", - "Invalid username or password": "Недопустимый пароль или имя пользователя", - "Please sign in using 'Sign in with Google'": "Пожалуйста войдите через Google", + "Wrong username or password": "Недопустимый пароль или имя пользователя", + "Please sign in using 'Log in with Google'": "Пожалуйста войдите через Google", "Password cannot be empty": "Пароль не может быть пустым", "Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов", - "Please sign in": "Пожалуйста, войдите", + "Please log in": "Пожалуйста, войдите", "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", "channel:`x`": "канал: `x`", "Deleted or invalid channel": "Канал удален или не найден", @@ -157,15 +157,15 @@ "Load more": "Загрузить больше", "`x` points": "`x` очков", "Could not create mix.": "Невозможно создать \"микс\".", - "Playlist is empty": "Плейлист пуст", - "Invalid playlist.": "Некорректный плейлист.", + "Empty playlist": "Плейлист пуст", + "Not a playlist.": "Некорректный плейлист.", "Playlist does not exist.": "Плейлист не существует.", "Could not pull trending pages.": "Невозможно получить страницы \"в тренде\".", "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле \"challenge\"", "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле \"токен\"", - "Invalid challenge": "Неправильный ответ в \"challenge\"", - "Invalid token": "Неправильный токен", - "Invalid user": "Недопустимое имя пользователя", + "Erroneous challenge": "Неправильный ответ в \"challenge\"", + "Erroneous token": "Неправильный токен", + "No such user": "Недопустимое имя пользователя", "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", "English": "Английский", "English (auto-generated)": "Английский (созданы автоматически)", @@ -234,7 +234,7 @@ "Marathi": "Маратхи", "Mongolian": "Монгольская", "Nepali": "Непальский", - "Norwegian": "Норвежский", + "Norwegian Bokmål": "Норвежский", "Nyanja": "Ньянджа", "Pashto": "Пушту", "Persian": "Персидский", @@ -296,11 +296,11 @@ "Download as: ": "Скачать как: ", "%A %B %-d, %Y": "%-d %B %Y, %A", "(edited)": "(изменено)", - "Youtube permalink of the comment": "Прямая ссылка на YouTube", + "YouTube comment permalink": "Прямая ссылка на YouTube", "`x` marked it with a ❤": "❤ от автора канала \"`x`\"", "Audio mode": "Аудио режим", "Video mode": "Видео режим", "Videos": "Видео", "Playlists": "Плейлисты", "Current version: ": "Текущая версия: " -} +} \ No newline at end of file diff --git a/locales/uk.json b/locales/uk.json index 54bb9a55..023db237 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -33,9 +33,9 @@ "An alternative front-end to YouTube": "Альтернативний фронтенд до YouTube", "JavaScript license information": "Інформація щодо ліцензій JavaScript", "source": "джерело", - "Login": "Увійти", - "Login/Register": "Увійти або зареєструватися", - "Login to Google": "Увійти через Google", + "Log in": "Увійти", + "Log in/register": "Увійти або зареєструватися", + "Log in with Google": "Увійти через Google", "User ID": "ID користувача", "Password": "Пароль", "Time (h:mm:ss):": "Час (г:мм:сс):", @@ -43,7 +43,7 @@ "Image CAPTCHA": "Зображення капчі", "Sign In": "Увійти", "Register": "Зареєструватися", - "Email": "Електронна пошта", + "E-mail": "Електронна пошта", "Google verification code": "Код підтвердження Google", "Preferences": "Налаштування", "Player preferences": "Налаштування програвача", @@ -81,7 +81,7 @@ "Only show notifications (if there are any): ": "Показувати лише сповіщення, якщо вони є: ", "Data preferences": "Налаштування даних", "Clear watch history": "Очистити історію переглядів", - "Import/Export data": "Імпорт і експорт даних", + "Import/export data": "Імпорт і експорт даних", "Manage subscriptions": "Керування підписками", "Manage tokens": "", "Watch history": "Історія переглядів", @@ -100,20 +100,20 @@ "Token": "", "`x` subscriptions": "`x` підписка / підписок / підписки", "`x` tokens": "", - "Import/Export": "Імпорт і експорт", + "Import/export": "Імпорт і експорт", "unsubscribe": "відписатися", "revoke": "", "Subscriptions": "Підписки", "`x` unseen notifications": "`x` непереглянуте сповіщення / непереглянутих сповіщень / непереглянутих сповіщення", "search": "пошук", - "Sign out": "Вийти", + "Log out": "Вийти", "Released under the AGPLv3 by Omar Roth.": "Реалізовано Омаром Ротом за ліцензією AGPLv3.", "Source available here.": "Програмний код доступний тут.", "View JavaScript license information.": "Переглянути інформацію щодо ліцензії JavaScript.", "View privacy policy.": "Переглянути політику приватності.", "Trending": "У тренді", "Unlisted": "Відсутнє у листі", - "Watch video on Youtube": "Дивитися відео на YouTube", + "Watch on YouTube": "Дивитися відео на YouTube", "Genre: ": "Жанр: ", "License: ": "Ліцензія: ", "Family friendly? ": "Перегляд із родиною? ", @@ -124,7 +124,7 @@ "Shared `x`": "Розміщено `x`", "`x` views": "", "Premieres in `x`": "Прем’єра через `x`", - "Hi! Looks like you have JavaScript disabled. Click here to view comments, keep in mind it may take a bit longer to load.": "Схоже, у вас відключений JavaScript. Щоб побачити коментарі, натисніть сюда, але майте на увазі, що вони можуть завантажуватися трохи довше.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Схоже, у вас відключений JavaScript. Щоб побачити коментарі, натисніть сюда, але майте на увазі, що вони можуть завантажуватися трохи довше.", "View YouTube comments": "Переглянути коментарі з YouTube", "View more comments on Reddit": "Переглянути більше коментарів на Reddit", "View `x` comments": "Переглянути `x` коментар / коментарів / коментаря", @@ -133,19 +133,19 @@ "Show replies": "Показати відповіді", "Incorrect password": "Неправильний пароль", "Quota exceeded, try again in a few hours": "Ліміт перевищено, спробуйте знову за декілька годин", - "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled.": "Не вдається увійти. Перевірте, чи не ввімкнена двофакторна аутентифікація (за кодом чи смс).", + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Не вдається увійти. Перевірте, чи не ввімкнена двофакторна аутентифікація (за кодом чи смс).", "Invalid TFA code": "Неправильний код двофакторної аутентифікації", - "Login failed. This may be because two-factor authentication is not enabled on your account.": "Не вдається увійти. Це може бути через те, що у вашій обліківці не ввімкнена двофакторна аутентифікація.", - "Invalid answer": "Неправильна відповідь", - "Invalid CAPTCHA": "Неправильна капча", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Не вдається увійти. Це може бути через те, що у вашій обліківці не ввімкнена двофакторна аутентифікація.", + "Wrong answer": "Неправильна відповідь", + "Erroneous CAPTCHA": "Неправильна капча", "CAPTCHA is a required field": "Необхідно пройти капчу", "User ID is a required field": "Необхідно ввести ID користувача", "Password is a required field": "Необхідно ввести пароль", - "Invalid username or password": "Неправильний логін чи пароль", - "Please sign in using 'Sign in with Google'": "Будь ласка, натисніть «Увійдіть через Google»", + "Wrong username or password": "Неправильний логін чи пароль", + "Please sign in using 'Log in with Google'": "Будь ласка, натисніть «Увійдіть через Google»", "Password cannot be empty": "Пароль не може бути порожнім", "Password cannot be longer than 55 characters": "Пароль не може бути довшим за 55 знаків", - "Please sign in": "Будь ласка, увійдіть", + "Please log in": "Будь ласка, увійдіть", "Invidious Private Feed for `x`": "Приватний поток відео Invidious для `x`", "channel:`x`": "канал: `x`", "Deleted or invalid channel": "Канал видалено або не знайдено", @@ -157,15 +157,15 @@ "Load more": "Завантажити більше", "`x` points": "`x` очко / очок / очка", "Could not create mix.": "Не вдається створити мікс.", - "Playlist is empty": "Плейлист порожній", - "Invalid playlist.": "Недійсний плейлист.", + "Empty playlist": "Плейлист порожній", + "Not a playlist.": "Недійсний плейлист.", "Playlist does not exist.": "Плейлист не існує.", "Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».", "Hidden field \"challenge\" is a required field": "Необхідно заповнити приховане поле «challenge»", "Hidden field \"token\" is a required field": "Необхідно заповнити приховане поле «token»", - "Invalid challenge": "Неправильна відповідь у «challenge»", - "Invalid token": "Недійсний токен", - "Invalid user": "Недопустиме ім’я користувача", + "Erroneous challenge": "Неправильна відповідь у «challenge»", + "Erroneous token": "Недійсний токен", + "No such user": "Недопустиме ім’я користувача", "Token is expired, please try again": "Термін дії токена закінчився, спробуйте пізніше", "English": "Англійська", "English (auto-generated)": "Англійська (сгенеровано автоматично)", @@ -234,7 +234,7 @@ "Marathi": "Маратхі", "Mongolian": "Монгольська", "Nepali": "Непальська", - "Norwegian": "Норвезька", + "Norwegian Bokmål": "Норвезька", "Nyanja": "Ньянджа", "Pashto": "Пушту", "Persian": "Перська", @@ -296,11 +296,11 @@ "Download as: ": "Завантажити як: ", "%A %B %-d, %Y": "%-d %B %Y, %A", "(edited)": "(змінено)", - "Youtube permalink of the comment": "Пряме посилання на коментар в YouTube", + "YouTube comment permalink": "Пряме посилання на коментар в YouTube", "`x` marked it with a ❤": "❤ цьому від каналу `x`", "Audio mode": "Аудіорежим", "Video mode": "Відеорежим", "Videos": "Відео", "Playlists": "Плейлисти", "Current version: ": "Поточна версія: " -} +} \ No newline at end of file diff --git a/src/invidious.cr b/src/invidious.cr index 57db7e28..878f9d4c 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -944,7 +944,7 @@ post "/login" do |env| # Voice or text message tfa_req = %(["#{user_hash}",null,2,null,[9,null,null,null,null,null,null,null,[null,"#{tfa_code}",false,2]]]) else - error_message = "Unable to login, make sure two-factor authentication (Authenticator or SMS) is enabled." + error_message = translate(locale, "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.") next templated "error" end @@ -1010,7 +1010,7 @@ post "/login" do |env| env.redirect referer rescue ex - error_message = translate(locale, "Login failed. This may be because two-factor authentication is not enabled on your account.") + error_message = translate(locale, "Login failed. This may be because two-factor authentication is not turned on for your account.") next templated "error" end when "invidious" @@ -1028,7 +1028,7 @@ post "/login" do |env| if user if !user.password - error_message = translate(locale, "Please sign in using 'Sign in with Google'") + error_message = translate(locale, "Please sign in using 'Log in with Google'") next templated "error" end @@ -1050,7 +1050,7 @@ post "/login" do |env| secure: secure, http_only: true) end else - error_message = translate(locale, "Invalid username or password") + error_message = translate(locale, "Wrong username or password") next templated "error" end @@ -1111,7 +1111,7 @@ post "/login" do |env| found_valid_captcha = false - error_message = translate(locale, "Invalid CAPTCHA") + error_message = translate(locale, "Erroneous CAPTCHA") tokens.each_with_index do |token, i| begin validate_request(token, answer, env.request, HMAC_KEY, PG_DB, locale) diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 645ca2a8..e1d8604d 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -319,7 +319,7 @@ def template_youtube_comments(comments, locale, thin_mode)

#{child["contentHtml"]}

#{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""} | - [YT] + [YT] | #{number_with_separator(child["likeCount"])} END_HTML diff --git a/src/invidious/helpers/tokens.cr b/src/invidious/helpers/tokens.cr index 6841127a..ba41cba3 100644 --- a/src/invidious/helpers/tokens.cr +++ b/src/invidious/helpers/tokens.cr @@ -81,14 +81,14 @@ def validate_request(token, session, request, key, db, locale = nil) end if token["session"] != session - raise translate(locale, "Invalid token") + raise translate(locale, "Erroneous token") end if token["nonce"]? && (nonce = db.query_one?("SELECT * FROM nonces WHERE nonce = $1", token["nonce"], as: {String, Time})) if nonce[1] > Time.now db.exec("UPDATE nonces SET expire = $1 WHERE nonce = $2", Time.new(1990, 1, 1), nonce[0]) else - raise translate(locale, "Invalid token") + raise translate(locale, "Erroneous token") end end diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 73c9ac69..1279486e 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -49,7 +49,7 @@ def fetch_playlist_videos(plid, page, video_count, continuation = nil, locale = response = client.get(url) response = JSON.parse(response.body) if !response["content_html"]? || response["content_html"].as_s.empty? - raise translate(locale, "Playlist is empty") + raise translate(locale, "Empty playlist") end document = XML.parse_html(response["content_html"].as_s) @@ -174,7 +174,7 @@ def fetch_playlist(plid, locale) response = client.get("/playlist?list=#{plid}&hl=en&disable_polymer=1") if response.status_code != 200 - raise translate(locale, "Invalid playlist.") + raise translate(locale, "Not a playlist.") end body = response.body.gsub(/]+>]+>\s*less\s*]+>\n<\/span><\/button>/, "") diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 2ad6cdaa..3bd30af5 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -67,7 +67,7 @@ CAPTION_LANGUAGES = { "Marathi", "Mongolian", "Nepali", - "Norwegian", + "Norwegian Bokmål", "Nyanja", "Pashto", "Persian", diff --git a/src/invidious/views/login.ecr b/src/invidious/views/login.ecr index f6a131f9..3acb2501 100644 --- a/src/invidious/views/login.ecr +++ b/src/invidious/views/login.ecr @@ -1,5 +1,5 @@ <% content_for "header" do %> -<%= translate(locale, "Login") %> - Invidious +<%= translate(locale, "Log in") %> - Invidious <% end %>
@@ -9,12 +9,12 @@ @@ -88,8 +88,8 @@ <% if email %> <% else %> - - "> + + "> <% end %> <% if password %> diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr index 3984a4ab..1af53488 100644 --- a/src/invidious/views/preferences.ecr +++ b/src/invidious/views/preferences.ecr @@ -214,7 +214,7 @@ function update_value(element) {
diff --git a/src/invidious/views/subscription_manager.ecr b/src/invidious/views/subscription_manager.ecr index 54fbb70e..ad572968 100644 --- a/src/invidious/views/subscription_manager.ecr +++ b/src/invidious/views/subscription_manager.ecr @@ -15,7 +15,7 @@
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index 89780ef7..1b8fc292 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -72,7 +72,7 @@
" method="post"> "> - "> + ">
@@ -94,7 +94,7 @@ <% if config.login_enabled %> <% end %> diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index accbca52..fb5186c5 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -55,7 +55,7 @@
-

<%= translate(locale, "Watch video on Youtube") %>

+

<%= translate(locale, "Watch on YouTube") %>

<% if CONFIG.dmca_content.includes? video.id %>

Download is disabled.

@@ -146,7 +146,7 @@ <% else %> <% end %> From 8614ff40df1d40fce5b4006e3064001654a44fd7 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 19 Apr 2019 11:20:18 -0500 Subject: [PATCH 084/210] Add support for Ukranian and Esperanto --- src/invidious.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/invidious.cr b/src/invidious.cr index 878f9d4c..bfcca9ca 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -62,6 +62,7 @@ LOCALES = { "ar" => load_locale("ar"), "de" => load_locale("de"), "en-US" => load_locale("en-US"), + "eo" => load_locale("eo"), "es" => load_locale("es"), "eu" => load_locale("eu"), "fr" => load_locale("fr"), @@ -70,6 +71,7 @@ LOCALES = { "nl" => load_locale("nl"), "pl" => load_locale("pl"), "ru" => load_locale("ru"), + "uk" => load_locale("uk"), } config = CONFIG From fb7068d415f422ae2ffbefc66c2d11aff855eac6 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 10 Apr 2019 17:58:42 -0500 Subject: [PATCH 085/210] Add '/api/v1/notifications' --- src/invidious.cr | 266 ++++++++---------------------- src/invidious/channels.cr | 43 +++-- src/invidious/helpers/handlers.cr | 12 +- src/invidious/videos.cr | 182 ++++++++++++++++++++ 4 files changed, 288 insertions(+), 215 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index bfcca9ca..66ed4512 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2625,12 +2625,14 @@ get "/feed/webhook/:token" do |env| end post "/feed/webhook/:token" do |env| + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + token = env.params.url["token"] body = env.request.body.not_nil!.gets_to_end signature = env.request.headers["X-Hub-Signature"].lchop("sha1=") if signature != OpenSSL::HMAC.hexdigest(:sha1, HMAC_KEY, body) - logger.write("#{token} : Invalid signature") + logger.write("#{token} : Invalid signature\n") env.response.status_code = 200 next end @@ -2644,7 +2646,25 @@ post "/feed/webhook/:token" do |env| updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) video = get_video(id, PG_DB, proxies, region: nil) - video = ChannelVideo.new(id, video.title, published, updated, video.ucid, author, video.length_seconds, video.live_now, video.premiere_timestamp) + + # Deliver notifications to `/api/v1/auth/notifications` + payload = { + "key" => video.id, + "topic" => video.ucid, + }.to_json + PG_DB.exec("NOTIFY notifications, E'#{payload}'") + + video = ChannelVideo.new( + id: id, + title: video.title, + published: published, + updated: updated, + ucid: video.ucid, + author: author, + length_seconds: video.length_seconds, + live_now: video.live_now, + premiere_timestamp: video.premiere_timestamp, + ) PG_DB.exec("UPDATE users SET notifications = notifications || $1 \ WHERE updated < $2 AND $3 = ANY(subscriptions) AND $1 <> ALL(notifications)", video.id, video.published, video.ucid) @@ -3184,197 +3204,7 @@ get "/api/v1/videos/:id" do |env| next error_message end - fmt_stream = video.fmt_stream(decrypt_function) - adaptive_fmts = video.adaptive_fmts(decrypt_function) - - captions = video.captions - - video_info = JSON.build do |json| - json.object do - json.field "title", video.title - json.field "videoId", video.id - json.field "videoThumbnails" do - generate_thumbnails(json, video.id, config, Kemal.config) - end - json.field "storyboards" do - generate_storyboards(json, video.storyboards, config, Kemal.config) - end - - video.description, description = html_to_content(video.description) - - json.field "description", description - json.field "descriptionHtml", video.description - json.field "published", video.published.to_unix - json.field "publishedText", translate(locale, "`x` ago", recode_date(video.published, locale)) - json.field "keywords", video.keywords - - json.field "viewCount", video.views - json.field "likeCount", video.likes - json.field "dislikeCount", video.dislikes - - json.field "paid", video.paid - json.field "premium", video.premium - json.field "isFamilyFriendly", video.is_family_friendly - json.field "allowedRegions", video.allowed_regions - json.field "genre", video.genre - json.field "genreUrl", video.genre_url - - json.field "author", video.author - json.field "authorId", video.ucid - json.field "authorUrl", "/channel/#{video.ucid}" - - json.field "authorThumbnails" do - json.array do - qualities = {32, 48, 76, 100, 176, 512} - - qualities.each do |quality| - json.object do - json.field "url", video.author_thumbnail.gsub("=s48-", "=s#{quality}-") - json.field "width", quality - json.field "height", quality - end - end - end - end - - json.field "subCountText", video.sub_count_text - - json.field "lengthSeconds", video.info["length_seconds"].to_i - json.field "allowRatings", video.allow_ratings - json.field "rating", video.info["avg_rating"].to_f32 - json.field "isListed", video.is_listed - json.field "liveNow", video.live_now - json.field "isUpcoming", video.is_upcoming - - if video.premiere_timestamp - json.field "premiereTimestamp", video.premiere_timestamp.not_nil!.to_unix - end - - if video.player_response["streamingData"]?.try &.["hlsManifestUrl"]? - host_url = make_host_url(config, Kemal.config) - - host_params = env.request.query_params - host_params.delete_all("v") - - hlsvp = video.player_response["streamingData"]["hlsManifestUrl"].as_s - hlsvp = hlsvp.gsub("https://manifest.googlevideo.com", host_url) - - json.field "hlsUrl", hlsvp - end - - json.field "dashUrl", "#{make_host_url(config, Kemal.config)}/api/manifest/dash/id/#{id}" - - json.field "adaptiveFormats" do - json.array do - adaptive_fmts.each do |fmt| - json.object do - json.field "index", fmt["index"] - json.field "bitrate", fmt["bitrate"] - json.field "init", fmt["init"] - json.field "url", fmt["url"] - json.field "itag", fmt["itag"] - json.field "type", fmt["type"] - json.field "clen", fmt["clen"] - json.field "lmt", fmt["lmt"] - json.field "projectionType", fmt["projection_type"] - - fmt_info = itag_to_metadata?(fmt["itag"]) - if fmt_info - fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.to_i || 30 - json.field "fps", fps - json.field "container", fmt_info["ext"] - json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"] - - if fmt_info["height"]? - json.field "resolution", "#{fmt_info["height"]}p" - - quality_label = "#{fmt_info["height"]}p" - if fps > 30 - quality_label += "60" - end - json.field "qualityLabel", quality_label - - if fmt_info["width"]? - json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}" - end - end - end - end - end - end - end - - json.field "formatStreams" do - json.array do - fmt_stream.each do |fmt| - json.object do - json.field "url", fmt["url"] - json.field "itag", fmt["itag"] - json.field "type", fmt["type"] - json.field "quality", fmt["quality"] - - fmt_info = itag_to_metadata?(fmt["itag"]) - if fmt_info - fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.to_i || 30 - json.field "fps", fps - json.field "container", fmt_info["ext"] - json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"] - - if fmt_info["height"]? - json.field "resolution", "#{fmt_info["height"]}p" - - quality_label = "#{fmt_info["height"]}p" - if fps > 30 - quality_label += "60" - end - json.field "qualityLabel", quality_label - - if fmt_info["width"]? - json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}" - end - end - end - end - end - end - end - - json.field "captions" do - json.array do - captions.each do |caption| - json.object do - json.field "label", caption.name.simpleText - json.field "languageCode", caption.languageCode - json.field "url", "/api/v1/captions/#{id}?label=#{URI.escape(caption.name.simpleText)}" - end - end - end - end - - json.field "recommendedVideos" do - json.array do - video.info["rvs"]?.try &.split(",").each do |rv| - rv = HTTP::Params.parse(rv) - - if rv["id"]? - json.object do - json.field "videoId", rv["id"] - json.field "title", rv["title"] - json.field "videoThumbnails" do - generate_thumbnails(json, rv["id"], config, Kemal.config) - end - json.field "author", rv["author"] - json.field "lengthSeconds", rv["length_seconds"].to_i - json.field "viewCountText", rv["short_view_count_text"] - end - end - end - end - end - end - end - - video_info + video.to_json(locale, config, Kemal.config, decrypt_function) end get "/api/v1/trending" do |env| @@ -4289,6 +4119,56 @@ get "/api/v1/mixes/:rdid" do |env| response end +get "/api/v1/auth/notifications" do |env| + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + + env.response.content_type = "text/event-stream" + + topics = env.params.query["topics"]?.try &.split(",").uniq.first(1000) + topics ||= [] of String + + begin + id = 0 + + spawn do + PG.connect_listen(PG_URL, "notifications") do |event| + notification = JSON.parse(event.payload) + topic = notification["topic"].as_s + key = notification["key"].as_s + + response = JSON.parse(get_video(key, PG_DB, proxies).to_json(locale, config, Kemal.config, decrypt_function)) + + if fields_text = env.params.query["fields"]? + begin + JSONFilter.filter(response, fields_text) + rescue ex + env.response.status_code = 400 + response = {"error" => ex.message} + end + end + + if topics.try &.includes? topic + env.response.puts "id: #{id}" + env.response.puts "data: #{response.to_json}" + env.response.puts + env.response.flush + + id += 1 + end + end + end + + # Send heartbeat + loop do + env.response.puts ":keepalive #{Time.now.to_unix}" + env.response.puts + env.response.flush + sleep (20 + rand(11)).seconds + end + rescue + end +end + # TODO # get "/api/v1/auth/preferences" do |env| # ... diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index 9339d197..d1f98644 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -138,16 +138,23 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil) premiere_timestamp = channel_video.try &.premiere_timestamp + # Deliver notifications to `/api/v1/auth/notifications` + # payload = { + # "key" => video_id, + # "topic" => ucid, + # }.to_json + # PG_DB.exec("NOTIFY notifications, E'#{payload}'") + video = ChannelVideo.new( - video_id, - title, - published, - Time.now, - ucid, - author, - length_seconds, - live_now, - premiere_timestamp + id: video_id, + title: title, + published: published, + updated: Time.now, + ucid: ucid, + author: author, + length_seconds: length_seconds, + live_now: live_now, + premiere_timestamp: premiere_timestamp ) db.exec("UPDATE users SET notifications = notifications || $1 \ @@ -187,15 +194,15 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil) count = nodeset.size videos = videos.map { |video| ChannelVideo.new( - video.id, - video.title, - video.published, - Time.now, - video.ucid, - video.author, - video.length_seconds, - video.live_now, - video.premiere_timestamp + id: video.id, + title: video.title, + published: video.published, + updated: Time.now, + ucid: video.ucid, + author: video.author, + length_seconds: video.length_seconds, + live_now: video.live_now, + premiere_timestamp: video.premiere_timestamp ) } videos.each do |video| diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr index 0c1b7bd2..e1c43cfe 100644 --- a/src/invidious/helpers/handlers.cr +++ b/src/invidious/helpers/handlers.cr @@ -57,7 +57,7 @@ class Kemal::ExceptionHandler end class FilteredCompressHandler < Kemal::Handler - exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/ggpht/*"] + exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/ggpht/*", "/api/v1/auth/notifications"] def call(env) return call_next env if exclude_match? env @@ -133,12 +133,17 @@ class APIHandler < Kemal::Handler {% for method in %w(GET POST PUT HEAD DELETE PATCH OPTIONS) %} only ["/api/v1/*"], {{method}} {% end %} + exclude ["/api/v1/auth/notifications"] def call(env) return call_next env unless only_match? env env.response.headers["Access-Control-Allow-Origin"] = "*" + # Since /api/v1/notifications is an event-stream, we don't want + # to wrap the response + return call_next env if exclude_match? env + # Here we swap out the socket IO so we can modify the response as needed output = env.response.output env.response.output = IO::Memory.new @@ -152,8 +157,7 @@ class APIHandler < Kemal::Handler if env.response.headers["Content-Type"]?.try &.== "application/json" response = JSON.parse(response) - if env.params.query["fields"]? - fields_text = env.params.query["fields"] + if fields_text = env.params.query["fields"]? begin JSONFilter.filter(response, fields_text) rescue ex @@ -168,7 +172,7 @@ class APIHandler < Kemal::Handler response = response.to_json end end - rescue + rescue ex ensure env.response.output = output env.response.puts response diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 3bd30af5..b67cf0c9 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -250,6 +250,188 @@ struct Video end end + def to_json(locale, config, kemal_config, decrypt_function) + JSON.build do |json| + json.object do + json.field "title", self.title + json.field "videoId", self.id + json.field "videoThumbnails" do + generate_thumbnails(json, self.id, config, kemal_config) + end + json.field "storyboards" do + generate_storyboards(json, self.storyboards, config, kemal_config) + end + + json.field "description", html_to_content(self.description) + json.field "descriptionHtml", self.description + json.field "published", self.published.to_unix + json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale)) + json.field "keywords", self.keywords + + json.field "viewCount", self.views + json.field "likeCount", self.likes + json.field "dislikeCount", self.dislikes + + json.field "paid", self.paid + json.field "premium", self.premium + json.field "isFamilyFriendly", self.is_family_friendly + json.field "allowedRegions", self.allowed_regions + json.field "genre", self.genre + json.field "genreUrl", self.genre_url + + json.field "author", self.author + json.field "authorId", self.ucid + json.field "authorUrl", "/channel/#{self.ucid}" + + json.field "authorThumbnails" do + json.array do + qualities = {32, 48, 76, 100, 176, 512} + + qualities.each do |quality| + json.object do + json.field "url", self.author_thumbnail.gsub("=s48-", "=s#{quality}-") + json.field "width", quality + json.field "height", quality + end + end + end + end + + json.field "subCountText", self.sub_count_text + + json.field "lengthSeconds", self.info["length_seconds"].to_i + json.field "allowRatings", self.allow_ratings + json.field "rating", self.info["avg_rating"].to_f32 + json.field "isListed", self.is_listed + json.field "liveNow", self.live_now + json.field "isUpcoming", self.is_upcoming + + if self.premiere_timestamp + json.field "premiereTimestamp", self.premiere_timestamp.not_nil!.to_unix + end + + if self.player_response["streamingData"]?.try &.["hlsManifestUrl"]? + host_url = make_host_url(config, kemal_config) + + hlsvp = self.player_response["streamingData"]["hlsManifestUrl"].as_s + hlsvp = hlsvp.gsub("https://manifest.googlevideo.com", host_url) + + json.field "hlsUrl", hlsvp + end + + json.field "dashUrl", "#{make_host_url(config, kemal_config)}/api/manifest/dash/id/#{id}" + + json.field "adaptiveFormats" do + json.array do + self.adaptive_fmts(decrypt_function).each do |fmt| + json.object do + json.field "index", fmt["index"] + json.field "bitrate", fmt["bitrate"] + json.field "init", fmt["init"] + json.field "url", fmt["url"] + json.field "itag", fmt["itag"] + json.field "type", fmt["type"] + json.field "clen", fmt["clen"] + json.field "lmt", fmt["lmt"] + json.field "projectionType", fmt["projection_type"] + + fmt_info = itag_to_metadata?(fmt["itag"]) + if fmt_info + fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.to_i || 30 + json.field "fps", fps + json.field "container", fmt_info["ext"] + json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"] + + if fmt_info["height"]? + json.field "resolution", "#{fmt_info["height"]}p" + + quality_label = "#{fmt_info["height"]}p" + if fps > 30 + quality_label += "60" + end + json.field "qualityLabel", quality_label + + if fmt_info["width"]? + json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}" + end + end + end + end + end + end + end + + json.field "formatStreams" do + json.array do + self.fmt_stream(decrypt_function).each do |fmt| + json.object do + json.field "url", fmt["url"] + json.field "itag", fmt["itag"] + json.field "type", fmt["type"] + json.field "quality", fmt["quality"] + + fmt_info = itag_to_metadata?(fmt["itag"]) + if fmt_info + fps = fmt_info["fps"]?.try &.to_i || fmt["fps"]?.try &.to_i || 30 + json.field "fps", fps + json.field "container", fmt_info["ext"] + json.field "encoding", fmt_info["vcodec"]? || fmt_info["acodec"] + + if fmt_info["height"]? + json.field "resolution", "#{fmt_info["height"]}p" + + quality_label = "#{fmt_info["height"]}p" + if fps > 30 + quality_label += "60" + end + json.field "qualityLabel", quality_label + + if fmt_info["width"]? + json.field "size", "#{fmt_info["width"]}x#{fmt_info["height"]}" + end + end + end + end + end + end + end + + json.field "captions" do + json.array do + self.captions.each do |caption| + json.object do + json.field "label", caption.name.simpleText + json.field "languageCode", caption.languageCode + json.field "url", "/api/v1/captions/#{id}?label=#{URI.escape(caption.name.simpleText)}" + end + end + end + end + + json.field "recommendedVideos" do + json.array do + self.info["rvs"]?.try &.split(",").each do |rv| + rv = HTTP::Params.parse(rv) + + if rv["id"]? + json.object do + json.field "videoId", rv["id"] + json.field "title", rv["title"] + json.field "videoThumbnails" do + generate_thumbnails(json, rv["id"], config, kemal_config) + end + json.field "author", rv["author"] + json.field "lengthSeconds", rv["length_seconds"].to_i + json.field "viewCountText", rv["short_view_count_text"] + end + end + end + end + end + end + end + end + def allow_ratings allow_ratings = player_response["videoDetails"]?.try &.["allowRatings"]?.try &.as_bool From 14620c32aad04f7c011e1cfa1cb18ff2627b87cb Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 20 Apr 2019 10:18:54 -0500 Subject: [PATCH 086/210] Don't overwrite published date for channel_videos --- src/invidious/channels.cr | 114 ++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index d1f98644..060d5c2e 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -102,76 +102,72 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil) auto_generated = true end - if !pull_all_videos - url = produce_channel_videos_url(ucid, 1, auto_generated: auto_generated) - response = client.get(url) - json = JSON.parse(response.body) + page = 1 - if json["content_html"]? && !json["content_html"].as_s.empty? - document = XML.parse_html(json["content_html"].as_s) - nodeset = document.xpath_nodes(%q(//li[contains(@class, "feed-item-container")])) + url = produce_channel_videos_url(ucid, page, auto_generated: auto_generated) + response = client.get(url) + json = JSON.parse(response.body) - if auto_generated - videos = extract_videos(nodeset) - else - videos = extract_videos(nodeset, ucid, author) - end + if json["content_html"]? && !json["content_html"].as_s.empty? + document = XML.parse_html(json["content_html"].as_s) + nodeset = document.xpath_nodes(%q(//li[contains(@class, "feed-item-container")])) + + if auto_generated + videos = extract_videos(nodeset) + else + videos = extract_videos(nodeset, ucid, author) end + end - videos ||= [] of ChannelVideo + videos ||= [] of ChannelVideo - rss.xpath_nodes("//feed/entry").each do |entry| - video_id = entry.xpath_node("videoid").not_nil!.content - title = entry.xpath_node("title").not_nil!.content - published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content) - updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) - author = entry.xpath_node("author/name").not_nil!.content - ucid = entry.xpath_node("channelid").not_nil!.content + rss.xpath_nodes("//feed/entry").each do |entry| + video_id = entry.xpath_node("videoid").not_nil!.content + title = entry.xpath_node("title").not_nil!.content + published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content) + updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) + author = entry.xpath_node("author/name").not_nil!.content + ucid = entry.xpath_node("channelid").not_nil!.content - channel_video = videos.select { |video| video.id == video_id }[0]? + channel_video = videos.select { |video| video.id == video_id }[0]? - length_seconds = channel_video.try &.length_seconds - length_seconds ||= 0 + length_seconds = channel_video.try &.length_seconds + length_seconds ||= 0 - live_now = channel_video.try &.live_now - live_now ||= false + live_now = channel_video.try &.live_now + live_now ||= false - premiere_timestamp = channel_video.try &.premiere_timestamp + premiere_timestamp = channel_video.try &.premiere_timestamp - # Deliver notifications to `/api/v1/auth/notifications` - # payload = { - # "key" => video_id, - # "topic" => ucid, - # }.to_json - # PG_DB.exec("NOTIFY notifications, E'#{payload}'") + video = ChannelVideo.new( + id: video_id, + title: title, + published: published, + updated: Time.now, + ucid: ucid, + author: author, + length_seconds: length_seconds, + live_now: live_now, + premiere_timestamp: premiere_timestamp + ) - video = ChannelVideo.new( - id: video_id, - title: title, - published: published, - updated: Time.now, - ucid: ucid, - author: author, - length_seconds: length_seconds, - live_now: live_now, - premiere_timestamp: premiere_timestamp - ) - - db.exec("UPDATE users SET notifications = notifications || $1 \ + db.exec("UPDATE users SET notifications = notifications || $1 \ WHERE updated < $2 AND $3 = ANY(subscriptions) AND $1 <> ALL(notifications)", video.id, video.published, ucid) - video_array = video.to_a - args = arg_array(video_array) + video_array = video.to_a + args = arg_array(video_array) - # We don't include the 'premire_timestamp' here because channel pages don't include them, - # meaning the above timestamp is always null - db.exec("INSERT INTO channel_videos VALUES (#{args}) \ + # We don't include the 'premire_timestamp' here because channel pages don't include them, + # meaning the above timestamp is always null + db.exec("INSERT INTO channel_videos VALUES (#{args}) \ ON CONFLICT (id) DO UPDATE SET title = $2, published = $3, \ updated = $4, ucid = $5, author = $6, length_seconds = $7, \ live_now = $8", video_array) - end - else - page = 1 + end + + if pull_all_videos + page += 1 + ids = [] of String loop do @@ -186,6 +182,8 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil) break end + nodeset = nodeset.not_nil! + if auto_generated videos = extract_videos(nodeset) else @@ -216,16 +214,14 @@ def fetch_channel(ucid, db, pull_all_videos = true, locale = nil) video_array = video.to_a args = arg_array(video_array) - # We don't include the 'premire_timestamp' here because channel pages don't include them, - # meaning the above timestamp is always null + # We don't update the 'premire_timestamp' here because channel pages don't include them db.exec("INSERT INTO channel_videos VALUES (#{args}) \ - ON CONFLICT (id) DO UPDATE SET title = $2, published = $3, \ - updated = $4, ucid = $5, author = $6, length_seconds = $7, \ - live_now = $8", video_array) + ON CONFLICT (id) DO UPDATE SET title = $2, updated = $4, \ + ucid = $5, author = $6, length_seconds = $7, live_now = $8", video_array) end end - if count < 30 + if count < 25 break end From ddd74549fe31dfc89d5d4a7c289b40aec90301ad Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 20 Apr 2019 10:50:55 -0500 Subject: [PATCH 087/210] Fix description field for /api/v1/videos --- src/invidious/videos.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index b67cf0c9..1da9fd0e 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -262,8 +262,8 @@ struct Video generate_storyboards(json, self.storyboards, config, kemal_config) end - json.field "description", html_to_content(self.description) - json.field "descriptionHtml", self.description + json.field "description", html_to_content(self.description).last + json.field "descriptionHtml", html_to_content(self.description).first json.field "published", self.published.to_unix json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale)) json.field "keywords", self.keywords From 30e567e8b63052d657f03e709be2662cafec62af Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 20 Apr 2019 12:41:51 -0500 Subject: [PATCH 088/210] Fix published time for /api/v1/auth/notifications --- src/invidious.cr | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 66ed4512..fb8ebbe4 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2649,8 +2649,9 @@ post "/feed/webhook/:token" do |env| # Deliver notifications to `/api/v1/auth/notifications` payload = { - "key" => video.id, - "topic" => video.ucid, + "topic" => video.ucid, + "videoId" => video.id, + "published" => published.to_unix, }.to_json PG_DB.exec("NOTIFY notifications, E'#{payload}'") @@ -4134,9 +4135,12 @@ get "/api/v1/auth/notifications" do |env| PG.connect_listen(PG_URL, "notifications") do |event| notification = JSON.parse(event.payload) topic = notification["topic"].as_s - key = notification["key"].as_s + video_id = notification["videoId"].as_s + published = notification["published"].as_i64 - response = JSON.parse(get_video(key, PG_DB, proxies).to_json(locale, config, Kemal.config, decrypt_function)) + video = get_video(video_id, PG_DB, proxies) + video.published = Time.unix(published) + response = JSON.parse(video.to_json(locale, config, Kemal.config, decrypt_function)) if fields_text = env.params.query["fields"]? begin From 3689b08237bd6188903bd45b5719690e8e7358e6 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Fri, 19 Apr 2019 17:30:24 +0000 Subject: [PATCH 089/210] Update Esperanto translation --- locales/eo.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/locales/eo.json b/locales/eo.json index 317dc3ba..36918b49 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -13,8 +13,8 @@ "Next page": "Sekva paĝo", "Previous page": "Antaŭa paĝo", "Clear watch history?": "Ĉu forigi vidohistorion?", - "Authorize token?": "", - "Authorize token for `x`?": "", + "Authorize token?": "Ĉu rajtigi ĵetonon?", + "Authorize token for `x`?": "Ĉu rajtigi ĵetonon por `x`?", "Yes": "Jes", "No": "Ne", "Import and Export Data": "Importi kaj Eksporti Datumojn", @@ -49,7 +49,7 @@ "Player preferences": "Spektilaj agordoj", "Always loop: ": "Ĉiam ripeti: ", "Autoplay: ": "Aŭtomate ludi: ", - "Play next by default: ": "", + "Play next by default: ": "Ludi sekvan defaŭlte: ", "Autoplay next video: ": "Aŭtomate ludi sekvan videon: ", "Listen by default: ": "Aŭskulti defaŭlte: ", "Proxy videos? ": "Ĉu uzi prokuran servilon por videoj? ", @@ -57,8 +57,8 @@ "Preferred video quality: ": "Preferita videkvalito: ", "Player volume: ": "Ludila sonforteco: ", "Default comments: ": "Defaŭltaj komentoj: ", - "youtube": "", - "reddit": "", + "youtube": "youtube", + "reddit": "reddit", "Default captions: ": "Defaŭltaj subtekstoj: ", "Fallback captions: ": "Retrodefaŭltaj subtekstoj: ", "Show related videos? ": "Ĉu montri rilatajn videojn? ", @@ -83,7 +83,7 @@ "Clear watch history": "Forigi vidohistorion", "Import/export data": "Importi/Eksporti datumojn", "Manage subscriptions": "Administri abonojn", - "Manage tokens": "", + "Manage tokens": "Administri ĵetonojn", "Watch history": "Vidohistorio", "Delete account": "Forigi konton", "Administrator preferences": "Agordoj de administranto", @@ -96,13 +96,13 @@ "Report statistics? ": "Ĉu raporti statistikojn? ", "Save preferences": "Konservi agordojn", "Subscription manager": "Administrilo de abonoj", - "Token manager": "", - "Token": "", + "Token manager": "Ĵetona administrilo", + "Token": "Ĵetono", "`x` subscriptions": "`x` abonoj", - "`x` tokens": "", + "`x` tokens": "`x` ĵetonoj", "Import/export": "Importi/Eksporti", "unsubscribe": "malaboni", - "revoke": "", + "revoke": "senvalidigi", "Subscriptions": "Abonoj", "`x` unseen notifications": "`x` neviditaj sciigoj", "search": "serĉi", @@ -122,7 +122,7 @@ "Whitelisted regions: ": "Regionoj listigitaj en blanka listo: ", "Blacklisted regions: ": "Regionoj listigitaj en nigra listo: ", "Shared `x`": "Konigita `x`", - "`x` views": "", + "`x` views": "`x` spektaĵoj", "Premieres in `x`": "Premieras en `x`", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Saluton! Ŝajnas, ke vi havas Ĝavoskripton malebligitan. Klaku ĉi tie por vidi komentojn, memoru, ke la ŝargado povus daŭri iom pli.", "View YouTube comments": "Vidi komentojn de YouTube", @@ -286,7 +286,7 @@ "About": "Pri", "Rating: ": "Takso: ", "Language: ": "Lingvo: ", - "View as playlist": "", + "View as playlist": "Vidi kiel ludlisto", "Default": "Defaŭlte", "Music": "Musiko", "Gaming": "Komputiloludoj", @@ -303,4 +303,4 @@ "Videos": "Videoj", "Playlists": "Ludlistoj", "Current version: ": "Nuna versio: " -} \ No newline at end of file +} From 64aecba7a020f85993f3ce06246d8793fa948b52 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 22 Apr 2019 10:18:17 -0500 Subject: [PATCH 090/210] Add option to change passwords --- locales/ar.json | 4 ++ locales/de.json | 4 ++ locales/en-US.json | 4 ++ locales/eo.json | 4 ++ locales/es.json | 4 ++ locales/eu.json | 4 ++ locales/fr.json | 4 ++ locales/it.json | 4 ++ locales/nb_NO.json | 4 ++ locales/nl.json | 4 ++ locales/pl.json | 4 ++ locales/ru.json | 4 ++ locales/uk.json | 4 ++ src/invidious.cr | 80 +++++++++++++++++++++++++ src/invidious/views/change_password.ecr | 32 ++++++++++ src/invidious/views/preferences.ecr | 4 ++ 16 files changed, 168 insertions(+) create mode 100644 src/invidious/views/change_password.ecr diff --git a/locales/ar.json b/locales/ar.json index adb8c649..695a3a61 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -13,6 +13,9 @@ "Next page": "الصفحة الثانية", "Previous page": "الصفحة السابقة", "Clear watch history?": "مسح السجل ؟", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "نعم", @@ -82,6 +85,7 @@ "Data preferences": "إعدادات التفضيلات", "Clear watch history": "حذف سجل المشاهدة", "Import/export data": "إضافة\\إستخراج البيانات", + "Change password": "", "Manage subscriptions": "إدارة المشتركين", "Manage tokens": "", "Watch history": "سجل المشاهدة", diff --git a/locales/de.json b/locales/de.json index cffe8b95..a2a09e68 100644 --- a/locales/de.json +++ b/locales/de.json @@ -13,6 +13,9 @@ "Next page": "Nächste Seite", "Previous page": "Vorherige Seite", "Clear watch history?": "Verlauf löschen?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Ja", @@ -82,6 +85,7 @@ "Data preferences": "Dateneinstellungen", "Clear watch history": "Verlauf löschen", "Import/export data": "Daten im- exportieren", + "Change password": "", "Manage subscriptions": "Abonnements verwalten", "Manage tokens": "", "Watch history": "Verlauf", diff --git a/locales/en-US.json b/locales/en-US.json index 9cfce711..8dbc7e61 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -13,6 +13,9 @@ "Next page": "Next page", "Previous page": "Previous page", "Clear watch history?": "Clear watch history?", + "New password": "New password", + "New passwords must match": "New passwords must match", + "Cannot change password for Google accounts": "Cannot change password for Google accounts", "Authorize token?": "Authorize token?", "Authorize token for `x`?": "Authorize token for `x`?", "Yes": "Yes", @@ -82,6 +85,7 @@ "Data preferences": "Data preferences", "Clear watch history": "Clear watch history", "Import/export data": "Import/export data", + "Change password": "Change password", "Manage subscriptions": "Manage subscriptions", "Manage tokens": "Manage tokens", "Watch history": "Watch history", diff --git a/locales/eo.json b/locales/eo.json index 317dc3ba..f8ae6912 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -13,6 +13,9 @@ "Next page": "Sekva paĝo", "Previous page": "Antaŭa paĝo", "Clear watch history?": "Ĉu forigi vidohistorion?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Jes", @@ -82,6 +85,7 @@ "Data preferences": "Datumagordoj", "Clear watch history": "Forigi vidohistorion", "Import/export data": "Importi/Eksporti datumojn", + "Change password": "", "Manage subscriptions": "Administri abonojn", "Manage tokens": "", "Watch history": "Vidohistorio", diff --git a/locales/es.json b/locales/es.json index 4c6f4f39..15191506 100644 --- a/locales/es.json +++ b/locales/es.json @@ -13,6 +13,9 @@ "Next page": "Página siguiente", "Previous page": "Página anterior", "Clear watch history?": "¿Quiere borrar el historial de reproducción?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Sí", @@ -82,6 +85,7 @@ "Data preferences": "Preferencias de los datos", "Clear watch history": "Borrar el historial de reproducción", "Import/export data": "Importar/Exportar datos", + "Change password": "", "Manage subscriptions": "Gestionar las suscripciones", "Manage tokens": "", "Watch history": "Historial de reproducción", diff --git a/locales/eu.json b/locales/eu.json index 9abeb684..a17f8ec8 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -13,6 +13,9 @@ "Next page": "Hurrengo orria", "Previous page": "Aurreko orria", "Clear watch history?": "Garbitu ikusitakoen historia?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Bai", @@ -82,6 +85,7 @@ "Data preferences": "", "Clear watch history": "", "Import/export data": "", + "Change password": "", "Manage subscriptions": "", "Manage tokens": "", "Watch history": "", diff --git a/locales/fr.json b/locales/fr.json index e94c0d1c..9e15d310 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -13,6 +13,9 @@ "Next page": "Page suivante", "Previous page": "Page précédente", "Clear watch history?": "Êtes-vous sûr de vouloir supprimer l'historique des vidéos regardées ?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Oui", @@ -82,6 +85,7 @@ "Data preferences": "Préférences liées aux données", "Clear watch history": "Supprimer l'historique des vidéos regardées", "Import/export data": "Importer/exporter les données", + "Change password": "", "Manage subscriptions": "Gérer les abonnements", "Manage tokens": "", "Watch history": "Historique de visionnage", diff --git a/locales/it.json b/locales/it.json index 05700de8..3c938ffb 100644 --- a/locales/it.json +++ b/locales/it.json @@ -13,6 +13,9 @@ "Next page": "Pagina successiva", "Previous page": "Pagina precedente", "Clear watch history?": "Sei sicuro di voler cancellare la cronologia dei video guardati?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Si", @@ -82,6 +85,7 @@ "Data preferences": "Preferenze dati", "Clear watch history": "Cancella la cronologia dei video guardati", "Import/export data": "Importazione/esportazione dati", + "Change password": "", "Manage subscriptions": "Gestisci le iscrizioni", "Manage tokens": "", "Watch history": "Cronologia dei video", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 382a951b..5adeeeeb 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -13,6 +13,9 @@ "Next page": "Neste side", "Previous page": "Forrige side", "Clear watch history?": "Tøm visningshistorikk?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Ja", @@ -82,6 +85,7 @@ "Data preferences": "Datainnstillinger", "Clear watch history": "Tøm visningshistorikk", "Import/export data": "Importer/eksporter data", + "Change password": "", "Manage subscriptions": "Behandle abonnementer", "Manage tokens": "", "Watch history": "Visningshistorikk", diff --git a/locales/nl.json b/locales/nl.json index 9d9dac9e..29e38e1c 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -13,6 +13,9 @@ "Next page": "Volgende pagina", "Previous page": "Vorige pagina", "Clear watch history?": "Kijk geschiedenis wissen?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Ja", @@ -82,6 +85,7 @@ "Data preferences": "Gegevens voorkeuren", "Clear watch history": "Kijkgeschiedenis wissen", "Import/export data": "Importeer/Exporteer gegevens", + "Change password": "", "Manage subscriptions": "Abonnees beheren", "Manage tokens": "", "Watch history": "Kijkgeschiedenis", diff --git a/locales/pl.json b/locales/pl.json index d970f8c9..745f8a79 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -13,6 +13,9 @@ "Next page": "Następna strona", "Previous page": "Poprzednia strona", "Clear watch history?": "Wyczyścić historię?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Tak", @@ -82,6 +85,7 @@ "Data preferences": "Preferencje danych", "Clear watch history": "Wyczyść historię", "Import/export data": "Import/Eksport danych", + "Change password": "", "Manage subscriptions": "Organizuj subskrybcje", "Manage tokens": "", "Watch history": "Historia", diff --git a/locales/ru.json b/locales/ru.json index 49a94436..79536302 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -13,6 +13,9 @@ "Next page": "Следующая страница", "Previous page": "Предыдущая страница", "Clear watch history?": "Очистить историю просмотров?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Да", @@ -82,6 +85,7 @@ "Data preferences": "Настройки данных", "Clear watch history": "Очистить историю просмотра", "Import/export data": "Импорт/Экспорт данных", + "Change password": "", "Manage subscriptions": "Управление подписками", "Manage tokens": "", "Watch history": "История просмотров", diff --git a/locales/uk.json b/locales/uk.json index 023db237..02fa563f 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -13,6 +13,9 @@ "Next page": "Наступна сторінка", "Previous page": "Попередня сторінка", "Clear watch history?": "Очистити історію переглядів?", + "New password": "", + "New passwords must match": "", + "Cannot change password for Google accounts": "", "Authorize token?": "", "Authorize token for `x`?": "", "Yes": "Так", @@ -82,6 +85,7 @@ "Data preferences": "Налаштування даних", "Clear watch history": "Очистити історію переглядів", "Import/export data": "Імпорт і експорт даних", + "Change password": "", "Manage subscriptions": "Керування підписками", "Manage tokens": "", "Watch history": "Історія переглядів", diff --git a/src/invidious.cr b/src/invidious.cr index fb8ebbe4..3780a2f0 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1875,6 +1875,86 @@ post "/data_control" do |env| env.redirect referer end +get "/change_password" do |env| + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env) + + if user + user = user.as(User) + sid = sid.as(String) + csrf_token = generate_response(sid, {":change_password"}, HMAC_KEY, PG_DB) + + templated "change_password" + else + env.redirect referer + end +end + +post "/change_password" do |env| + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env) + + if user + user = user.as(User) + sid = sid.as(String) + token = env.params.body["csrf_token"]? + + # We don't store passwords for Google accounts + if !user.password + error_message = "Cannot change password for Google accounts" + next templated "error" + end + + begin + validate_request(token, sid, env.request, HMAC_KEY, PG_DB, locale) + rescue ex + error_message = ex.message + env.response.status_code = 400 + next templated "error" + end + + password = env.params.body["password"]? + if !password + error_message = translate(locale, "Password is a required field") + next templated "error" + end + + new_passwords = env.params.body.select { |k, v| k.match(/^new_password\[\d+\]$/) }.map { |k, v| v } + + if new_passwords.size <= 1 || new_passwords.uniq.size != 1 + error_message = translate(locale, "New passwords must match") + next templated "error" + end + + new_password = new_passwords.uniq[0] + if new_password.empty? + error_message = translate(locale, "Password cannot be empty") + next templated "error" + end + + if new_password.size > 55 + error_message = translate(locale, "Password cannot be longer than 55 characters") + next templated "error" + end + + if Crypto::Bcrypt::Password.new(user.password.not_nil!) != password + error_message = translate(locale, "Incorrect password") + next templated "error" + end + + new_password = Crypto::Bcrypt::Password.create(new_password, cost: 10) + PG_DB.exec("UPDATE users SET password = $1 WHERE email = $2", new_password.to_s, user.email) + end + + env.redirect referer +end + get "/delete_account" do |env| locale = LOCALES[env.get("preferences").as(Preferences).locale]? diff --git a/src/invidious/views/change_password.ecr b/src/invidious/views/change_password.ecr new file mode 100644 index 00000000..2e68556b --- /dev/null +++ b/src/invidious/views/change_password.ecr @@ -0,0 +1,32 @@ +<% content_for "header" do %> +<%= translate(locale, "Change password") %> - Invidious +<% end %> + +
+
+
+
+
+ <%= translate(locale, "Change password") %> + +
+ + "> + + + "> + + + "> + + + + +
+
+
+
+
+
diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr index 1af53488..5d2c35b1 100644 --- a/src/invidious/views/preferences.ecr +++ b/src/invidious/views/preferences.ecr @@ -213,6 +213,10 @@ function update_value(element) { <%= translate(locale, "Clear watch history") %>
+ + From 250860d92c166b06d4af5d713ec2640a5ad4e701 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 22 Apr 2019 10:40:29 -0500 Subject: [PATCH 091/210] Add '/api/v1/auth/subscriptions' --- src/invidious.cr | 67 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 3780a2f0..ac9b1320 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1712,7 +1712,14 @@ get "/subscription_manager" do |env| format = env.params.query["format"]? format ||= "rss" - subscriptions = PG_DB.query_all("SELECT * FROM channels WHERE id = ANY('{#{user.subscriptions.join(",")}}')", as: InvidiousChannel) + if user.subscriptions.empty? + values = "'{}'" + else + values = "VALUES #{user.subscriptions.map { |id| %(('#{id}')) }.join(",")}" + end + + subscriptions = PG_DB.query_all("SELECT * FROM channels WHERE id = ANY(#{values})", as: InvidiousChannel) + subscriptions.sort_by! { |channel| channel.author.downcase } if action_takeout @@ -4263,20 +4270,54 @@ end # ... # end -# TODO -# get "/api/v1/auth/subscriptions" do |env| -# ... -# end +get "/api/v1/auth/subscriptions" do |env| + env.response.content_type = "application/json" + user = env.get("user").as(User) -# TODO -# post "/api/v1/auth/subscriptions/:ucid" do |env| -# ... -# end + if user.subscriptions.empty? + values = "'{}'" + else + values = "VALUES #{user.subscriptions.map { |id| %(('#{id}')) }.join(",")}" + end -# TODO -# delete "/api/v1/auth/subscriptions/:ucid" do |env| -# ... -# end + subscriptions = PG_DB.query_all("SELECT * FROM channels WHERE id = ANY(#{values})", as: InvidiousChannel) + + JSON.build do |json| + json.array do + subscriptions.each do |subscription| + json.object do + json.field "author", subscription.author + json.field "authorId", subscription.id + end + end + end + end +end + +post "/api/v1/auth/subscriptions/:ucid" do |env| + env.response.content_type = "application/json" + user = env.get("user").as(User) + + ucid = env.params.url["ucid"] + + if !user.subscriptions.includes? ucid + get_channel(ucid, PG_DB, false, false) + PG_DB.exec("UPDATE users SET subscriptions = array_append(subscriptions,$1) WHERE email = $2", ucid, user.email) + end + + env.response.status_code = 204 +end + +delete "/api/v1/auth/subscriptions/:ucid" do |env| + env.response.content_type = "application/json" + user = env.get("user").as(User) + + ucid = env.params.url["ucid"] + + PG_DB.exec("UPDATE users SET subscriptions = array_remove(subscriptions,$1) WHERE email = $2", ucid, user.email) + + env.response.status_code = 204 +end get "/api/v1/auth/tokens" do |env| env.response.content_type = "application/json" From 0a8e20fd60d65e2d38acda4e09435190fa1f2e89 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 22 Apr 2019 11:07:41 -0500 Subject: [PATCH 092/210] Revert "Update French translation" This reverts commit a2533af1165f8178125285ab1796477cdc5b91e4. --- locales/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 9e15d310..a592f523 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -63,7 +63,7 @@ "youtube": "", "reddit": "", "Default captions: ": "Sous-titres par défaut : ", - "Fallback captions: ": "Sous-titres par défaut : ", + "Fallback captions: ": "Fallback captions: ", "Show related videos? ": "Voir les vidéos liées ? ", "Visual preferences": "Préférences du site", "Dark mode: ": "Mode Sombre : ", @@ -114,7 +114,7 @@ "Released under the AGPLv3 by Omar Roth.": "Publié sous licence AGPLv3 par Omar Roth.", "Source available here.": "Code Source.", "View JavaScript license information.": "Voir les informations des licences JavaScript.", - "View privacy policy.": "Consulter la politique de confidentialité.", + "View privacy policy.": "Politique de confidentialité", "Trending": "Tendances", "Unlisted": "Non répertoriée", "Watch on YouTube": "Voir la vidéo sur Youtube", From 5567e2843d0abc1871b0a83b530d83e722a80ee2 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 22 Apr 2019 11:15:19 -0500 Subject: [PATCH 093/210] Force refresh after receiving PubSub notification --- src/invidious.cr | 2 +- src/invidious/videos.cr | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index ac9b1320..4a15602e 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -2732,7 +2732,7 @@ post "/feed/webhook/:token" do |env| published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content) updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) - video = get_video(id, PG_DB, proxies, region: nil) + video = get_video(id, PG_DB, proxies, force_refresh: true) # Deliver notifications to `/api/v1/auth/notifications` payload = { diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr index 1da9fd0e..b039436a 100644 --- a/src/invidious/videos.cr +++ b/src/invidious/videos.cr @@ -817,12 +817,12 @@ end class VideoRedirect < Exception end -def get_video(id, db, proxies = {} of String => Array({ip: String, port: Int32}), refresh = true, region = nil) +def get_video(id, db, proxies = {} of String => Array({ip: String, port: Int32}), refresh = true, region = nil, force_refresh = false) if db.query_one?("SELECT EXISTS (SELECT true FROM videos WHERE id = $1)", id, as: Bool) && !region video = db.query_one("SELECT * FROM videos WHERE id = $1", id, as: Video) # If record was last updated over 10 minutes ago, refresh (expire param in response lasts for 6 hours) - if refresh && Time.now - video.updated > 10.minutes + if (refresh && Time.now - video.updated > 10.minutes) || force_refresh begin video = fetch_video(id, proxies, region) video_array = video.to_a From 19ed5bf993a070534e1c0745de16f03c7409b738 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Mon, 22 Apr 2019 15:39:57 -0500 Subject: [PATCH 094/210] Add support for 'user' URLs in NewPipe import --- src/invidious.cr | 18 ++++++++++++++++-- src/invidious/channels.cr | 3 +-- src/invidious/search.cr | 6 ++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 4a15602e..0cabd507 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1842,8 +1842,22 @@ post "/data_control" do |env| PG_DB.exec("UPDATE users SET subscriptions = $1 WHERE email = $2", user.subscriptions, user.email) when "import_newpipe_subscriptions" body = JSON.parse(body) - user.subscriptions += body["subscriptions"].as_a.map do |channel| - channel["url"].as_s.match(/UC[a-zA-Z0-9_-]{22}/).not_nil![0] + user.subscriptions += body["subscriptions"].as_a.compact_map do |channel| + if match = channel["url"].as_s.match(/\/channel\/(?UC[a-zA-Z0-9_-]{22})/) + next match["channel"] + elsif match = channel["url"].as_s.match(/\/user\/(?.+)/) + client = make_client(YT_URL) + response = client.get("/user/#{match["user"]}?disable_polymer=1&hl=en&gl=US") + document = XML.parse_html(response.body) + canonical = document.xpath_node(%q(//link[@rel="canonical"])) + + if canonical + ucid = canonical["href"].split("/")[-1] + next ucid + end + end + + nil end user.subscriptions.uniq! diff --git a/src/invidious/channels.cr b/src/invidious/channels.cr index 060d5c2e..96544b49 100644 --- a/src/invidious/channels.cr +++ b/src/invidious/channels.cr @@ -51,8 +51,7 @@ def get_batch_channels(channels, db, refresh = false, pull_all_videos = true, ma final = [] of String channels.size.times do - ucid = finished_channel.receive - if ucid + if ucid = finished_channel.receive final << ucid end end diff --git a/src/invidious/search.cr b/src/invidious/search.cr index c3c48af3..58ccb164 100644 --- a/src/invidious/search.cr +++ b/src/invidious/search.cr @@ -63,6 +63,12 @@ def channel_search(query, page, channel) canonical = document.xpath_node(%q(//link[@rel="canonical"])) end + if !canonical + response = client.get("/user/#{channel}?disable_polymer=1&hl=en&gl=US") + document = XML.parse_html(response.body) + canonical = document.xpath_node(%q(//link[@rel="canonical"])) + end + if !canonical return 0, [] of SearchItem end From f6d8df1e836fee1f1c83c1bc5eef36d4a6c963cb Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 24 Apr 2019 08:48:34 -0500 Subject: [PATCH 095/210] Update videojs-share --- assets/js/videojs-share.min.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/videojs-share.min.js b/assets/js/videojs-share.min.js index 9c3e7c4e..48930119 100644 --- a/assets/js/videojs-share.min.js +++ b/assets/js/videojs-share.min.js @@ -1,7 +1,7 @@ /** * videojs-share - * @version 3.0.0 + * @version 3.2.0 * @copyright 2019 Mikhail Khazov * @license MIT */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("video.js")):"function"==typeof define&&define.amd?define(["video.js"],e):t.videojsShare=e(t.videojs)}(this,function(t){"use strict";function e(t,e){return e={exports:{}},t(e,e.exports),e.exports}function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var i=window.getSelection(),o=document.createRange();o.selectNodeContents(t),i.removeAllRanges(),i.addRange(o),e=i.toString()}return e}function i(){}function o(t,e){for(;t&&t.nodeType!==G;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}function r(t,e,n,i,o){var r=l.apply(this,arguments);return t.addEventListener(n,r,o),{destroy:function(){t.removeEventListener(n,r,o)}}}function a(t,e,n,i,o){return"function"==typeof t.addEventListener?r.apply(null,arguments):"function"==typeof n?r.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return r(t,e,n,i,o)}))}function l(t,e,n,i){return function(n){n.delegateTarget=K(n.target,e),n.delegateTarget&&i.call(t,n)}}function s(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!X.string(e))throw new TypeError("Second argument must be a String");if(!X.fn(n))throw new TypeError("Third argument must be a Function");if(X.node(t))return c(t,e,n);if(X.nodeList(t))return u(t,e,n);if(X.string(t))return h(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function u(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function h(t,e,n){return Q(document.body,t,e,n)}function d(t){return Object.keys(t).filter(function(e){return void 0!==t[e]&&""!==t[e]}).map(function(e){return encodeURIComponent(e)+"="+encodeURIComponent(t[e])}).join("&")}function f(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.fbAppId,n=t.url,i=t.redirectUri;if(!e)throw new Error("fbAppId is not defined");var o=d({app_id:e,display:"popup",redirect_uri:i,link:n});return window.open("https://www.facebook.com/dialog/feed?"+o,"_blank",tt)}function p(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.fbAppId,n=t.url,i=t.hashtag,o=t.redirectUri;if(!e)throw new Error("fbAppId is not defined");var r=d({app_id:e,display:"popup",redirect_uri:o,href:n,hashtag:i});return window.open("https://www.facebook.com/dialog/share?"+r,"_blank",tt)}function v(){var t=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).url;if(!t)throw new Error("url is not defined");var e=d({kid_directed_site:"0",sdk:"joey",u:t,display:"popup",ref:"plugin",src:"share_button"});return window.open("https://www.facebook.com/sharer/sharer.php?"+e,"_blank",tt)}function g(){var t=d({url:(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).url});return window.open("https://plus.google.com/share?"+t,"_blank",tt)}function w(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({share_url:t.url,title:t.title,description:t.description,imageurl:t.image});return window.open("http://connect.mail.ru/share?"+e,"_blank",tt)}function m(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.title,i=t.description,o=(n||"")+"\r\n"+(i||"")+"\r\n"+(e||""),r="mailto:?body="+encodeURIComponent(o);return window.location.assign(r)}function y(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({"st.cmd":"addShare","st._surl":t.url,title:t.title});return window.open("https://ok.ru/dk?"+e,"_blank",tt)}function b(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({url:t.url,text:t.title});return window.open("https://t.me/share/url?"+e,"_blank",tt)}function k(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.title,n=t.url,i=t.hashtags,o=d({text:e,url:n,hashtags:(void 0===i?[]:i).join(",")});return window.open("https://twitter.com/intent/tweet?"+o,"_blank",tt)}function _(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({url:t.url,title:t.title});return window.open("https://www.reddit.com/submit?"+e,"_blank",tt)}function C(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.description,n=d({url:t.url,description:e,media:t.media});return window.open("https://pinterest.com/pin/create/button/?"+n,"_blank",tt)}function x(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.title,i=t.caption,o=t.tags,r=void 0===o?[]:o,a=t.posttype,l=void 0===a?"link":a,s=d({canonicalUrl:e,title:n,caption:i,tags:r.join(","),posttype:l});return window.open("https://www.tumblr.com/widgets/share/tool?"+s,"_blank",tt)}function E(){return!!window.navigator.userAgent.match(/Version\/[\d.]+.*Safari/)}function S(t){return E()?window.open(t):window.location.assign(t)}function j(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.title;if(!e&&!n)throw new Error("url and title not specified");return S("viber://forward?"+d({text:[n,e].filter(function(t){return t}).join(" ")}))}function F(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.image,i=t.isVkParse,o=t.description,r=t.title;o&&o.length>et&&(o=o.substr(0,et)+"..."),r&&r.length>et&&(r=r.substr(0,et)+"...");return"https://vk.com/share.php?"+d(i?{url:e}:{url:e,title:r,description:o,image:n,noparse:!0})}function A(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return window.open(F(t),"_blank",tt)}function T(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.phone,n=d({text:[t.title,t.url].filter(function(t){return t}).join(" "),phone:e});return window.open("https://api.whatsapp.com/send?"+n,"_blank",tt)}function z(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.title,n=t.url,i=d({title:e,summary:t.description,url:n});return window.open("https://www.linkedin.com/shareArticle?mini=true&"+i,"_blank",tt)}function M(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.fbAppId,n=t.url;if(!e)throw new Error("fbAppId is not defined");var i=d({app_id:e,link:n});return window.location.assign("fb-messenger://share?"+i)}function O(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.title,n=t.url;if(!n)throw new Error("url is not defined");var i=encodeURIComponent(""+n);return e&&(i=""+encodeURIComponent(e+" ")+i),window.open("https://line.me/R/msg/text/?"+i,"_blank",tt)}function L(){return"ontouchstart"in window||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints>0}function V(){return/Android/.test(window.navigator.userAgent)||/iP(hone|ad|od)/i.test(window.navigator.userAgent)}function H(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return!(arguments.length>1&&void 0!==arguments[1])||arguments[1]?V()?t:t.filter(function(t){return!it.includes(t)}):t}t=t&&t.hasOwnProperty("default")?t.default:t;var I=function(){return window.location.href}(),P={mobileVerification:!0,title:"Video",url:I,socials:["fbFeed","tw","reddit","gp","messenger","linkedin","vk","ok","mail","email","telegram","whatsapp","viber"],embedCode:function(){return""}(),redirectUri:function(){return I+"#close_window"}()},R=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},U=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":o(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})});i.prototype={on:function(t,e,n){var i=this.e||(this.e={});return(i[t]||(i[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function i(){o.off(t,i),e.apply(n,arguments)}var o=this;return i._=e,this.on(t,i,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),i=0,o=n.length;for(i;i0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,h.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new c.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return s("action",t)}},{key:"defaultTarget",value:function(t){var e=s("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return s("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(u.default);t.exports=p})})),tt="scrollbars=0, resizable=1, menubar=0, left=100, top=100, width=550, height=440, toolbar=0, status=0",et=80,nt=(Object.freeze||Object)({fbFeed:f,fbShare:p,fbButton:v,gp:g,mail:w,email:m,ok:y,telegram:b,tw:k,reddit:_,pinterest:C,tumblr:x,viber:j,getVkUrl:F,vk:A,whatsapp:T,linkedin:z,messenger:M,line:O}),it=["whatsapp","viber","messenger"],ot={fbFeed:'\n \n\n',tw:'\n \n\n',reddit:'\n \n\n',gp:'\n \n\n',messenger:'\n \n \n\n',linkedin:'\n \n\n',vk:'\n \n\n',ok:'\n \n\n',mail:'\n \n\n',email:'\n \n\n',telegram:'\n \n\n',whatsapp:'\n \n\n',viber:'\n \n\n'},rt=function(){function t(e,n){R(this,t),this.player=e,this.options=n,this.socials=H(n.socials,n.mobileVerification),this.copyBtnTextClass="vjs-share__btn-text",this.socialBtnClass="vjs-share__social",this._createContent(),this._initToggle(),this._initClipboard(),this._initSharing()}return t.prototype.getContent=function(){return this.content},t.prototype._createContent=function(){var t='\n \n \n \n '+this.player.localize("Copy")+"\n ",e=document.createElement("div"),n="";this.options.embedCode&&(n='\n \n "),e.innerHTML='
\n \n\n
\n \n \n\n "+n+'\n
\n\n
\n
\n '+this._getSocialItems().join("")+"\n
\n
\n
",this.content=e.firstChild},t.prototype._initClipboard=function(){var t=this;new $(".vjs-share__btn",{target:function(t){return t.previousElementSibling}}).on("success",function(e){var n=e.trigger.querySelector("."+t.copyBtnTextClass),i=function(){n.innerText=t.player.localize("Copy"),e.clearSelection()};n.innerText=t.player.localize("Copied"),L()?setTimeout(i,1e3):n.parentElement.addEventListener("mouseleave",function(){setTimeout(i,300)})})},t.prototype._initSharing=function(){var t=this,e=this.content.querySelectorAll("."+this.socialBtnClass);Array.from(e).forEach(function(e){e.addEventListener("click",function(e){var n=e.currentTarget.getAttribute("data-social"),i=nt[n];"function"==typeof i&&i(t.socialOptions)})})},t.prototype._initToggle=function(){var t=this.content.querySelector(".vjs-share__socials");this.socials.length>10||window.innerWidth<=180&&this.socials.length>6?t.style.height="calc((2em + 5px) * 2)":t.classList.add("horizontal")},t.prototype._getSocialItems=function(){var t=[];return this.socials.forEach(function(e){ot[e]&&t.push('\n \n ")}),t},U(t,[{key:"socialOptions",get:function(){var t=this.options;return{url:t.url,title:t.title,description:t.description,image:t.image,fbAppId:t.fbAppId,isVkParse:t.isVkParse,redirectUri:t.redirectUri,subject:t.subject}}}]),t}(),at=function(t){function e(n,i){R(this,e);var o=B(this,t.call(this,n,i));return o.playerClassName="vjs-videojs-share_open",o}return N(e,t),e.prototype.open=function(){var e=this.player();e.addClass(this.playerClassName),t.prototype.open.call(this),e.trigger("sharing:opened")},e.prototype.close=function(){var e=this.player();e.removeClass(this.playerClassName),t.prototype.close.call(this),e.trigger("sharing:closed")},e}(t.getComponent("ModalDialog")),lt=function(t){function e(n,i){R(this,e);var o=B(this,t.call(this,n,i));return o.player=n,o.options=i,o}return N(e,t),e.prototype._createModal=function(){var t=new rt(this.player,this.options).getContent();this.modal=new at(this.player,{content:t,temporary:!0}),this.el=this.modal.contentEl(),this.player.addChild(this.modal)},e.prototype.open=function(){this._createModal(),this.modal.open()},e}(t.getComponent("Component")),st=function(e){function n(i,o){R(this,n);var r=B(this,e.call(this,i));return r.options=t.mergeOptions(P,o),r.player.ready(function(){r.player.addClass("vjs-share"),i.addClass("vjs-videojs-share"),i.getChild("controlBar").addChild("ShareButton",o),i.addChild("ShareOverlay",o)}),r}return N(n,e),n}(t.getPlugin("plugin"));return st.defaultState={},st.VERSION="3.0.0",t.registerComponent("ShareButton",q),t.registerComponent("ShareOverlay",lt),t.registerPlugin("share",st),st}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("video.js")):"function"==typeof define&&define.amd?define(["video.js"],e):t.videojsShare=e(t.videojs)}(this,function(t){"use strict";function e(t,e){return e={exports:{}},t(e,e.exports),e.exports}function n(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var i=window.getSelection(),o=document.createRange();o.selectNodeContents(t),i.removeAllRanges(),i.addRange(o),e=i.toString()}return e}function i(){}function o(t,e){for(;t&&t.nodeType!==G;){if("function"==typeof t.matches&&t.matches(e))return t;t=t.parentNode}}function r(t,e,n,i,o){var r=l.apply(this,arguments);return t.addEventListener(n,r,o),{destroy:function(){t.removeEventListener(n,r,o)}}}function a(t,e,n,i,o){return"function"==typeof t.addEventListener?r.apply(null,arguments):"function"==typeof n?r.bind(null,document).apply(null,arguments):("string"==typeof t&&(t=document.querySelectorAll(t)),Array.prototype.map.call(t,function(t){return r(t,e,n,i,o)}))}function l(t,e,n,i){return function(n){n.delegateTarget=K(n.target,e),n.delegateTarget&&i.call(t,n)}}function s(t,e,n){if(!t&&!e&&!n)throw new Error("Missing required arguments");if(!X.string(e))throw new TypeError("Second argument must be a String");if(!X.fn(n))throw new TypeError("Third argument must be a Function");if(X.node(t))return c(t,e,n);if(X.nodeList(t))return u(t,e,n);if(X.string(t))return h(t,e,n);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(t,e,n){return t.addEventListener(e,n),{destroy:function(){t.removeEventListener(e,n)}}}function u(t,e,n){return Array.prototype.forEach.call(t,function(t){t.addEventListener(e,n)}),{destroy:function(){Array.prototype.forEach.call(t,function(t){t.removeEventListener(e,n)})}}}function h(t,e,n){return Q(document.body,t,e,n)}function d(t){return Object.keys(t).filter(function(e){return void 0!==t[e]&&""!==t[e]}).map(function(e){return encodeURIComponent(e)+"="+encodeURIComponent(t[e])}).join("&")}function f(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.fbAppId,n=t.url,i=t.redirectUri;if(!e)throw new Error("fbAppId is not defined");var o=d({app_id:e,display:"popup",redirect_uri:i,link:n});return window.open("https://www.facebook.com/dialog/feed?"+o,"_blank",tt)}function p(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.fbAppId,n=t.url,i=t.hashtag,o=t.redirectUri;if(!e)throw new Error("fbAppId is not defined");var r=d({app_id:e,display:"popup",redirect_uri:o,href:n,hashtag:i});return window.open("https://www.facebook.com/dialog/share?"+r,"_blank",tt)}function v(){var t=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).url;if(!t)throw new Error("url is not defined");var e=d({kid_directed_site:"0",sdk:"joey",u:t,display:"popup",ref:"plugin",src:"share_button"});return window.open("https://www.facebook.com/sharer/sharer.php?"+e,"_blank",tt)}function g(){var t=d({url:(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).url});return window.open("https://plus.google.com/share?"+t,"_blank",tt)}function w(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({share_url:t.url,title:t.title,description:t.description,imageurl:t.image});return window.open("http://connect.mail.ru/share?"+e,"_blank",tt)}function m(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.title,i=t.description,o=(n||"")+"\r\n"+(i||"")+"\r\n"+(e||""),r="mailto:?body="+encodeURIComponent(o);return window.location.assign(r)}function y(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({"st.cmd":"addShare","st._surl":t.url,title:t.title});return window.open("https://ok.ru/dk?"+e,"_blank",tt)}function b(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({url:t.url,text:t.title});return window.open("https://t.me/share/url?"+e,"_blank",tt)}function k(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.title,n=t.url,i=t.hashtags,o=d({text:e,url:n,hashtags:(void 0===i?[]:i).join(",")});return window.open("https://twitter.com/intent/tweet?"+o,"_blank",tt)}function _(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=d({url:t.url,title:t.title});return window.open("https://www.reddit.com/submit?"+e,"_blank",tt)}function C(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.description,n=d({url:t.url,description:e,media:t.media});return window.open("https://pinterest.com/pin/create/button/?"+n,"_blank",tt)}function x(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.title,i=t.caption,o=t.tags,r=void 0===o?[]:o,a=t.posttype,l=void 0===a?"link":a,s=d({canonicalUrl:e,title:n,caption:i,tags:r.join(","),posttype:l});return window.open("https://www.tumblr.com/widgets/share/tool?"+s,"_blank",tt)}function E(){return!!window.navigator.userAgent.match(/Version\/[\d.]+.*Safari/)}function S(t){return E()?window.open(t):window.location.assign(t)}function j(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.title;if(!e&&!n)throw new Error("url and title not specified");return S("viber://forward?"+d({text:[n,e].filter(function(t){return t}).join(" ")}))}function F(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.url,n=t.image,i=t.isVkParse,o=t.description,r=t.title;o&&o.length>et&&(o=o.substr(0,et)+"..."),r&&r.length>et&&(r=r.substr(0,et)+"...");return"https://vk.com/share.php?"+d(i?{url:e}:{url:e,title:r,description:o,image:n,noparse:!0})}function A(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return window.open(F(t),"_blank",tt)}function T(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.phone,n=d({text:[t.title,t.url].filter(function(t){return t}).join(" "),phone:e});return window.open("https://api.whatsapp.com/send?"+n,"_blank",tt)}function z(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.title,n=t.url,i=d({title:e,summary:t.description,url:n});return window.open("https://www.linkedin.com/shareArticle?mini=true&"+i,"_blank",tt)}function M(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.fbAppId,n=t.url;if(!e)throw new Error("fbAppId is not defined");var i=d({app_id:e,link:n});return window.location.assign("fb-messenger://share?"+i)}function O(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.title,n=t.url;if(!n)throw new Error("url is not defined");var i=encodeURIComponent(""+n);return e&&(i=""+encodeURIComponent(e+" ")+i),window.open("https://line.me/R/msg/text/?"+i,"_blank",tt)}function L(){return"ontouchstart"in window||navigator.MaxTouchPoints>0||navigator.msMaxTouchPoints>0}function V(){return/Android/.test(window.navigator.userAgent)||/iP(hone|ad|od)/i.test(window.navigator.userAgent)}function H(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return!(arguments.length>1&&void 0!==arguments[1])||arguments[1]?V()?t:t.filter(function(t){return!it.includes(t)}):t}t=t&&t.hasOwnProperty("default")?t.default:t;var I=function(){return window.location.href}(),P={mobileVerification:!0,title:"Video",url:I,socials:["fbFeed","tw","reddit","gp","messenger","linkedin","vk","ok","mail","email","telegram","whatsapp","viber"],embedCode:function(){return""}(),redirectUri:function(){return I+"#close_window"}()},R=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},U=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":o(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a})});i.prototype={on:function(t,e,n){var i=this.e||(this.e={});return(i[t]||(i[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){function i(){o.off(t,i),e.apply(n,arguments)}var o=this;return i._=e,this.on(t,i,n)},emit:function(t){var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),i=0,o=n.length;for(i;i0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===d(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,h.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new c.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return s("action",t)}},{key:"defaultTarget",value:function(t){var e=s("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return s("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}(u.default);t.exports=p})})),tt="scrollbars=0, resizable=1, menubar=0, left=100, top=100, width=550, height=440, toolbar=0, status=0",et=80,nt=(Object.freeze||Object)({fbFeed:f,fbShare:p,fbButton:v,gp:g,mail:w,email:m,ok:y,telegram:b,tw:k,reddit:_,pinterest:C,tumblr:x,viber:j,getVkUrl:F,vk:A,whatsapp:T,linkedin:z,messenger:M,line:O}),it=["whatsapp","viber","messenger"],ot={fbFeed:'\n \n\n',tw:'\n \n\n',reddit:'\n \n\n',gp:'\n \n\n',messenger:'\n \n \n\n',linkedin:'\n \n\n',vk:'\n \n\n',ok:'\n \n\n',mail:'\n \n\n',email:'\n \n\n',telegram:'\n \n\n',whatsapp:'\n \n\n',viber:'\n \n\n'},rt=function(){function t(e,n){R(this,t),this.player=e,this.options=n,this.socials=H(n.socials,n.mobileVerification),this.copyBtnTextClass="vjs-share__btn-text",this.socialBtnClass="vjs-share__social",this._createContent(),this._initToggle(),this._initClipboard(),this._initSharing()}return t.prototype.getContent=function(){return this.content},t.prototype._createContent=function(){var t='\n \n \n \n '+this.player.localize("Copy")+"\n ",e=document.createElement("div"),n="";this.options.embedCode&&(n='\n \n "),e.innerHTML='
\n \n\n
\n \n \n\n "+n+'\n
\n\n
\n
\n '+this._getSocialItems().join("")+"\n
\n
\n
",this.content=e.firstChild},t.prototype._initClipboard=function(){var t=this;new $(".vjs-share__btn",{target:function(t){return t.previousElementSibling}}).on("success",function(e){var n=e.trigger.querySelector("."+t.copyBtnTextClass),i=function(){n.innerText=t.player.localize("Copy"),e.clearSelection()};n.innerText=t.player.localize("Copied"),L()?setTimeout(i,1e3):n.parentElement.addEventListener("mouseleave",function(){setTimeout(i,300)})})},t.prototype._initSharing=function(){var t=this,e=this.content.querySelectorAll("."+this.socialBtnClass);Array.from(e).forEach(function(e){e.addEventListener("click",function(e){var n=e.currentTarget.getAttribute("data-social"),i=nt[n];"function"==typeof i&&i(t.socialOptions)})})},t.prototype._initToggle=function(){var t=this.content.querySelector(".vjs-share__socials");this.socials.length>10||window.innerWidth<=180&&this.socials.length>6?t.style.height="calc((2em + 5px) * 2)":t.classList.add("horizontal")},t.prototype._getSocialItems=function(){var t=[];return this.socials.forEach(function(e){ot[e]&&t.push('\n \n ")}),t},U(t,[{key:"socialOptions",get:function(){var t=this.options;return{url:t.url,title:t.title,description:t.description,image:t.image,fbAppId:t.fbAppId,isVkParse:t.isVkParse,redirectUri:t.redirectUri}}}]),t}(),at=function(t){function e(n,i){R(this,e);var o=B(this,t.call(this,n,i));return o.playerClassName="vjs-videojs-share_open",o}return N(e,t),e.prototype.open=function(){var e=this.player();e.addClass(this.playerClassName),t.prototype.open.call(this),e.trigger("sharing:opened")},e.prototype.close=function(){var e=this.player();e.removeClass(this.playerClassName),t.prototype.close.call(this),e.trigger("sharing:closed")},e}(t.getComponent("ModalDialog")),lt=function(t){function e(n,i){R(this,e);var o=B(this,t.call(this,n,i));return o.player=n,o.options=i,o}return N(e,t),e.prototype._createModal=function(){var t=new rt(this.player,this.options).getContent();this.modal=new at(this.player,{content:t,temporary:!0}),this.el=this.modal.contentEl(),this.player.addChild(this.modal)},e.prototype.open=function(){this._createModal(),this.modal.open()},e}(t.getComponent("Component")),st=function(e){function n(i,o){R(this,n);var r=B(this,e.call(this,i));return r.options=t.mergeOptions(P,o),r.player.ready(function(){r.player.addClass("vjs-share"),i.addClass("vjs-videojs-share"),i.getChild("controlBar").addChild("ShareButton",o),i.addChild("ShareOverlay",o)}),r}return N(n,e),n}(t.getPlugin("plugin"));return st.defaultState={},st.VERSION="3.2.0",t.registerComponent("ShareButton",q),t.registerComponent("ShareOverlay",lt),t.registerPlugin("share",st),st}); \ No newline at end of file From f15b7cebac9d9383340a33f4f091badc5d07d2ca Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Wed, 24 Apr 2019 20:18:35 -0500 Subject: [PATCH 096/210] Try to prevent timeout in /data_control --- src/invidious.cr | 30 ++++++++++++++++++++++++++++++ src/invidious/helpers/handlers.cr | 1 + 2 files changed, 31 insertions(+) diff --git a/src/invidious.cr b/src/invidious.cr index 0cabd507..2fe2fb42 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -1792,6 +1792,36 @@ post "/data_control" do |env| if user user = user.as(User) + spawn do + # Since import can take a while, if we're not done after 20 seconds + # push out content to prevent timeout. + + # Interesting to note is that Chrome will try to render before the content has finished loading, + # which is why we include a loading icon. Firefox and its derivatives will not see this page, + # instead redirecting immediately once the connection has closed. + + # https://stackoverflow.com/q/2091239 is helpful but not directly applicable here. + + sleep 20.seconds + env.response.puts %() + env.response.puts %() + env.response.puts %() + if env.get("preferences").as(Preferences).dark_mode + env.response.puts %() + else + env.response.puts %() + end + env.response.puts %(

) + env.response.flush + + loop do + env.response.puts %() + env.response.flush + + sleep (20 + rand(11)).seconds + end + end + HTTP::FormData.parse(env.request) do |part| body = part.body.gets_to_end if body.empty? diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr index e1c43cfe..560ed670 100644 --- a/src/invidious/helpers/handlers.cr +++ b/src/invidious/helpers/handlers.cr @@ -58,6 +58,7 @@ end class FilteredCompressHandler < Kemal::Handler exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/ggpht/*", "/api/v1/auth/notifications"] + exclude ["/data_control"], "POST" def call(env) return call_next env if exclude_match? env From 8c2958b86d0952c176c1df83f2cbaa9adce5e59f Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Thu, 25 Apr 2019 12:41:35 -0500 Subject: [PATCH 097/210] Add 'local=true' to hlsUrl --- src/invidious.cr | 31 ++++++++++++++++++----- src/invidious/views/components/player.ecr | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 2fe2fb42..cf263635 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -4602,13 +4602,21 @@ get "/api/manifest/hls_variant/*" do |env| next end + local = env.params.query["local"]?.try &.== "true" + env.response.content_type = "application/x-mpegURL" env.response.headers.add("Access-Control-Allow-Origin", "*") host_url = make_host_url(config, Kemal.config) manifest = manifest.body - manifest.gsub("https://www.youtube.com", host_url) + + if local + manifest = manifest.gsub("https://www.youtube.com", host_url) + manifest = manifest.gsub("index.m3u8", "index.m3u8?local=true") + end + + manifest end get "/api/manifest/hls_playlist/*" do |env| @@ -4620,15 +4628,24 @@ get "/api/manifest/hls_playlist/*" do |env| next end - host_url = make_host_url(config, Kemal.config) - - manifest = manifest.body.gsub("https://www.youtube.com", host_url) - manifest = manifest.gsub(/https:\/\/r\d---.{11}\.c\.youtube\.com/, host_url) - fvip = manifest.match(/hls_chunk_host\/r(?\d)---/).not_nil!["fvip"] - manifest = manifest.gsub("seg.ts", "seg.ts/fvip/#{fvip}") + local = env.params.query["local"]?.try &.== "true" env.response.content_type = "application/x-mpegURL" env.response.headers.add("Access-Control-Allow-Origin", "*") + + host_url = make_host_url(config, Kemal.config) + + manifest = manifest.body + + if local + manifest = manifest.gsub("https://www.youtube.com", host_url) + manifest = manifest.gsub(/https:\/\/r\d---.{11}\.c\.youtube\.com/, host_url) + manifest = manifest.gsub("seg.ts", "seg.ts?local=true") + end + + fvip = manifest.match(/hls_chunk_host\/r(?\d+)---/).not_nil!["fvip"] + manifest = manifest.gsub("seg.ts", "seg.ts/fvip/#{fvip}") + manifest end diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr index 30f5294f..bdcf999e 100644 --- a/src/invidious/views/components/player.ecr +++ b/src/invidious/views/components/player.ecr @@ -7,7 +7,7 @@ <% if params[:video_loop] %>loop<% end %> <% if params[:controls] %>controls<% end %>> <% if hlsvp %> - + <% else %> <% if params[:listen] %> <% audio_streams.each_with_index do |fmt, i| %> From 5ce72a346189fb3c2fd70bd1fb1299e01c8844e1 Mon Sep 17 00:00:00 2001 From: Esmail EL BoB Date: Thu, 25 Apr 2019 18:09:38 +0000 Subject: [PATCH 098/210] Updated most of ar.json (#508) * Update ar.json --- locales/ar.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 695a3a61..0be55904 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -9,15 +9,15 @@ "newest": "الأجدد", "oldest": "الأقدم", "popular": "الاكثر شعبية", - "last": "اخر الفيديوهات المعدلة", + "last": "اخر قوائم التشغيل المعدلة", "Next page": "الصفحة الثانية", "Previous page": "الصفحة السابقة", "Clear watch history?": "مسح السجل ؟", - "New password": "", - "New passwords must match": "", - "Cannot change password for Google accounts": "", - "Authorize token?": "", - "Authorize token for `x`?": "", + "New password": "الرقم السرى الجديد", + "New passwords must match": "الأرقام السرية يجب ان تكون متطابقة", + "Cannot change password for Google accounts": "لا يستطيع تغيير الرقم السرى لحساب جوجل", + "Authorize token?": "رمز الإذن ؟", + "Authorize token for `x`?": "رمز الإذن لـ 'x' ?", "Yes": "نعم", "No": "لا", "Import and Export Data": "استخراج و إضافة البيانات", @@ -85,9 +85,9 @@ "Data preferences": "إعدادات التفضيلات", "Clear watch history": "حذف سجل المشاهدة", "Import/export data": "إضافة\\إستخراج البيانات", - "Change password": "", + "Change password": "غير الرقم السرى", "Manage subscriptions": "إدارة المشتركين", - "Manage tokens": "", + "Manage tokens": "إدارة الرموز", "Watch history": "سجل المشاهدة", "Delete account": "حذف الحساب", "Administrator preferences": "إعدادات المدير", @@ -100,13 +100,13 @@ "Report statistics? ": "إبلاغ الإحصائيات", "Save preferences": "حفظ التفضيلات", "Subscription manager": "مدير الإشتراكات", - "Token manager": "", - "Token": "", + "Token manager": "إداره الرمز", + "Token": "الرمز", "`x` subscriptions": "`x` مشتركين", - "`x` tokens": "", + "`x` tokens": "'x' رموز", "Import/export": "إضافة\\إستخراج", "unsubscribe": "إلغاء الإشتراك", - "revoke": "", + "revoke": "مسح", "Subscriptions": "الإشتراكات", "`x` unseen notifications": "`x` إشعارات لم تشاهدها بعد ", "search": "بحث", @@ -126,7 +126,7 @@ "Whitelisted regions: ": "الدول المسموح فيها هذا الفيديو: ", "Blacklisted regions: ": "الدول الحظور فيها هذا الفيديو: ", "Shared `x`": "شارك منذ `x`", - "`x` views": "", + "`x` views": "'x' مشاهدون", "Premieres in `x`": "يعرض فى 'x'", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "اهلا! يبدو ان الجافاسكريبت معطلة. اضغط هنا لعرض التعليقات, ضع فى إعتبارك انها ستأخذ وقت اطول للعرض.", "View YouTube comments": "عرض تعليقات اليوتيوب", @@ -290,7 +290,7 @@ "About": "حول", "Rating: ": "التقييم", "Language: ": "اللغة", - "View as playlist": "", + "View as playlist": "عرض كا قائمة التشغيل", "Default": "الكل", "Music": "الاغانى", "Gaming": "الألعاب", @@ -307,4 +307,4 @@ "Videos": "الفيديوهات", "Playlists": "قوائم التشغيل", "Current version: ": "الإصدار الحالى" -} \ No newline at end of file +} From 075adb4f0371d85b74b6ced3e27ee9696a0e379b Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Sat, 23 Mar 2019 13:31:07 -0500 Subject: [PATCH 099/210] Add http-source-selector --- assets/css/default.css | 7 +- assets/css/video-js.min.css | 2 +- assets/css/videojs-http-source-selector.css | 7 + assets/js/dash.mediaplayer.min.js | 28 - .../js/silvermine-videojs-quality-selector.js | 1979 ----------------- assets/js/video.min.js | 5 +- .../js/videojs-contrib-quality-levels.min.js | 4 +- assets/js/videojs-dash.min.js | 3 - assets/js/videojs-http-source-selector.min.js | 7 + assets/js/videojs-http-streaming.min.js | 14 - src/invidious/views/components/player.ecr | 11 +- .../views/components/player_sources.ecr | 14 +- src/invidious/views/licenses.ecr | 48 +- 13 files changed, 50 insertions(+), 2079 deletions(-) create mode 100644 assets/css/videojs-http-source-selector.css delete mode 100644 assets/js/dash.mediaplayer.min.js delete mode 100644 assets/js/silvermine-videojs-quality-selector.js delete mode 100644 assets/js/videojs-dash.min.js create mode 100644 assets/js/videojs-http-source-selector.min.js delete mode 100644 assets/js/videojs-http-streaming.min.js diff --git a/assets/css/default.css b/assets/css/default.css index e6d333bf..b536b75e 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -279,7 +279,8 @@ input[type="search"]::-webkit-search-cancel-button { order: 2; } -.vjs-quality-selector { +.vjs-quality-selector, +.video-js .vjs-http-source-selector { order: 3; } @@ -300,6 +301,10 @@ input[type="search"]::-webkit-search-cancel-button { flex-direction: row; } +.video-js .vjs-icon-cog { + font-size: 18px; +} + .video-js .vjs-control-bar, .vjs-menu-button-popup .vjs-menu .vjs-menu-content { background-color: rgba(35, 35, 35, 0.75); diff --git a/assets/css/video-js.min.css b/assets/css/video-js.min.css index 980f15ee..4884bdf8 100644 --- a/assets/css/video-js.min.css +++ b/assets/css/video-js.min.css @@ -1 +1 @@ -.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-modal-dialog,.vjs-button>.vjs-icon-placeholder:before,.vjs-modal-dialog .vjs-modal-dialog-content{position:absolute;top:0;left:0;width:100%;height:100%}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.vjs-button>.vjs-icon-placeholder:before{text-align:center}@font-face{font-family:VideoJS;src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAABBIAAsAAAAAGoQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAAPgAAAFZRiV3RY21hcAAAAYQAAADQAAADIjn098ZnbHlmAAACVAAACv4AABEIAwnSw2hlYWQAAA1UAAAAKwAAADYV1OgpaGhlYQAADYAAAAAbAAAAJA4DByFobXR4AAANnAAAAA8AAACE4AAAAGxvY2EAAA2sAAAARAAAAEQ9NEHGbWF4cAAADfAAAAAfAAAAIAEyAIFuYW1lAAAOEAAAASUAAAIK1cf1oHBvc3QAAA84AAABDwAAAZ5AAl/0eJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGQ7xTiBgZWBgaWQ5RkDA8MvCM0cwxDOeI6BgYmBlZkBKwhIc01hcPjI+FGBHcRdyA4RZgQRAC4HCwEAAHic7dFprsIgAEXhg8U61XmeWcBb1FuQP4w7ZQXK5boMm3yclFDSANAHmuKviBBeBPQ8ymyo8w3jOh/5r2ui5nN6v8sYNJb3WMdeWRvLji0DhozKdxM6psyYs2DJijUbtuzYc+DIiTMXrty4k8oGLb+n0xCe37ekM7Z66j1DbUy3l6PpHnLfdLO5NdSBoQ4NdWSoY9ON54mhdqa/y1NDnRnq3FAXhro01JWhrg11Y6hbQ90Z6t5QD4Z6NNSToZ4N9WKoV0O9GerdUJORPqkhTd54nJ1YDXBU1RV+576/JBs2bPYPkrDZt5vsJrv53V/I5mclhGDCTwgGBQQSTEji4hCkYIAGd4TGIWFAhV0RQTpWmQp1xv6hA4OTOlNr2zFANbHUYbq2OtNCpViRqsk+e+7bTQAhzti8vPfuPffcc88959zznbcMMPjHD/KDDGEY0ABpYX384NhlomIYlo4JISGEY9mMh2FSidYiqkEUphtNYDSY/dXg9023l4DdxlqUl0chuZRhncJKrsCQHIwcGuwfnhMIzBnuH4Sym+1D2zaGjheXlhYfD238z80mKYMmvJ5XeOTzd8z9eujbMxJNhu4C9xPE/bCMiDuSNIWgkTQwBE55hLSAE7ZwhrHLnAHZOGV/kmBGTiNjZxzI77Hb7Hqjz68TjT6vh+5JT/cCIkqS0D6CqPf5jX4Qjdx5j6vlDfZM4aZFdbVXIxtOlJaP/WottMnH6CJQ3bTiue3PrY23HjnChtuamxwvvzFjxkPrNj3z0tG9T561HDYf6OgmRWvlY3JQHoQb8ltV2Yet7YfWctEjR1AtxS/cSX6U4alf6NJEBQ7YKg9wrXQKd0IeZCb2ux75Uhh1Un+Nz+9LTOE7PK777nN5xqdTneTBhCbx446mZrhnUkrCz2YhA9dSMxaG0SYmT8hi9ZPu1E94PJYQSH6LRmhxec7Q7ZeXntgQuVpbh+a4qWNsckVyTdn0P7o7DpgPW84+uRcq0BITflBikGdUjAZ9wYBVI3mtrNvr9kpg1UsaK6t3690aoorC1lg0GpMH2HAMtkZjsSi5Ig9ESVosOh7GQfLjKNLvKpMKkLSKNFAka710GdgSi8oDMSoNhqjkKBXTgn3swtaxyzGkUzIzae9RtLdWkSlZ1KDX6EzgllzV4NV4SoDFSOGD4+HCeQUF8wrZ5Hs8zIb5EaVxy8DYFTbMCJPnLIWZxugZE2NlivC0gc1qEQUR8jEKgZcAXeH18BiCgl5nlHh0CrjB4Hb5fX4gb0J7c9PuHVsfgkx2n/vTY/JV8kn8PGxf7faOZ8qX8JVByuIf4whk9sqXli2hvPJV9hrp0hY7l8r2x37ydaVsb4xvXv/47v2NjfCl8m5oRDJclFMoE1yk0Uh1Te4/m8lFXe9qBZD0EkheicebXvzI2PLCuoKCukLuhPIeKwaHPEouxw3kMqaIUXDQ1p0mip+MyCORSCQaoUsnY1VZ38nUTrG21WvVo4f1OsEJFhvSfAFwGfT8VHRMeAVUpwLOoLzjT/REIj3O3FhuURE+nERF+0pTId5Fyxv5sfwGyg4O+my4vZv0sZm7oeQlFZORiB+tG0MweVNraeitl7yxiPIHTk4/diVxs94o5lEYishB2iAtkchEnsActoEpx44Fo8XnsQMaA22BlqC20RmhBKzYojZyYaxg+JggMc4HHY2m+L9EkWSYljirOisrO7d3VorxzyZ6Vc4lJqITAu1b2wOBdrLElAP+bFc2eGaZFVbkmJktv5uT6Jlz5D/MnBFor6ig/JPnRViBsV3LNKGGqB1ChJ0tgQywlVLFJIuQgTFttwkiKxhyQdAZMdMYtSaoAewqfvXVYPAbDT6/1mez85YS8FSDywQ6NfAnef6FNEGMilnppyvn5rB6tTyq1pOceRWnp2WJEZFXHeX5oyoem1nTTgdqc4heDY7bOeKz63vnz+/dRx+s31Ht2JGanQ5seirfWJL9tjozU/12TnEjn5oux9OzU3ckGbBzBwNOyk69JykKH0n/0LM9A72tuwM3zQpIRu4AxiToseEpgPOmbROyFe9/X2yeUvoUsCyEvjcgs7fpWP3/aKlFN0+6HFUe6D9HFz/XPwBlN9tTqNyZjFJ8UO2RUT5/h4CptCctEyeisnOyXjALEp7dXKaQKf6O7IMnGjNNACRMLxqdYJX8eMLvmmd68D+ayBLyKKYZwYxDt/GNhzETDJ05Qxlyi3pi3/Z93ndYVSumgj0V/KkIFlO6+1K3fF2+3g0q+YtuSIf0bvmLqV09nnobI6hwcjIP8aPCKayjsF5JBY3LaKAeRLSyYB1h81oTwe9SlPMkXB7G0mfL9q71gaqqwPqu67QRKS1+ObTx+sbQy9QV2OQHEScGkdFBeT7v7qisqqrs6N52i78/R+6S0qQONVj26agOVoswCyQWIV5D86vH53bxNUeXV0K+XZaHv/nm/KsHhOvylwsWnJX/HE8l/4WCv5x+l5n08z6UU8bUMa3MBpSmM7F63AxntdC9eBCKEZW9Hr+ABNqtxgAQrSbMtmrW7lKQuoSgBhSrTazWVU2QAKWY8wiiuhqFmQgWJBgoXiuWIm42N7hqZbBsgXz52O5P5uSvaNgFGnOuvsRw8I8Laha91wMvDuxqWFheN7/8GVtTltdS83DQsXRmqc5ZtcJXEVrlV2doTWk5+Yunm71dG5f55m/qY0MjI93vv9/NfpxXV9sUXrxy2fbNy1or65cOlDRnOoKFeeXcbw42H/bNDT5Qs3flgs31gWC1lD1nfUV/X7NdCnSUdHY2e8afzfKsqZ5ZljfDqjLOmk3UebNXB+aHArPYDRs+/HDDxeT5DiP+sFg7OpRaVQMGBV89PpeBdj22hCE0Uub0UqwLrNWsG0cuyadgLXTeR5rbO4+3c/vl15cur2nRq+TXCQDcS3SO+s6ak+e5/eMS+1dw3btu3YG2tvFL8XdIZvdjdW6TO/4B7IdrZWVPmctm5/59AgsPItTSbCiIBr2OqIGzmu20SMKAS7yqwGBUfGfgjDYlLLDeF0SfcLB2LSx8flT+08/kzz6yOj96rft4rpTjdPQcmLd47uKibbDq7ZSz/XtbH2nN717Nd62rU+c8Icevvv7I09wA6WvjVcafb+FsbNG+ZQ80Rn6ZZsvrP7teP2dzTdoETvNhjCmsr8FID2sJ69VYvdUcxk4AzYRlKcaE38eXNRlfW9H1as9i6acLHp1XpuNB5K7DIvkX08y1ZYvh3KfWaiCzH+ztrSDmD7LuX73x/mJelB8Yj39t8nhNQJJ2CAthpoFGLsGgtSOCJooCGoaJAMTjSWHVZ08YAa1Fg9lPI5U6DOsGVjDasJeZZ+YyhfCwfOzCxlBA69M9XLXtza7H/rav+9Tjq5xNi0wpKQIRNO4Lrzz7yp5QVYM6Jd/oc1Uvn/mQhhuWh6ENXoS2YTZ8QT42bF5d/559zp5r0Uff2VnR2tdf2/WCOd2cO0Mw6qpWPnvxpV0nrt5fZd2yItc199GWe8vlNfNDq+CH/7yAAnB9hn7T4QO4c1g9ScxsZgmzntnE/IDGndtHMw69lFwoCnYsMGx+rBp8JSBqdLzBr9QRPq/PbhWMWFtQZp1xguy/haw3TEHm3TWAnxFWQQWgt7M5OV0lCz1VRYucpWliy7z6Zd4urwPIyeZQqli2Lgg7szJV09PysATbOQtYIrB2YzbkJYkGgJ0m4AjPUap1pvYu1K9qr97z0Yl3p332b2LYB78ncYIlRkau/8GObSsOlZancACE5d5ily+c2+7h5Yj4lqhVmXXB+iXLfvdqSgqfKtQvfHDV0OnvQR1qhw42XS/vkvsh/hXcrDFP0a+SJNIomEfD1nsrYGO+1bgTOJhM8Hv6ek+7vVglxuSRwoKn17S937bm6YJCeSSG0Op1n+7tE37tcZ/p7dsTv4EUrGpDbWueKigsLHhqTVsoEj+JU0kaSjnj9tz8/gryQWwJ9BcJXBC/7smO+I/IFURJetFPrdt5WcoL6DbEJaygI8CTHfQTjf40ofD+DwalTqIAAHicY2BkYGAA4gDud4bx/DZfGbjZGUDg+q1z05BpdkawOAcDE4gCAB45CXEAeJxjYGRgYGcAARD5/z87IwMjAypQBAAtgwI4AHicY2BgYGAfYAwAOkQA4QAAAAAAAA4AaAB+AMwA4AECAUIBbAGYAcICGAJYArQC4AMwA7AD3gQwBJYE3AUkBWYFigYgBmYGtAbqB1gIEghYCG4IhHicY2BkYGBQZChlYGcAASYg5gJCBob/YD4DABfTAbQAeJxdkE1qg0AYhl8Tk9AIoVDaVSmzahcF87PMARLIMoFAl0ZHY1BHdBJIT9AT9AQ9RQ9Qeqy+yteNMzDzfM+88w0K4BY/cNAMB6N2bUaPPBLukybCLvleeAAPj8JD+hfhMV7hC3u4wxs7OO4NzQSZcI/8Ltwnfwi75E/hAR7wJTyk/xYeY49fYQ/PztM+jbTZ7LY6OWdBJdX/pqs6NYWa+zMxa13oKrA6Uoerqi/JwtpYxZXJ1coUVmeZUWVlTjq0/tHacjmdxuL90OR8O0UEDYMNdtiSEpz5XQGqzlm30kzUdAYFFOb8R7NOZk0q2lwAyz1i7oAr1xoXvrOgtYhZx8wY5KRV269JZ5yGpmzPTjQhvY9je6vEElPOuJP3mWKnP5M3V+YAAAB4nG2PyXLCMBBE3YCNDWEL2ffk7o8S8oCnkCVHC5C/jzBQlUP6IHVPzYyekl5y0iL5X5/ooY8BUmQYIkeBEca4wgRTzDDHAtdY4ga3uMM9HvCIJzzjBa94wzs+8ImvZNAq8TM+HqVkKxWlrQiOxjujQkNlEzyNzl6Z/cU2XF06at7U83VQyklLpEvSnuzsb+HAPnPfQVgaupa1Jlu4sPLsFblcitaz0dHU0ZF1qatjZ1+aTXYCmp6u0gSvWNPyHLtFZ+ZeXWVSaEkqs3T8S74WklbGbNNNq4LL4+CWKtZDv2cfX8l8aFbKFhEnJnJ+IULFpqwoQnNHlHaVQtPBl+ypmbSWdmyC61KS/AKZC3Y+AA==) format("woff");font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder,.vjs-icon-play{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder:before,.vjs-icon-play:before{content:"\f101"}.vjs-icon-play-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-play-circle:before{content:"\f102"}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder,.vjs-icon-pause{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before,.vjs-icon-pause:before{content:"\f103"}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder,.vjs-icon-volume-mute{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder:before,.vjs-icon-volume-mute:before{content:"\f104"}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder,.vjs-icon-volume-low{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder:before,.vjs-icon-volume-low:before{content:"\f105"}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder,.vjs-icon-volume-mid{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder:before,.vjs-icon-volume-mid:before{content:"\f106"}.video-js .vjs-mute-control .vjs-icon-placeholder,.vjs-icon-volume-high{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control .vjs-icon-placeholder:before,.vjs-icon-volume-high:before{content:"\f107"}.video-js .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-enter{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-enter:before{content:"\f108"}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-exit{font-family:VideoJS;font-weight:400;font-style:normal}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-exit:before{content:"\f109"}.vjs-icon-square{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-square:before{content:"\f10a"}.vjs-icon-spinner{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-spinner:before{content:"\f10b"}.video-js .vjs-subs-caps-button .vjs-icon-placeholder,.video-js .vjs-subtitles-button .vjs-icon-placeholder,.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder,.vjs-icon-subtitles{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js .vjs-subtitles-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder:before,.vjs-icon-subtitles:before{content:"\f10c"}.video-js .vjs-captions-button .vjs-icon-placeholder,.video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder,.vjs-icon-captions{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-captions-button .vjs-icon-placeholder:before,.video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder:before,.vjs-icon-captions:before{content:"\f10d"}.video-js .vjs-chapters-button .vjs-icon-placeholder,.vjs-icon-chapters{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-chapters-button .vjs-icon-placeholder:before,.vjs-icon-chapters:before{content:"\f10e"}.vjs-icon-share{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-share:before{content:"\f10f"}.vjs-icon-cog{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-cog:before{content:"\f110"}.video-js .vjs-play-progress,.video-js .vjs-volume-level,.vjs-icon-circle,.vjs-seek-to-live-control .vjs-icon-placeholder{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-progress:before,.video-js .vjs-volume-level:before,.vjs-icon-circle:before,.vjs-seek-to-live-control .vjs-icon-placeholder:before{content:"\f111"}.vjs-icon-circle-outline{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-outline:before{content:"\f112"}.vjs-icon-circle-inner-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-inner-circle:before{content:"\f113"}.vjs-icon-hd{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-hd:before{content:"\f114"}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder,.vjs-icon-cancel{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder:before,.vjs-icon-cancel:before{content:"\f115"}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder,.vjs-icon-replay{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder:before,.vjs-icon-replay:before{content:"\f116"}.vjs-icon-facebook{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-facebook:before{content:"\f117"}.vjs-icon-gplus{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-gplus:before{content:"\f118"}.vjs-icon-linkedin{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-linkedin:before{content:"\f119"}.vjs-icon-twitter{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-twitter:before{content:"\f11a"}.vjs-icon-tumblr{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-tumblr:before{content:"\f11b"}.vjs-icon-pinterest{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-pinterest:before{content:"\f11c"}.video-js .vjs-descriptions-button .vjs-icon-placeholder,.vjs-icon-audio-description{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-descriptions-button .vjs-icon-placeholder:before,.vjs-icon-audio-description:before{content:"\f11d"}.video-js .vjs-audio-button .vjs-icon-placeholder,.vjs-icon-audio{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-audio-button .vjs-icon-placeholder:before,.vjs-icon-audio:before{content:"\f11e"}.vjs-icon-next-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-next-item:before{content:"\f11f"}.vjs-icon-previous-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-previous-item:before{content:"\f120"}.video-js{display:block;vertical-align:top;box-sizing:border-box;color:#fff;background-color:#000;position:relative;padding:0;font-size:10px;line-height:1;font-weight:400;font-style:normal;font-family:Arial,Helvetica,sans-serif;word-break:initial}.video-js:-moz-full-screen{position:absolute}.video-js:-webkit-full-screen{width:100%!important;height:100%!important}.video-js[tabindex="-1"]{outline:0}.video-js *,.video-js :after,.video-js :before{box-sizing:inherit}.video-js ul{font-family:inherit;font-size:inherit;line-height:inherit;list-style-position:outside;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0}.video-js.vjs-16-9,.video-js.vjs-4-3,.video-js.vjs-fluid{width:100%;max-width:100%;height:0}.video-js.vjs-16-9{padding-top:56.25%}.video-js.vjs-4-3{padding-top:75%}.video-js.vjs-fill{width:100%;height:100%}.video-js .vjs-tech{position:absolute;top:0;left:0;width:100%;height:100%}body.vjs-full-window{padding:0;margin:0;height:100%}.vjs-full-window .video-js.vjs-fullscreen{position:fixed;overflow:hidden;z-index:1000;left:0;top:0;bottom:0;right:0}.video-js.vjs-fullscreen{width:100%!important;height:100%!important;padding-top:0!important}.video-js.vjs-fullscreen.vjs-user-inactive{cursor:none}.vjs-hidden{display:none!important}.vjs-disabled{opacity:.5;cursor:default}.video-js .vjs-offscreen{height:1px;left:-9999px;position:absolute;top:0;width:1px}.vjs-lock-showing{display:block!important;opacity:1;visibility:visible}.vjs-no-js{padding:20px;color:#fff;background-color:#000;font-size:18px;font-family:Arial,Helvetica,sans-serif;text-align:center;width:300px;height:150px;margin:0 auto}.vjs-no-js a,.vjs-no-js a:visited{color:#66a8cc}.video-js .vjs-big-play-button{font-size:3em;line-height:1.5em;height:1.5em;width:3em;display:block;position:absolute;top:10px;left:10px;padding:0;cursor:pointer;opacity:1;border:.06666em solid #fff;background-color:#2b333f;background-color:rgba(43,51,63,.7);border-radius:.3em;transition:all .4s}.vjs-big-play-centered .vjs-big-play-button{top:50%;left:50%;margin-top:-.75em;margin-left:-1.5em}.video-js .vjs-big-play-button:focus,.video-js:hover .vjs-big-play-button{border-color:#fff;background-color:#73859f;background-color:rgba(115,133,159,.5);transition:all 0s}.vjs-controls-disabled .vjs-big-play-button,.vjs-error .vjs-big-play-button,.vjs-has-started .vjs-big-play-button,.vjs-using-native-controls .vjs-big-play-button{display:none}.vjs-has-started.vjs-paused.vjs-show-big-play-button-on-pause .vjs-big-play-button{display:block}.video-js button{background:0 0;border:none;color:inherit;display:inline-block;font-size:inherit;line-height:inherit;text-transform:none;text-decoration:none;transition:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.vjs-control .vjs-button{width:100%;height:100%}.video-js .vjs-control.vjs-close-button{cursor:pointer;height:3em;position:absolute;right:0;top:.5em;z-index:2}.video-js .vjs-modal-dialog{background:rgba(0,0,0,.8);background:linear-gradient(180deg,rgba(0,0,0,.8),rgba(255,255,255,0));overflow:auto}.video-js .vjs-modal-dialog>*{box-sizing:border-box}.vjs-modal-dialog .vjs-modal-dialog-content{font-size:1.2em;line-height:1.5;padding:20px 24px;z-index:1}.vjs-menu-button{cursor:pointer}.vjs-menu-button.vjs-disabled{cursor:default}.vjs-workinghover .vjs-menu-button.vjs-disabled:hover .vjs-menu{display:none}.vjs-menu .vjs-menu-content{display:block;padding:0;margin:0;font-family:Arial,Helvetica,sans-serif;overflow:auto}.vjs-menu .vjs-menu-content>*{box-sizing:border-box}.vjs-scrubbing .vjs-control.vjs-menu-button:hover .vjs-menu{display:none}.vjs-menu li{list-style:none;margin:0;padding:.2em 0;line-height:1.4em;font-size:1.2em;text-align:center;text-transform:lowercase}.js-focus-visible .vjs-menu li.vjs-menu-item:hover,.vjs-menu li.vjs-menu-item:focus,.vjs-menu li.vjs-menu-item:hover{background-color:#73859f;background-color:rgba(115,133,159,.5)}.js-focus-visible .vjs-menu li.vjs-selected:hover,.vjs-menu li.vjs-selected,.vjs-menu li.vjs-selected:focus,.vjs-menu li.vjs-selected:hover{background-color:#fff;color:#2b333f}.vjs-menu li.vjs-menu-title{text-align:center;text-transform:uppercase;font-size:1em;line-height:2em;padding:0;margin:0 0 .3em 0;font-weight:700;cursor:default}.vjs-menu-button-popup .vjs-menu{display:none;position:absolute;bottom:0;width:10em;left:-3em;height:0;margin-bottom:1.5em;border-top-color:rgba(43,51,63,.7)}.vjs-menu-button-popup .vjs-menu .vjs-menu-content{background-color:#2b333f;background-color:rgba(43,51,63,.7);position:absolute;width:100%;bottom:1.5em;max-height:15em}.vjs-menu-button-popup .vjs-menu.vjs-lock-showing,.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu{display:block}.video-js .vjs-menu-button-inline{transition:all .4s;overflow:hidden}.video-js .vjs-menu-button-inline:before{width:2.222222222em}.video-js .vjs-menu-button-inline.vjs-slider-active,.video-js .vjs-menu-button-inline:focus,.video-js .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline{width:12em}.vjs-menu-button-inline .vjs-menu{opacity:0;height:100%;width:auto;position:absolute;left:4em;top:0;padding:0;margin:0;transition:all .4s}.vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-menu-button-inline:focus .vjs-menu,.vjs-menu-button-inline:hover .vjs-menu{display:block;opacity:1}.vjs-no-flex .vjs-menu-button-inline .vjs-menu{display:block;opacity:1;position:relative;width:auto}.vjs-no-flex .vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:focus .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:hover .vjs-menu{width:auto}.vjs-menu-button-inline .vjs-menu-content{width:auto;height:100%;margin:0;overflow:hidden}.video-js .vjs-control-bar{display:none;width:100%;position:absolute;bottom:0;left:0;right:0;height:3em;background-color:#2b333f;background-color:rgba(43,51,63,.7)}.vjs-has-started .vjs-control-bar{display:flex;visibility:visible;opacity:1;transition:visibility .1s,opacity .1s}.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:visible;opacity:0;transition:visibility 1s,opacity 1s}.vjs-controls-disabled .vjs-control-bar,.vjs-error .vjs-control-bar,.vjs-using-native-controls .vjs-control-bar{display:none!important}.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{opacity:1;visibility:visible}.vjs-has-started.vjs-no-flex .vjs-control-bar{display:table}.video-js .vjs-control{position:relative;text-align:center;margin:0;padding:0;height:100%;width:4em;flex:none}.vjs-button>.vjs-icon-placeholder:before{font-size:1.8em;line-height:1.67}.video-js .vjs-control:focus,.video-js .vjs-control:focus:before,.video-js .vjs-control:hover:before{text-shadow:0 0 1em #fff}.video-js .vjs-control-text{border:0;clip:rect(0 0 0 0);height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.vjs-no-flex .vjs-control{display:table-cell;vertical-align:middle}.video-js .vjs-custom-control-spacer{display:none}.video-js .vjs-progress-control{cursor:pointer;flex:auto;display:flex;align-items:center;min-width:4em;touch-action:none}.video-js .vjs-progress-control.disabled{cursor:default}.vjs-live .vjs-progress-control{display:none}.vjs-liveui .vjs-progress-control{display:flex;align-items:center}.vjs-no-flex .vjs-progress-control{width:auto}.video-js .vjs-progress-holder{flex:auto;transition:all .2s;height:.3em}.video-js .vjs-progress-control .vjs-progress-holder{margin:0 10px}.video-js .vjs-progress-control:hover .vjs-progress-holder{font-size:1.666666666666666666em}.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled{font-size:1em}.video-js .vjs-progress-holder .vjs-load-progress,.video-js .vjs-progress-holder .vjs-load-progress div,.video-js .vjs-progress-holder .vjs-play-progress{position:absolute;display:block;height:100%;margin:0;padding:0;width:0}.video-js .vjs-play-progress{background-color:#fff}.video-js .vjs-play-progress:before{font-size:.9em;position:absolute;right:-.5em;top:-.333333333333333em;z-index:1}.video-js .vjs-load-progress{background:rgba(115,133,159,.5)}.video-js .vjs-load-progress div{background:rgba(115,133,159,.75)}.video-js .vjs-time-tooltip{background-color:#fff;background-color:rgba(255,255,255,.8);border-radius:.3em;color:#000;float:right;font-family:Arial,Helvetica,sans-serif;font-size:1em;padding:6px 8px 8px 8px;pointer-events:none;position:absolute;top:-3.4em;visibility:hidden;z-index:1}.video-js .vjs-progress-holder:focus .vjs-time-tooltip{display:none}.video-js .vjs-progress-control:hover .vjs-progress-holder:focus .vjs-time-tooltip,.video-js .vjs-progress-control:hover .vjs-time-tooltip{display:block;font-size:.6em;visibility:visible}.video-js .vjs-progress-control.disabled:hover .vjs-time-tooltip{font-size:1em}.video-js .vjs-progress-control .vjs-mouse-display{display:none;position:absolute;width:1px;height:100%;background-color:#000;z-index:1}.vjs-no-flex .vjs-progress-control .vjs-mouse-display{z-index:0}.video-js .vjs-progress-control:hover .vjs-mouse-display{display:block}.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display{visibility:hidden;opacity:0;transition:visibility 1s,opacity 1s}.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display{display:none}.vjs-mouse-display .vjs-time-tooltip{color:#fff;background-color:#000;background-color:rgba(0,0,0,.8)}.video-js .vjs-slider{position:relative;cursor:pointer;padding:0;margin:0 .45em 0 .45em;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#73859f;background-color:rgba(115,133,159,.5)}.video-js .vjs-slider.disabled{cursor:default}.video-js .vjs-slider:focus{text-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.video-js .vjs-mute-control{cursor:pointer;flex:none}.video-js .vjs-volume-control{cursor:pointer;margin-right:1em;display:flex}.video-js .vjs-volume-control.vjs-volume-horizontal{width:5em}.video-js .vjs-volume-panel .vjs-volume-control{visibility:visible;opacity:0;width:1px;height:1px;margin-left:-1px}.video-js .vjs-volume-panel{transition:width 1s}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active,.video-js .vjs-volume-panel .vjs-volume-control:active,.video-js .vjs-volume-panel .vjs-volume-control:hover,.video-js .vjs-volume-panel:active .vjs-volume-control,.video-js .vjs-volume-panel:focus .vjs-volume-control,.video-js .vjs-volume-panel:hover .vjs-volume-control{visibility:visible;opacity:1;position:relative;transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-horizontal,.video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical,.video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical{left:-3.5em}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover{width:9em;transition:width .1s}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-mute-toggle-only{width:4em}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{height:8em;width:3em;left:-3000em;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s}.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em;visibility:visible;opacity:1;position:relative;transition:none}.video-js.vjs-no-flex .vjs-volume-control.vjs-volume-vertical,.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{position:absolute;bottom:3em;left:.5em}.video-js .vjs-volume-panel{display:flex}.video-js .vjs-volume-bar{margin:1.35em .45em}.vjs-volume-bar.vjs-slider-horizontal{width:5em;height:.3em}.vjs-volume-bar.vjs-slider-vertical{width:.3em;height:5em;margin:1.35em auto}.video-js .vjs-volume-level{position:absolute;bottom:0;left:0;background-color:#fff}.video-js .vjs-volume-level:before{position:absolute;font-size:.9em}.vjs-slider-vertical .vjs-volume-level{width:.3em}.vjs-slider-vertical .vjs-volume-level:before{top:-.5em;left:-.3em}.vjs-slider-horizontal .vjs-volume-level{height:.3em}.vjs-slider-horizontal .vjs-volume-level:before{top:-.3em;right:-.5em}.video-js .vjs-volume-panel.vjs-volume-panel-vertical{width:4em}.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level{height:100%}.vjs-volume-bar.vjs-slider-horizontal .vjs-volume-level{width:100%}.video-js .vjs-volume-vertical{width:3em;height:8em;bottom:8em;background-color:#2b333f;background-color:rgba(43,51,63,.7)}.video-js .vjs-volume-horizontal .vjs-menu{left:-2em}.vjs-poster{display:inline-block;vertical-align:middle;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;background-color:#000;cursor:pointer;margin:0;padding:0;position:absolute;top:0;right:0;bottom:0;left:0;height:100%}.vjs-has-started .vjs-poster{display:none}.vjs-audio.vjs-has-started .vjs-poster{display:block}.vjs-using-native-controls .vjs-poster{display:none}.video-js .vjs-live-control{display:flex;align-items:flex-start;flex:auto;font-size:1em;line-height:3em}.vjs-no-flex .vjs-live-control{display:table-cell;width:auto;text-align:left}.video-js.vjs-liveui .vjs-live-control,.video-js:not(.vjs-live) .vjs-live-control{display:none}.video-js .vjs-seek-to-live-control{cursor:pointer;flex:none;display:inline-flex;height:100%;padding-left:.5em;padding-right:.5em;font-size:1em;line-height:3em;width:auto;min-width:4em}.vjs-no-flex .vjs-seek-to-live-control{display:table-cell;width:auto;text-align:left}.video-js.vjs-live:not(.vjs-liveui) .vjs-seek-to-live-control,.video-js:not(.vjs-live) .vjs-seek-to-live-control{display:none}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge{cursor:auto}.vjs-seek-to-live-control .vjs-icon-placeholder{margin-right:.5em;color:#888}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge .vjs-icon-placeholder{color:red}.video-js .vjs-time-control{flex:none;font-size:1em;line-height:3em;min-width:2em;width:auto;padding-left:1em;padding-right:1em}.vjs-live .vjs-time-control{display:none}.video-js .vjs-current-time,.vjs-no-flex .vjs-current-time{display:none}.video-js .vjs-duration,.vjs-no-flex .vjs-duration{display:none}.vjs-time-divider{display:none;line-height:3em}.vjs-live .vjs-time-divider{display:none}.video-js .vjs-play-control{cursor:pointer}.video-js .vjs-play-control .vjs-icon-placeholder{flex:none}.vjs-text-track-display{position:absolute;bottom:3em;left:0;right:0;top:0;pointer-events:none}.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display{bottom:1em}.video-js .vjs-text-track{font-size:1.4em;text-align:center;margin-bottom:.1em}.vjs-subtitles{color:#fff}.vjs-captions{color:#fc6}.vjs-tt-cue{display:block}video::-webkit-media-text-track-display{-webkit-transform:translateY(-3em);transform:translateY(-3em)}.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display{-webkit-transform:translateY(-1.5em);transform:translateY(-1.5em)}.video-js .vjs-fullscreen-control{cursor:pointer;flex:none}.vjs-playback-rate .vjs-playback-rate-value,.vjs-playback-rate>.vjs-menu-button{position:absolute;top:0;left:0;width:100%;height:100%}.vjs-playback-rate .vjs-playback-rate-value{pointer-events:none;font-size:1.5em;line-height:2;text-align:center}.vjs-playback-rate .vjs-menu{width:4em;left:0}.vjs-error .vjs-error-display .vjs-modal-dialog-content{font-size:1.4em;text-align:center}.vjs-error .vjs-error-display:before{color:#fff;content:'X';font-family:Arial,Helvetica,sans-serif;font-size:4em;left:0;line-height:1;margin-top:-.5em;position:absolute;text-shadow:.05em .05em .1em #000;text-align:center;top:50%;vertical-align:middle;width:100%}.vjs-loading-spinner{display:none;position:absolute;top:50%;left:50%;margin:-25px 0 0 -25px;opacity:.85;text-align:left;border:6px solid rgba(43,51,63,.7);box-sizing:border-box;background-clip:padding-box;width:50px;height:50px;border-radius:25px;visibility:hidden}.vjs-seeking .vjs-loading-spinner,.vjs-waiting .vjs-loading-spinner{display:block;-webkit-animation:0s linear .3s forwards vjs-spinner-show;animation:0s linear .3s forwards vjs-spinner-show}.vjs-loading-spinner:after,.vjs-loading-spinner:before{content:"";position:absolute;margin:-6px;box-sizing:inherit;width:inherit;height:inherit;border-radius:inherit;opacity:1;border:inherit;border-color:transparent;border-top-color:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:before{-webkit-animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite;animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite}.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:before{border-top-color:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:after{border-top-color:#fff;-webkit-animation-delay:.44s;animation-delay:.44s}@keyframes vjs-spinner-show{to{visibility:visible}}@-webkit-keyframes vjs-spinner-show{to{visibility:visible}}@keyframes vjs-spinner-spin{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes vjs-spinner-spin{100%{-webkit-transform:rotate(360deg)}}@keyframes vjs-spinner-fade{0%{border-top-color:#73859f}20%{border-top-color:#73859f}35%{border-top-color:#fff}60%{border-top-color:#73859f}100%{border-top-color:#73859f}}@-webkit-keyframes vjs-spinner-fade{0%{border-top-color:#73859f}20%{border-top-color:#73859f}35%{border-top-color:#fff}60%{border-top-color:#73859f}100%{border-top-color:#73859f}}.vjs-chapters-button .vjs-menu ul{width:24em}.video-js .vjs-subs-caps-button+.vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder{vertical-align:middle;display:inline-block;margin-bottom:-.1em}.video-js .vjs-subs-caps-button+.vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:"\f10d";font-size:1.5em;line-height:inherit}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder{vertical-align:middle;display:inline-block;margin-bottom:-.1em}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:" \f11d";font-size:1.5em;line-height:inherit}.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-custom-control-spacer{flex:auto;display:block}.video-js.vjs-layout-tiny:not(.vjs-fullscreen).vjs-no-flex .vjs-custom-control-spacer{width:auto}.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-audio-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-descriptions-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-progress-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-subs-caps-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-volume-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-volume-panel{display:none}.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-audio-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-descriptions-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-subs-caps-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-volume-control,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-volume-panel{display:none}.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-audio-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-descriptions-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-volume-control,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-volume-panel{display:none}.vjs-modal-dialog.vjs-text-track-settings{background-color:#2b333f;background-color:rgba(43,51,63,.75);color:#fff;height:70%}.vjs-text-track-settings .vjs-modal-dialog-content{display:table}.vjs-text-track-settings .vjs-track-settings-colors,.vjs-text-track-settings .vjs-track-settings-controls,.vjs-text-track-settings .vjs-track-settings-font{display:table-cell}.vjs-text-track-settings .vjs-track-settings-controls{text-align:right;vertical-align:bottom}@supports (display:grid){.vjs-text-track-settings .vjs-modal-dialog-content{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr;padding:20px 24px 0 24px}.vjs-track-settings-controls .vjs-default-button{margin-bottom:20px}.vjs-text-track-settings .vjs-track-settings-controls{grid-column:1/-1}.vjs-layout-small .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-tiny .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-x-small .vjs-text-track-settings .vjs-modal-dialog-content{grid-template-columns:1fr}}.vjs-track-setting>select{margin-right:1em;margin-bottom:.5em}.vjs-text-track-settings fieldset{margin:5px;padding:3px;border:none}.vjs-text-track-settings fieldset span{display:inline-block}.vjs-text-track-settings fieldset span>select{max-width:7.3em}.vjs-text-track-settings legend{color:#fff;margin:0 0 5px 0}.vjs-text-track-settings .vjs-label{position:absolute;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);display:block;margin:0 0 5px 0;padding:0;border:0;height:1px;width:1px;overflow:hidden}.vjs-track-settings-controls button:active,.vjs-track-settings-controls button:focus{outline-style:solid;outline-width:medium;background-image:linear-gradient(0deg,#fff 88%,#73859f 100%)}.vjs-track-settings-controls button:hover{color:rgba(43,51,63,.75)}.vjs-track-settings-controls button{background-color:#fff;background-image:linear-gradient(-180deg,#fff 88%,#73859f 100%);color:#2b333f;cursor:pointer;border-radius:2px}.vjs-track-settings-controls .vjs-default-button{margin-right:1em}@media print{.video-js>:not(.vjs-tech):not(.vjs-poster){visibility:hidden}}.vjs-resize-manager{position:absolute;top:0;left:0;width:100%;height:100%;border:none;z-index:-1000}.js-focus-visible .video-js :focus:not(.focus-visible){outline:0;background:0 0}.video-js .vjs-menu :focus:not(:focus-visible),.video-js :focus:not(:focus-visible){outline:0;background:0 0} \ No newline at end of file +.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-modal-dialog,.vjs-button>.vjs-icon-placeholder:before,.vjs-modal-dialog .vjs-modal-dialog-content{position:absolute;top:0;left:0;width:100%;height:100%}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.vjs-button>.vjs-icon-placeholder:before{text-align:center}@font-face{font-family:VideoJS;src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAABBIAAsAAAAAGoQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADsAAABUIIslek9TLzIAAAFEAAAAPgAAAFZRiV3RY21hcAAAAYQAAADQAAADIjn098ZnbHlmAAACVAAACv4AABEIAwnSw2hlYWQAAA1UAAAAKwAAADYX10J/aGhlYQAADYAAAAAbAAAAJA4DByFobXR4AAANnAAAAA8AAACE4AAAAGxvY2EAAA2sAAAARAAAAEQ9NEHGbWF4cAAADfAAAAAfAAAAIAEyAIFuYW1lAAAOEAAAASUAAAIK1cf1oHBvc3QAAA84AAABDwAAAZ5AAl/0eJxjYGRgYOBiMGCwY2BycfMJYeDLSSzJY5BiYGGAAJA8MpsxJzM9kYEDxgPKsYBpDiBmg4gCACY7BUgAeJxjYGQ7xTiBgZWBgaWQ5RkDA8MvCM0cwxDOeI6BgYmBlZkBKwhIc01hcPjI+FGBHcRdyA4RZgQRAC4HCwEAAHic7dFprsIgAEXhg8U61XmeWcBb1FuQP4w7ZQXK5boMm3yclFDSANAHmuKviBBeBPQ8ymyo8w3jOh/5r2ui5nN6v8sYNJb3WMdeWRvLji0DhozKdxM6psyYs2DJijUbtuzYc+DIiTMXrty4k8oGLb+n0xCe37ekM7Z66j1DbUy3l6PpHnLfdLO5NdSBoQ4NdWSoY9ON54mhdqa/y1NDnRnq3FAXhro01JWhrg11Y6hbQ90Z6t5QD4Z6NNSToZ4N9WKoV0O9GerdUJORPqkhTd54nJ1YDXBU1RV+576/JBs2bPYPkrDZt5vsJrv53V/I5mclhGDCTwgGBQQSTEji4hCkYIAGd4TGIWFAhV0RQTpWmQp1xv6hA4OTOlNr2zFANbHUYbq2OtNCpViRqsk+e+7bTQAhzti8vPfuPffcc88959zznbcMMPjHD/KDDGEY0ABpYX384NhlomIYlo4JISGEY9mMh2FSidYiqkEUphtNYDSY/dXg9023l4DdxlqUl0chuZRhncJKrsCQHIwcGuwfnhMIzBnuH4Sym+1D2zaGjheXlhYfD238z80mKYMmvJ5XeOTzd8z9eujbMxJNhu4C9xPE/bCMiDuSNIWgkTQwBE55hLSAE7ZwhrHLnAHZOGV/kmBGTiNjZxzI77Hb7Hqjz68TjT6vh+5JT/cCIkqS0D6CqPf5jX4Qjdx5j6vlDfZM4aZFdbVXIxtOlJaP/WottMnH6CJQ3bTiue3PrY23HjnChtuamxwvvzFjxkPrNj3z0tG9T561HDYf6OgmRWvlY3JQHoQb8ltV2Yet7YfWctEjR1AtxS/cSX6U4alf6NJEBQ7YKg9wrXQKd0IeZCb2ux75Uhh1Un+Nz+9LTOE7PK777nN5xqdTneTBhCbx446mZrhnUkrCz2YhA9dSMxaG0SYmT8hi9ZPu1E94PJYQSH6LRmhxec7Q7ZeXntgQuVpbh+a4qWNsckVyTdn0P7o7DpgPW84+uRcq0BITflBikGdUjAZ9wYBVI3mtrNvr9kpg1UsaK6t3690aoorC1lg0GpMH2HAMtkZjsSi5Ig9ESVosOh7GQfLjKNLvKpMKkLSKNFAka710GdgSi8oDMSoNhqjkKBXTgn3swtaxyzGkUzIzae9RtLdWkSlZ1KDX6EzgllzV4NV4SoDFSOGD4+HCeQUF8wrZ5Hs8zIb5EaVxy8DYFTbMCJPnLIWZxugZE2NlivC0gc1qEQUR8jEKgZcAXeH18BiCgl5nlHh0CrjB4Hb5fX4gb0J7c9PuHVsfgkx2n/vTY/JV8kn8PGxf7faOZ8qX8JVByuIf4whk9sqXli2hvPJV9hrp0hY7l8r2x37ydaVsb4xvXv/47v2NjfCl8m5oRDJclFMoE1yk0Uh1Te4/m8lFXe9qBZD0EkheicebXvzI2PLCuoKCukLuhPIeKwaHPEouxw3kMqaIUXDQ1p0mip+MyCORSCQaoUsnY1VZ38nUTrG21WvVo4f1OsEJFhvSfAFwGfT8VHRMeAVUpwLOoLzjT/REIj3O3FhuURE+nERF+0pTId5Fyxv5sfwGyg4O+my4vZv0sZm7oeQlFZORiB+tG0MweVNraeitl7yxiPIHTk4/diVxs94o5lEYishB2iAtkchEnsActoEpx44Fo8XnsQMaA22BlqC20RmhBKzYojZyYaxg+JggMc4HHY2m+L9EkWSYljirOisrO7d3VorxzyZ6Vc4lJqITAu1b2wOBdrLElAP+bFc2eGaZFVbkmJktv5uT6Jlz5D/MnBFor6ig/JPnRViBsV3LNKGGqB1ChJ0tgQywlVLFJIuQgTFttwkiKxhyQdAZMdMYtSaoAewqfvXVYPAbDT6/1mez85YS8FSDywQ6NfAnef6FNEGMilnppyvn5rB6tTyq1pOceRWnp2WJEZFXHeX5oyoem1nTTgdqc4heDY7bOeKz63vnz+/dRx+s31Ht2JGanQ5seirfWJL9tjozU/12TnEjn5oux9OzU3ckGbBzBwNOyk69JykKH0n/0LM9A72tuwM3zQpIRu4AxiToseEpgPOmbROyFe9/X2yeUvoUsCyEvjcgs7fpWP3/aKlFN0+6HFUe6D9HFz/XPwBlN9tTqNyZjFJ8UO2RUT5/h4CptCctEyeisnOyXjALEp7dXKaQKf6O7IMnGjNNACRMLxqdYJX8eMLvmmd68D+ayBLyKKYZwYxDt/GNhzETDJ05Qxlyi3pi3/Z93ndYVSumgj0V/KkIFlO6+1K3fF2+3g0q+YtuSIf0bvmLqV09nnobI6hwcjIP8aPCKayjsF5JBY3LaKAeRLSyYB1h81oTwe9SlPMkXB7G0mfL9q71gaqqwPqu67QRKS1+ObTx+sbQy9QV2OQHEScGkdFBeT7v7qisqqrs6N52i78/R+6S0qQONVj26agOVoswCyQWIV5D86vH53bxNUeXV0K+XZaHv/nm/KsHhOvylwsWnJX/HE8l/4WCv5x+l5n08z6UU8bUMa3MBpSmM7F63AxntdC9eBCKEZW9Hr+ABNqtxgAQrSbMtmrW7lKQuoSgBhSrTazWVU2QAKWY8wiiuhqFmQgWJBgoXiuWIm42N7hqZbBsgXz52O5P5uSvaNgFGnOuvsRw8I8Laha91wMvDuxqWFheN7/8GVtTltdS83DQsXRmqc5ZtcJXEVrlV2doTWk5+Yunm71dG5f55m/qY0MjI93vv9/NfpxXV9sUXrxy2fbNy1or65cOlDRnOoKFeeXcbw42H/bNDT5Qs3flgs31gWC1lD1nfUV/X7NdCnSUdHY2e8afzfKsqZ5ZljfDqjLOmk3UebNXB+aHArPYDRs+/HDDxeT5DiP+sFg7OpRaVQMGBV89PpeBdj22hCE0Uub0UqwLrNWsG0cuyadgLXTeR5rbO4+3c/vl15cur2nRq+TXCQDcS3SO+s6ak+e5/eMS+1dw3btu3YG2tvFL8XdIZvdjdW6TO/4B7IdrZWVPmctm5/59AgsPItTSbCiIBr2OqIGzmu20SMKAS7yqwGBUfGfgjDYlLLDeF0SfcLB2LSx8flT+08/kzz6yOj96rft4rpTjdPQcmLd47uKibbDq7ZSz/XtbH2nN717Nd62rU+c8Icevvv7I09wA6WvjVcafb+FsbNG+ZQ80Rn6ZZsvrP7teP2dzTdoETvNhjCmsr8FID2sJ69VYvdUcxk4AzYRlKcaE38eXNRlfW9H1as9i6acLHp1XpuNB5K7DIvkX08y1ZYvh3KfWaiCzH+ztrSDmD7LuX73x/mJelB8Yj39t8nhNQJJ2CAthpoFGLsGgtSOCJooCGoaJAMTjSWHVZ08YAa1Fg9lPI5U6DOsGVjDasJeZZ+YyhfCwfOzCxlBA69M9XLXtza7H/rav+9Tjq5xNi0wpKQIRNO4Lrzz7yp5QVYM6Jd/oc1Uvn/mQhhuWh6ENXoS2YTZ8QT42bF5d/559zp5r0Uff2VnR2tdf2/WCOd2cO0Mw6qpWPnvxpV0nrt5fZd2yItc199GWe8vlNfNDq+CH/7yAAnB9hn7T4QO4c1g9ScxsZgmzntnE/IDGndtHMw69lFwoCnYsMGx+rBp8JSBqdLzBr9QRPq/PbhWMWFtQZp1xguy/haw3TEHm3TWAnxFWQQWgt7M5OV0lCz1VRYucpWliy7z6Zd4urwPIyeZQqli2Lgg7szJV09PysATbOQtYIrB2YzbkJYkGgJ0m4AjPUap1pvYu1K9qr97z0Yl3p332b2LYB78ncYIlRkau/8GObSsOlZancACE5d5ily+c2+7h5Yj4lqhVmXXB+iXLfvdqSgqfKtQvfHDV0OnvQR1qhw42XS/vkvsh/hXcrDFP0a+SJNIomEfD1nsrYGO+1bgTOJhM8Hv6ek+7vVglxuSRwoKn17S937bm6YJCeSSG0Op1n+7tE37tcZ/p7dsTv4EUrGpDbWueKigsLHhqTVsoEj+JU0kaSjnj9tz8/gryQWwJ9BcJXBC/7smO+I/IFURJetFPrdt5WcoL6DbEJaygI8CTHfQTjf40ofD+DwalTqIAAHicY2BkYGAAYh92y9Z4fpuvDNzsDCBw4/bvg8g0OyNYnIOBCUQBABxdCbwAeJxjYGRgYGcAARD5/z87IwMjAypQBAAtgwI4AHicY2BgYGAfYAwAOkQA4QAAAAAAAA4AaAB+AMwA4AECAUIBbAGYAcICGAJYArQC4AMwA7AD3gQwBJYE3AUkBWYFigYgBmYGtAbqB1gIEghYCG4IhHicY2BkYGBQZChlYGcAASYg5gJCBob/YD4DABfTAbQAeJxdkE1qg0AYhl8Tk9AIoVDaVSmzahcF87PMARLIMoFAl0ZHY1BHdBJIT9AT9AQ9RQ9Qeqy+yteNMzDzfM+88w0K4BY/cNAMB6N2bUaPPBLukybCLvleeAAPj8JD+hfhMV7hC3u4wxs7OO4NzQSZcI/8Ltwnfwi75E/hAR7wJTyk/xYeY49fYQ/PztM+jbTZ7LY6OWdBJdX/pqs6NYWa+zMxa13oKrA6Uoerqi/JwtpYxZXJ1coUVmeZUWVlTjq0/tHacjmdxuL90OR8O0UEDYMNdtiSEpz5XQGqzlm30kzUdAYFFOb8R7NOZk0q2lwAyz1i7oAr1xoXvrOgtYhZx8wY5KRV269JZ5yGpmzPTjQhvY9je6vEElPOuJP3mWKnP5M3V+YAAAB4nG2PyXLCMBBE3YCNDWEL2ffk7o8S8oCnkCVHC5C/jzBQlUP6IHVPzYyekl5y0iL5X5/ooY8BUmQYIkeBEca4wgRTzDDHAtdY4ga3uMM9HvCIJzzjBa94wzs+8ImvZNAq8TM+HqVkKxWlrQiOxjujQkNlEzyNzl6Z/cU2XF06at7U83VQyklLpEvSnuzsb+HAPnPfQVgaupa1Jlu4sPLsFblcitaz0dHU0ZF1qatjZ1+aTXYCmp6u0gSvWNPyHLtFZ+ZeXWVSaEkqs3T8S74WklbGbNNNq4LL4+CWKtZDv2cfX8l8aFbKFhEnJnJ+IULFpqwoQnNHlHaVQtPBl+ypmbSWdmyC61KS/AKZC3Y+AA==) format("woff");font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder,.vjs-icon-play{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-big-play-button .vjs-icon-placeholder:before,.video-js .vjs-play-control .vjs-icon-placeholder:before,.vjs-icon-play:before{content:"\f101"}.vjs-icon-play-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-play-circle:before{content:"\f102"}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder,.vjs-icon-pause{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-playing .vjs-icon-placeholder:before,.vjs-icon-pause:before{content:"\f103"}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder,.vjs-icon-volume-mute{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-0 .vjs-icon-placeholder:before,.vjs-icon-volume-mute:before{content:"\f104"}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder,.vjs-icon-volume-low{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-1 .vjs-icon-placeholder:before,.vjs-icon-volume-low:before{content:"\f105"}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder,.vjs-icon-volume-mid{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control.vjs-vol-2 .vjs-icon-placeholder:before,.vjs-icon-volume-mid:before{content:"\f106"}.video-js .vjs-mute-control .vjs-icon-placeholder,.vjs-icon-volume-high{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-mute-control .vjs-icon-placeholder:before,.vjs-icon-volume-high:before{content:"\f107"}.video-js .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-enter{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-enter:before{content:"\f108"}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder,.vjs-icon-fullscreen-exit{font-family:VideoJS;font-weight:400;font-style:normal}.video-js.vjs-fullscreen .vjs-fullscreen-control .vjs-icon-placeholder:before,.vjs-icon-fullscreen-exit:before{content:"\f109"}.vjs-icon-square{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-square:before{content:"\f10a"}.vjs-icon-spinner{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-spinner:before{content:"\f10b"}.video-js .vjs-subs-caps-button .vjs-icon-placeholder,.video-js .vjs-subtitles-button .vjs-icon-placeholder,.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder,.vjs-icon-subtitles{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js .vjs-subtitles-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-AU) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-GB) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-IE) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js.video-js:lang(en-NZ) .vjs-subs-caps-button .vjs-icon-placeholder:before,.vjs-icon-subtitles:before{content:"\f10c"}.video-js .vjs-captions-button .vjs-icon-placeholder,.video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder,.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder,.vjs-icon-captions{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-captions-button .vjs-icon-placeholder:before,.video-js:lang(en) .vjs-subs-caps-button .vjs-icon-placeholder:before,.video-js:lang(fr-CA) .vjs-subs-caps-button .vjs-icon-placeholder:before,.vjs-icon-captions:before{content:"\f10d"}.video-js .vjs-chapters-button .vjs-icon-placeholder,.vjs-icon-chapters{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-chapters-button .vjs-icon-placeholder:before,.vjs-icon-chapters:before{content:"\f10e"}.vjs-icon-share{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-share:before{content:"\f10f"}.vjs-icon-cog{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-cog:before{content:"\f110"}.video-js .vjs-play-progress,.video-js .vjs-volume-level,.vjs-icon-circle,.vjs-seek-to-live-control .vjs-icon-placeholder{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-progress:before,.video-js .vjs-volume-level:before,.vjs-icon-circle:before,.vjs-seek-to-live-control .vjs-icon-placeholder:before{content:"\f111"}.vjs-icon-circle-outline{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-outline:before{content:"\f112"}.vjs-icon-circle-inner-circle{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-circle-inner-circle:before{content:"\f113"}.vjs-icon-hd{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-hd:before{content:"\f114"}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder,.vjs-icon-cancel{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-control.vjs-close-button .vjs-icon-placeholder:before,.vjs-icon-cancel:before{content:"\f115"}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder,.vjs-icon-replay{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-play-control.vjs-ended .vjs-icon-placeholder:before,.vjs-icon-replay:before{content:"\f116"}.vjs-icon-facebook{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-facebook:before{content:"\f117"}.vjs-icon-gplus{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-gplus:before{content:"\f118"}.vjs-icon-linkedin{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-linkedin:before{content:"\f119"}.vjs-icon-twitter{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-twitter:before{content:"\f11a"}.vjs-icon-tumblr{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-tumblr:before{content:"\f11b"}.vjs-icon-pinterest{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-pinterest:before{content:"\f11c"}.video-js .vjs-descriptions-button .vjs-icon-placeholder,.vjs-icon-audio-description{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-descriptions-button .vjs-icon-placeholder:before,.vjs-icon-audio-description:before{content:"\f11d"}.video-js .vjs-audio-button .vjs-icon-placeholder,.vjs-icon-audio{font-family:VideoJS;font-weight:400;font-style:normal}.video-js .vjs-audio-button .vjs-icon-placeholder:before,.vjs-icon-audio:before{content:"\f11e"}.vjs-icon-next-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-next-item:before{content:"\f11f"}.vjs-icon-previous-item{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-previous-item:before{content:"\f120"}.video-js{display:block;vertical-align:top;box-sizing:border-box;color:#fff;background-color:#000;position:relative;padding:0;font-size:10px;line-height:1;font-weight:400;font-style:normal;font-family:Arial,Helvetica,sans-serif;word-break:initial}.video-js:-moz-full-screen{position:absolute}.video-js:-webkit-full-screen{width:100%!important;height:100%!important}.video-js[tabindex="-1"]{outline:0}.video-js *,.video-js :after,.video-js :before{box-sizing:inherit}.video-js ul{font-family:inherit;font-size:inherit;line-height:inherit;list-style-position:outside;margin-left:0;margin-right:0;margin-top:0;margin-bottom:0}.video-js.vjs-16-9,.video-js.vjs-4-3,.video-js.vjs-fluid{width:100%;max-width:100%;height:0}.video-js.vjs-16-9{padding-top:56.25%}.video-js.vjs-4-3{padding-top:75%}.video-js.vjs-fill{width:100%;height:100%}.video-js .vjs-tech{position:absolute;top:0;left:0;width:100%;height:100%}body.vjs-full-window{padding:0;margin:0;height:100%}.vjs-full-window .video-js.vjs-fullscreen{position:fixed;overflow:hidden;z-index:1000;left:0;top:0;bottom:0;right:0}.video-js.vjs-fullscreen{width:100%!important;height:100%!important;padding-top:0!important}.video-js.vjs-fullscreen.vjs-user-inactive{cursor:none}.vjs-hidden{display:none!important}.vjs-disabled{opacity:.5;cursor:default}.video-js .vjs-offscreen{height:1px;left:-9999px;position:absolute;top:0;width:1px}.vjs-lock-showing{display:block!important;opacity:1;visibility:visible}.vjs-no-js{padding:20px;color:#fff;background-color:#000;font-size:18px;font-family:Arial,Helvetica,sans-serif;text-align:center;width:300px;height:150px;margin:0 auto}.vjs-no-js a,.vjs-no-js a:visited{color:#66a8cc}.video-js .vjs-big-play-button{font-size:3em;line-height:1.5em;height:1.63332em;width:3em;display:block;position:absolute;top:10px;left:10px;padding:0;cursor:pointer;opacity:1;border:.06666em solid #fff;background-color:#2b333f;background-color:rgba(43,51,63,.7);border-radius:.3em;transition:all .4s}.vjs-big-play-centered .vjs-big-play-button{top:50%;left:50%;margin-top:-.81666em;margin-left:-1.5em}.video-js .vjs-big-play-button:focus,.video-js:hover .vjs-big-play-button{border-color:#fff;background-color:#73859f;background-color:rgba(115,133,159,.5);transition:all 0s}.vjs-controls-disabled .vjs-big-play-button,.vjs-error .vjs-big-play-button,.vjs-has-started .vjs-big-play-button,.vjs-using-native-controls .vjs-big-play-button{display:none}.vjs-has-started.vjs-paused.vjs-show-big-play-button-on-pause .vjs-big-play-button{display:block}.video-js button{background:0 0;border:none;color:inherit;display:inline-block;font-size:inherit;line-height:inherit;text-transform:none;text-decoration:none;transition:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.vjs-control .vjs-button{width:100%;height:100%}.video-js .vjs-control.vjs-close-button{cursor:pointer;height:3em;position:absolute;right:0;top:.5em;z-index:2}.video-js .vjs-modal-dialog{background:rgba(0,0,0,.8);background:linear-gradient(180deg,rgba(0,0,0,.8),rgba(255,255,255,0));overflow:auto}.video-js .vjs-modal-dialog>*{box-sizing:border-box}.vjs-modal-dialog .vjs-modal-dialog-content{font-size:1.2em;line-height:1.5;padding:20px 24px;z-index:1}.vjs-menu-button{cursor:pointer}.vjs-menu-button.vjs-disabled{cursor:default}.vjs-workinghover .vjs-menu-button.vjs-disabled:hover .vjs-menu{display:none}.vjs-menu .vjs-menu-content{display:block;padding:0;margin:0;font-family:Arial,Helvetica,sans-serif;overflow:auto}.vjs-menu .vjs-menu-content>*{box-sizing:border-box}.vjs-scrubbing .vjs-control.vjs-menu-button:hover .vjs-menu{display:none}.vjs-menu li{list-style:none;margin:0;padding:.2em 0;line-height:1.4em;font-size:1.2em;text-align:center;text-transform:lowercase}.js-focus-visible .vjs-menu li.vjs-menu-item:hover,.vjs-menu li.vjs-menu-item:focus,.vjs-menu li.vjs-menu-item:hover{background-color:#73859f;background-color:rgba(115,133,159,.5)}.js-focus-visible .vjs-menu li.vjs-selected:hover,.vjs-menu li.vjs-selected,.vjs-menu li.vjs-selected:focus,.vjs-menu li.vjs-selected:hover{background-color:#fff;color:#2b333f}.vjs-menu li.vjs-menu-title{text-align:center;text-transform:uppercase;font-size:1em;line-height:2em;padding:0;margin:0 0 .3em 0;font-weight:700;cursor:default}.vjs-menu-button-popup .vjs-menu{display:none;position:absolute;bottom:0;width:10em;left:-3em;height:0;margin-bottom:1.5em;border-top-color:rgba(43,51,63,.7)}.vjs-menu-button-popup .vjs-menu .vjs-menu-content{background-color:#2b333f;background-color:rgba(43,51,63,.7);position:absolute;width:100%;bottom:1.5em;max-height:15em}.vjs-layout-tiny .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-x-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:5em}.vjs-layout-small .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:10em}.vjs-layout-medium .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:14em}.vjs-layout-huge .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content,.vjs-layout-x-large .vjs-menu-button-popup .vjs-menu .vjs-menu-content{max-height:25em}.vjs-menu-button-popup .vjs-menu.vjs-lock-showing,.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu{display:block}.video-js .vjs-menu-button-inline{transition:all .4s;overflow:hidden}.video-js .vjs-menu-button-inline:before{width:2.222222222em}.video-js .vjs-menu-button-inline.vjs-slider-active,.video-js .vjs-menu-button-inline:focus,.video-js .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline{width:12em}.vjs-menu-button-inline .vjs-menu{opacity:0;height:100%;width:auto;position:absolute;left:4em;top:0;padding:0;margin:0;transition:all .4s}.vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-menu-button-inline:focus .vjs-menu,.vjs-menu-button-inline:hover .vjs-menu{display:block;opacity:1}.vjs-no-flex .vjs-menu-button-inline .vjs-menu{display:block;opacity:1;position:relative;width:auto}.vjs-no-flex .vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:focus .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:hover .vjs-menu{width:auto}.vjs-menu-button-inline .vjs-menu-content{width:auto;height:100%;margin:0;overflow:hidden}.video-js .vjs-control-bar{display:none;width:100%;position:absolute;bottom:0;left:0;right:0;height:3em;background-color:#2b333f;background-color:rgba(43,51,63,.7)}.vjs-has-started .vjs-control-bar{display:flex;visibility:visible;opacity:1;transition:visibility .1s,opacity .1s}.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:visible;opacity:0;transition:visibility 1s,opacity 1s}.vjs-controls-disabled .vjs-control-bar,.vjs-error .vjs-control-bar,.vjs-using-native-controls .vjs-control-bar{display:none!important}.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{opacity:1;visibility:visible}.vjs-has-started.vjs-no-flex .vjs-control-bar{display:table}.video-js .vjs-control{position:relative;text-align:center;margin:0;padding:0;height:100%;width:4em;flex:none}.vjs-button>.vjs-icon-placeholder:before{font-size:1.8em;line-height:1.67}.video-js .vjs-control:focus,.video-js .vjs-control:focus:before,.video-js .vjs-control:hover:before{text-shadow:0 0 1em #fff}.video-js .vjs-control-text{border:0;clip:rect(0 0 0 0);height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.vjs-no-flex .vjs-control{display:table-cell;vertical-align:middle}.video-js .vjs-custom-control-spacer{display:none}.video-js .vjs-progress-control{cursor:pointer;flex:auto;display:flex;align-items:center;min-width:4em;touch-action:none}.video-js .vjs-progress-control.disabled{cursor:default}.vjs-live .vjs-progress-control{display:none}.vjs-liveui .vjs-progress-control{display:flex;align-items:center}.vjs-no-flex .vjs-progress-control{width:auto}.video-js .vjs-progress-holder{flex:auto;transition:all .2s;height:.3em}.video-js .vjs-progress-control .vjs-progress-holder{margin:0 10px}.video-js .vjs-progress-control:hover .vjs-progress-holder{font-size:1.666666666666666666em}.video-js .vjs-progress-control:hover .vjs-progress-holder.disabled{font-size:1em}.video-js .vjs-progress-holder .vjs-load-progress,.video-js .vjs-progress-holder .vjs-load-progress div,.video-js .vjs-progress-holder .vjs-play-progress{position:absolute;display:block;height:100%;margin:0;padding:0;width:0}.video-js .vjs-play-progress{background-color:#fff}.video-js .vjs-play-progress:before{font-size:.9em;position:absolute;right:-.5em;top:-.333333333333333em;z-index:1}.video-js .vjs-load-progress{background:rgba(115,133,159,.5)}.video-js .vjs-load-progress div{background:rgba(115,133,159,.75)}.video-js .vjs-time-tooltip{background-color:#fff;background-color:rgba(255,255,255,.8);border-radius:.3em;color:#000;float:right;font-family:Arial,Helvetica,sans-serif;font-size:1em;padding:6px 8px 8px 8px;pointer-events:none;position:absolute;top:-3.4em;visibility:hidden;z-index:1}.video-js .vjs-progress-holder:focus .vjs-time-tooltip{display:none}.video-js .vjs-progress-control:hover .vjs-progress-holder:focus .vjs-time-tooltip,.video-js .vjs-progress-control:hover .vjs-time-tooltip{display:block;font-size:.6em;visibility:visible}.video-js .vjs-progress-control.disabled:hover .vjs-time-tooltip{font-size:1em}.video-js .vjs-progress-control .vjs-mouse-display{display:none;position:absolute;width:1px;height:100%;background-color:#000;z-index:1}.vjs-no-flex .vjs-progress-control .vjs-mouse-display{z-index:0}.video-js .vjs-progress-control:hover .vjs-mouse-display{display:block}.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display{visibility:hidden;opacity:0;transition:visibility 1s,opacity 1s}.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display{display:none}.vjs-mouse-display .vjs-time-tooltip{color:#fff;background-color:#000;background-color:rgba(0,0,0,.8)}.video-js .vjs-slider{position:relative;cursor:pointer;padding:0;margin:0 .45em 0 .45em;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#73859f;background-color:rgba(115,133,159,.5)}.video-js .vjs-slider.disabled{cursor:default}.video-js .vjs-slider:focus{text-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.video-js .vjs-mute-control{cursor:pointer;flex:none}.video-js .vjs-volume-control{cursor:pointer;margin-right:1em;display:flex}.video-js .vjs-volume-control.vjs-volume-horizontal{width:5em}.video-js .vjs-volume-panel .vjs-volume-control{visibility:visible;opacity:0;width:1px;height:1px;margin-left:-1px}.video-js .vjs-volume-panel{transition:width 1s}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active,.video-js .vjs-volume-panel .vjs-volume-control:active,.video-js .vjs-volume-panel .vjs-volume-control:hover,.video-js .vjs-volume-panel:active .vjs-volume-control,.video-js .vjs-volume-panel:focus .vjs-volume-control,.video-js .vjs-volume-panel:hover .vjs-volume-control{visibility:visible;opacity:1;position:relative;transition:visibility .1s,opacity .1s,height .1s,width .1s,left 0s,top 0s}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal,.video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-horizontal,.video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal,.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em}.video-js .vjs-volume-panel .vjs-mute-control:hover~.vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical,.video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical,.video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical,.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical{left:-3.5em}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover{width:9em;transition:width .1s}.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-mute-toggle-only{width:4em}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{height:8em;width:3em;left:-3000em;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s 1s,left 1s 1s,top 1s 1s}.video-js .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,left 1s 1s,top 1s 1s}.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{width:5em;height:3em;visibility:visible;opacity:1;position:relative;transition:none}.video-js.vjs-no-flex .vjs-volume-control.vjs-volume-vertical,.video-js.vjs-no-flex .vjs-volume-panel .vjs-volume-control.vjs-volume-vertical{position:absolute;bottom:3em;left:.5em}.video-js .vjs-volume-panel{display:flex}.video-js .vjs-volume-bar{margin:1.35em .45em}.vjs-volume-bar.vjs-slider-horizontal{width:5em;height:.3em}.vjs-volume-bar.vjs-slider-vertical{width:.3em;height:5em;margin:1.35em auto}.video-js .vjs-volume-level{position:absolute;bottom:0;left:0;background-color:#fff}.video-js .vjs-volume-level:before{position:absolute;font-size:.9em}.vjs-slider-vertical .vjs-volume-level{width:.3em}.vjs-slider-vertical .vjs-volume-level:before{top:-.5em;left:-.3em}.vjs-slider-horizontal .vjs-volume-level{height:.3em}.vjs-slider-horizontal .vjs-volume-level:before{top:-.3em;right:-.5em}.video-js .vjs-volume-panel.vjs-volume-panel-vertical{width:4em}.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level{height:100%}.vjs-volume-bar.vjs-slider-horizontal .vjs-volume-level{width:100%}.video-js .vjs-volume-vertical{width:3em;height:8em;bottom:8em;background-color:#2b333f;background-color:rgba(43,51,63,.7)}.video-js .vjs-volume-horizontal .vjs-menu{left:-2em}.vjs-poster{display:inline-block;vertical-align:middle;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;background-color:#000;cursor:pointer;margin:0;padding:0;position:absolute;top:0;right:0;bottom:0;left:0;height:100%}.vjs-has-started .vjs-poster{display:none}.vjs-audio.vjs-has-started .vjs-poster{display:block}.vjs-using-native-controls .vjs-poster{display:none}.video-js .vjs-live-control{display:flex;align-items:flex-start;flex:auto;font-size:1em;line-height:3em}.vjs-no-flex .vjs-live-control{display:table-cell;width:auto;text-align:left}.video-js.vjs-liveui .vjs-live-control,.video-js:not(.vjs-live) .vjs-live-control{display:none}.video-js .vjs-seek-to-live-control{cursor:pointer;flex:none;display:inline-flex;height:100%;padding-left:.5em;padding-right:.5em;font-size:1em;line-height:3em;width:auto;min-width:4em}.vjs-no-flex .vjs-seek-to-live-control{display:table-cell;width:auto;text-align:left}.video-js.vjs-live:not(.vjs-liveui) .vjs-seek-to-live-control,.video-js:not(.vjs-live) .vjs-seek-to-live-control{display:none}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge{cursor:auto}.vjs-seek-to-live-control .vjs-icon-placeholder{margin-right:.5em;color:#888}.vjs-seek-to-live-control.vjs-control.vjs-at-live-edge .vjs-icon-placeholder{color:red}.video-js .vjs-time-control{flex:none;font-size:1em;line-height:3em;min-width:2em;width:auto;padding-left:1em;padding-right:1em}.vjs-live .vjs-time-control{display:none}.video-js .vjs-current-time,.vjs-no-flex .vjs-current-time{display:none}.video-js .vjs-duration,.vjs-no-flex .vjs-duration{display:none}.vjs-time-divider{display:none;line-height:3em}.vjs-live .vjs-time-divider{display:none}.video-js .vjs-play-control{cursor:pointer}.video-js .vjs-play-control .vjs-icon-placeholder{flex:none}.vjs-text-track-display{position:absolute;bottom:3em;left:0;right:0;top:0;pointer-events:none}.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display{bottom:1em}.video-js .vjs-text-track{font-size:1.4em;text-align:center;margin-bottom:.1em}.vjs-subtitles{color:#fff}.vjs-captions{color:#fc6}.vjs-tt-cue{display:block}video::-webkit-media-text-track-display{-webkit-transform:translateY(-3em);transform:translateY(-3em)}.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display{-webkit-transform:translateY(-1.5em);transform:translateY(-1.5em)}.video-js .vjs-fullscreen-control{cursor:pointer;flex:none}.vjs-playback-rate .vjs-playback-rate-value,.vjs-playback-rate>.vjs-menu-button{position:absolute;top:0;left:0;width:100%;height:100%}.vjs-playback-rate .vjs-playback-rate-value{pointer-events:none;font-size:1.5em;line-height:2;text-align:center}.vjs-playback-rate .vjs-menu{width:4em;left:0}.vjs-error .vjs-error-display .vjs-modal-dialog-content{font-size:1.4em;text-align:center}.vjs-error .vjs-error-display:before{color:#fff;content:'X';font-family:Arial,Helvetica,sans-serif;font-size:4em;left:0;line-height:1;margin-top:-.5em;position:absolute;text-shadow:.05em .05em .1em #000;text-align:center;top:50%;vertical-align:middle;width:100%}.vjs-loading-spinner{display:none;position:absolute;top:50%;left:50%;margin:-25px 0 0 -25px;opacity:.85;text-align:left;border:6px solid rgba(43,51,63,.7);box-sizing:border-box;background-clip:padding-box;width:50px;height:50px;border-radius:25px;visibility:hidden}.vjs-seeking .vjs-loading-spinner,.vjs-waiting .vjs-loading-spinner{display:block;-webkit-animation:vjs-spinner-show 0s linear .3s forwards;animation:vjs-spinner-show 0s linear .3s forwards}.vjs-loading-spinner:after,.vjs-loading-spinner:before{content:"";position:absolute;margin:-6px;box-sizing:inherit;width:inherit;height:inherit;border-radius:inherit;opacity:1;border:inherit;border-color:transparent;border-top-color:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:before{-webkit-animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite;animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite}.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:before{border-top-color:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:after{border-top-color:#fff;-webkit-animation-delay:.44s;animation-delay:.44s}@keyframes vjs-spinner-show{to{visibility:visible}}@-webkit-keyframes vjs-spinner-show{to{visibility:visible}}@keyframes vjs-spinner-spin{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes vjs-spinner-spin{100%{-webkit-transform:rotate(360deg)}}@keyframes vjs-spinner-fade{0%{border-top-color:#73859f}20%{border-top-color:#73859f}35%{border-top-color:#fff}60%{border-top-color:#73859f}100%{border-top-color:#73859f}}@-webkit-keyframes vjs-spinner-fade{0%{border-top-color:#73859f}20%{border-top-color:#73859f}35%{border-top-color:#fff}60%{border-top-color:#73859f}100%{border-top-color:#73859f}}.vjs-chapters-button .vjs-menu ul{width:24em}.video-js .vjs-subs-caps-button+.vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder{vertical-align:middle;display:inline-block;margin-bottom:-.1em}.video-js .vjs-subs-caps-button+.vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:"\f10d";font-size:1.5em;line-height:inherit}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder{vertical-align:middle;display:inline-block;margin-bottom:-.1em}.video-js .vjs-audio-button+.vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder:before{font-family:VideoJS;content:" \f11d";font-size:1.5em;line-height:inherit}.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-control,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-control,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-audio-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-captions-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-chapters-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-current-time,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-descriptions-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-duration,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-playback-rate,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-remaining-time,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-subtitles-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-time-divider,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-control{display:none}.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover{width:auto;width:initial}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subs-caps-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-live) .vjs-subs-caps-button,.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-liveui) .vjs-subs-caps-button{display:none}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-custom-control-spacer,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-custom-control-spacer{flex:auto;display:block}.video-js:not(.vjs-fullscreen).vjs-layout-tiny.vjs-no-flex .vjs-custom-control-spacer,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui.vjs-no-flex .vjs-custom-control-spacer{width:auto}.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-progress-control,.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-progress-control{display:none}.vjs-modal-dialog.vjs-text-track-settings{background-color:#2b333f;background-color:rgba(43,51,63,.75);color:#fff;height:70%}.vjs-text-track-settings .vjs-modal-dialog-content{display:table}.vjs-text-track-settings .vjs-track-settings-colors,.vjs-text-track-settings .vjs-track-settings-controls,.vjs-text-track-settings .vjs-track-settings-font{display:table-cell}.vjs-text-track-settings .vjs-track-settings-controls{text-align:right;vertical-align:bottom}@supports (display:grid){.vjs-text-track-settings .vjs-modal-dialog-content{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr;padding:20px 24px 0 24px}.vjs-track-settings-controls .vjs-default-button{margin-bottom:20px}.vjs-text-track-settings .vjs-track-settings-controls{grid-column:1/-1}.vjs-layout-small .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-tiny .vjs-text-track-settings .vjs-modal-dialog-content,.vjs-layout-x-small .vjs-text-track-settings .vjs-modal-dialog-content{grid-template-columns:1fr}}.vjs-track-setting>select{margin-right:1em;margin-bottom:.5em}.vjs-text-track-settings fieldset{margin:5px;padding:3px;border:none}.vjs-text-track-settings fieldset span{display:inline-block}.vjs-text-track-settings fieldset span>select{max-width:7.3em}.vjs-text-track-settings legend{color:#fff;margin:0 0 5px 0}.vjs-text-track-settings .vjs-label{position:absolute;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);display:block;margin:0 0 5px 0;padding:0;border:0;height:1px;width:1px;overflow:hidden}.vjs-track-settings-controls button:active,.vjs-track-settings-controls button:focus{outline-style:solid;outline-width:medium;background-image:linear-gradient(0deg,#fff 88%,#73859f 100%)}.vjs-track-settings-controls button:hover{color:rgba(43,51,63,.75)}.vjs-track-settings-controls button{background-color:#fff;background-image:linear-gradient(-180deg,#fff 88%,#73859f 100%);color:#2b333f;cursor:pointer;border-radius:2px}.vjs-track-settings-controls .vjs-default-button{margin-right:1em}@media print{.video-js>:not(.vjs-tech):not(.vjs-poster){visibility:hidden}}.vjs-resize-manager{position:absolute;top:0;left:0;width:100%;height:100%;border:none;z-index:-1000}.js-focus-visible .video-js :focus:not(.focus-visible){outline:0;background:0 0}.video-js .vjs-menu :focus:not(:focus-visible),.video-js :focus:not(:focus-visible){outline:0;background:0 0} \ No newline at end of file diff --git a/assets/css/videojs-http-source-selector.css b/assets/css/videojs-http-source-selector.css new file mode 100644 index 00000000..c0cd24e3 --- /dev/null +++ b/assets/css/videojs-http-source-selector.css @@ -0,0 +1,7 @@ +/** + * videojs-http-source-selector + * @version 1.1.5 + * @copyright 2019 Justin Fujita + * @license MIT + */ +.video-js.vjs-http-source-selector{display:block} diff --git a/assets/js/dash.mediaplayer.min.js b/assets/js/dash.mediaplayer.min.js deleted file mode 100644 index 22207e41..00000000 --- a/assets/js/dash.mediaplayer.min.js +++ /dev/null @@ -1,28 +0,0 @@ -/*! v2.9.0-0420f9cc, 2018-08-01T23:15:19Z */ -!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g>6),b.push(128|63&d)):d<65536?(b.push(224|d>>12),b.push(128|63&d>>6),b.push(128|63&d)):(b.push(240|d>>18),b.push(128|63&d>>12),b.push(128|63&d>>6),b.push(128|63&d))}return b},e.decode=function(a){for(var b=[],c=0;c>18)),d.push(b.charAt(63&f>>12)),d.push(b.charAt(63&f>>6)),d.push(b.charAt(63&f))}if(2==a.length-c){var f=(a[c]<<16)+(a[c+1]<<8);d.push(b.charAt(63&f>>18)),d.push(b.charAt(63&f>>12)),d.push(b.charAt(63&f>>6)),d.push("=")}else if(1==a.length-c){var f=a[c]<<16;d.push(b.charAt(63&f>>18)),d.push(b.charAt(63&f>>12)),d.push("==")}return d.join("")},d=function(){for(var a=[],c=0;c=c&&console.log(this.time+" ["+a+"] "+b)}},l=function(a){for(var b=[],c=0;ce&&(k.log("ERROR","Too large cursor position "+this.pos),this.pos=e)},moveCursor:function(a){var b=this.pos+a;if(a>1)for(var c=this.pos+1;c=144&&this.backSpace();var b=c(a);if(this.pos>=e)return void k.log("ERROR","Cannot insert "+a.toString(16)+" ("+b+") at position "+this.pos+". Skipping it!");this.chars[this.pos].setChar(b,this.currPenState),this.moveCursor(1)},clearFromPos:function(a){var b;for(b=a;b0&&(c=a?"["+b.join(" | ")+"]":b.join("\n")),c},getTextAndFormat:function(){return this.rows}};var q=function(a,b){this.chNr=a,this.outputFilter=b,this.mode=null,this.verbose=0,this.displayedMemory=new p,this.nonDisplayedMemory=new p,this.lastOutputScreen=new p,this.currRollUpRow=this.displayedMemory.rows[d-1],this.writeScreen=this.displayedMemory,this.mode=null,this.cueStartTime=null};q.prototype={modes:["MODE_ROLL-UP","MODE_POP-ON","MODE_PAINT-ON","MODE_TEXT"],reset:function(){this.mode=null,this.displayedMemory.reset(),this.nonDisplayedMemory.reset(),this.lastOutputScreen.reset(),this.currRollUpRow=this.displayedMemory.rows[d-1],this.writeScreen=this.displayedMemory,this.mode=null,this.cueStartTime=null,this.lastCueEndTime=null},getHandler:function(){return this.outputFilter},setHandler:function(a){this.outputFilter=a},setPAC:function(a){this.writeScreen.setPAC(a)},setBkgData:function(a){this.writeScreen.setBkgData(a)},setMode:function(a){a!==this.mode&&(this.mode=a,k.log("INFO","MODE="+a),"MODE_POP-ON"==this.mode?this.writeScreen=this.nonDisplayedMemory:(this.writeScreen=this.displayedMemory,this.writeScreen.reset()),"MODE_ROLL-UP"!==this.mode&&(this.displayedMemory.nrRollUpRows=null,this.nonDisplayedMemory.nrRollUpRows=null),this.mode=a)},insertChars:function(a){for(var b=0;b=46,b.italics)b.foreground="white";else{var c=Math.floor(a/2)-16,d=["white","green","blue","cyan","red","yellow","magenta"];b.foreground=d[c]}k.log("INFO","MIDROW: "+JSON.stringify(b)),this.writeScreen.setPen(b)},outputDataUpdate:function(){var a=k.time;null!==a&&this.outputFilter&&(this.outputFilter.updateData&&this.outputFilter.updateData(a,this.displayedMemory),null!==this.cueStartTime||this.displayedMemory.isEmpty()?this.displayedMemory.equals(this.lastOutputScreen)||(this.outputFilter.newCue&&this.outputFilter.newCue(this.cueStartTime,a,this.lastOutputScreen),this.cueStartTime=this.displayedMemory.isEmpty()?null:a):this.cueStartTime=a,this.lastOutputScreen.copy(this.displayedMemory))},cueSplitAtTime:function(a){this.outputFilter&&(this.displayedMemory.isEmpty()||(this.outputFilter.newCue&&this.outputFilter.newCue(this.cueStartTime,a,this.displayedMemory),this.cueStartTime=a))}};var r=function(a,b,c){this.field=a||1,this.outputs=[b,c],this.channels=[new q(1,b),new q(2,c)],this.currChNr=-1,this.lastCmdA=null,this.lastCmdB=null,this.bufferedData=[],this.startTime=null,this.lastTime=null,this.dataCounters={padding:0,char:0,cmd:0,other:0}};r.prototype={getHandler:function(a){return this.channels[a].getHandler()},setHandler:function(a,b){this.channels[a].setHandler(b)},addData:function(a,b){var c,d,e,f=!1;this.lastTime=a,k.setTime(a);for(var g=0;g=16&&d<=31&&d===this.lastCmdA&&e===this.lastCmdB)this.lastCmdA=null,this.lastCmdB=null,k.log("DEBUG","Repeated command ("+l([d,e])+") is dropped");else if(0!==d||0!==e){if(k.log("DATA","["+l([b[g],b[g+1]])+"] -> ("+l([d,e])+")"),c=this.parseCmd(d,e),c||(c=this.parseMidrow(d,e)),c||(c=this.parsePAC(d,e)),c||(c=this.parseBackgroundAttributes(d,e)),!c&&(f=this.parseChars(d,e)))if(this.currChNr&&this.currChNr>=0){var h=this.channels[this.currChNr-1];h.insertChars(f)}else k.log("WARNING","No channel found yet. TEXT-MODE?");c?this.dataCounters.cmd+=2:f?this.dataCounters.char+=2:(this.dataCounters.other+=2,k.log("WARNING","Couldn't parse cleaned data "+l([d,e])+" orig: "+l([b[g],b[g+1]])))}else this.dataCounters.padding+=2},parseCmd:function(a,b){var c=null,d=(20===a||21===a||28===a||29===a)&&32<=b&&b<=47,e=(23===a||31===a)&&33<=b&&b<=35;if(!d&&!e)return!1;c=20===a||21===a||23===a?1:2;var f=this.channels[c-1];return 20===a||21===a||28===a||29===a?32===b?f.cc_RCL():33===b?f.cc_BS():34===b?f.cc_AOF():35===b?f.cc_AON():36===b?f.cc_DER():37===b?f.cc_RU(2):38===b?f.cc_RU(3):39===b?f.cc_RU(4):40===b?f.cc_FON():41===b?f.cc_RDC():42===b?f.cc_TR():43===b?f.cc_RTD():44===b?f.cc_EDM():45===b?f.cc_CR():46===b?f.cc_ENM():47===b&&f.cc_EOC():f.cc_TO(b-32),this.lastCmdA=a,this.lastCmdB=b,this.currChNr=c,!0},parseMidrow:function(a,b){var c=null;if((17===a||25===a)&&32<=b&&b<=47){if((c=17===a?1:2)!==this.currChNr)return k.log("ERROR","Mismatch channel in midrow parsing"),!1;var d=this.channels[c-1];return d.insertChars([32]),d.cc_MIDROW(b),k.log("DEBUG","MIDROW ("+l([a,b])+")"),this.lastCmdA=a,this.lastCmdB=b,!0}return!1},parsePAC:function(a,b){var c=null,d=null,e=(17<=a&&a<=23||25<=a&&a<=31)&&64<=b&&b<=127,j=(16===a||24===a)&&64<=b&&b<=95;if(!e&&!j)return!1;c=a<=23?1:2,d=64<=b&&b<=95?1===c?f[a]:h[a]:1===c?g[a]:i[a];var k=this.interpretPAC(d,b);return this.channels[c-1].setPAC(k),this.lastCmdA=a,this.lastCmdB=b,this.currChNr=c,!0},interpretPAC:function(a,b){var c=b,d={color:null,italics:!1,indent:null,underline:!1,row:a};return c=b>95?b-96:b-64,d.underline=1==(1&c),c<=13?d.color=["white","green","blue","cyan","red","yellow","magenta","white"][Math.floor(c/2)]:c<=15?(d.italics=!0,d.color="white"):d.indent=4*Math.floor((c-16)/2),d},parseChars:function(a,b){var d=null,e=null,f=null;if(a>=25?(d=2,f=a-8):(d=1,f=a),17<=f&&f<=19){var g=b;g=17===f?b+80:18===f?b+112:b+144,k.log("INFO","Special char '"+c(g)+"' in channel "+d),e=[g],this.lastCmdA=a,this.lastCmdB=b}else 32<=a&&a<=127&&(e=0===b?[a]:[a,b],this.lastCmdA=null,this.lastCmdB=null);if(e){var h=l(e);k.log("DEBUG","Char codes = "+h.join(","))}return e},parseBackgroundAttributes:function(a,b){var c,d,e,f,g=(16===a||24===a)&&32<=b&&b<=47,h=(23===a||31===a)&&45<=b&&b<=47;return!(!g&&!h)&&(c={},16===a||24===a?(d=Math.floor((b-32)/2),c.background=j[d],b%2==1&&(c.background=c.background+"_semi")):45===b?c.background="transparent":(c.foreground="black",47===b&&(c.underline=!0)),e=a<24?1:2,f=this.channels[e-1],f.setBkgData(c),this.lastCmdA=a,this.lastCmdB=b,!0)},reset:function(){for(var a=0;a/g,">").replace(/"/g,""").replace(/'/g,"'"):a}function g(a,b,c,d){for(var e=0;e0&&g(a.arrayAccessFormPaths,b,c,d)&&(b[c]=[b[c]])}function i(a){var b=a.split(/[-T:+Z]/g),c=new Date(b[0],b[1]-1,b[2]),d=b[5].split(".");if(c.setHours(b[3],b[4],d[0]),d.length>1&&c.setMilliseconds(d[1]),b[6]&&b[7]){var e=60*b[6]+Number(b[7]);e=0+("-"==(/\d\d-\d\d:\d\d$/.test(a)?"-":"+")?-1*e:e),c.setMinutes(c.getMinutes()-e-c.getTimezoneOffset())}else-1!==a.indexOf("Z",a.length-1)&&(c=new Date(Date.UTC(c.getFullYear(),c.getMonth(),c.getDate(),c.getHours(),c.getMinutes(),c.getSeconds(),c.getMilliseconds())));return c}function j(b,c,d){if(a.datetimeAccessFormPaths.length>0){var e=d.split(".#")[0];return g(a.datetimeAccessFormPaths,b,c,e)?i(b):b}return b}function k(b,c,d,e){return!(c==z.ELEMENT_NODE&&a.xmlElementsFilter.length>0)||g(a.xmlElementsFilter,b,d,e)}function l(b,c){if(b.nodeType==z.DOCUMENT_NODE){for(var f=new Object,g=b.childNodes,i=0;i1&&null!=f.__text&&a.skipEmptyTextNodesForObj&&(a.stripWhitespaces&&""==f.__text||""==f.__text.trim())&&delete f.__text:f=f.__cdata,delete f.__cnt,!a.enableToStringFunc||null==f.__text&&null==f.__cdata||(f.toString=function(){return(null!=this.__text?this.__text:"")+(null!=this.__cdata?this.__cdata:"")}),f}if(b.nodeType==z.TEXT_NODE||b.nodeType==z.CDATA_SECTION_NODE)return b.nodeValue}function m(b,c,d,e){var g="<"+(null!=b&&null!=b.__prefix?b.__prefix+":":"")+c;if(null!=d)for(var h=0;h":">"}function n(a,b){return""}function o(a,b){return-1!==a.indexOf(b,a.length-b.length)}function p(b,c){return!!("property"==a.arrayAccessForm&&o(c.toString(),"_asArray")||0==c.toString().indexOf(a.attributePrefix)||0==c.toString().indexOf("__")||b[c]instanceof Function)}function q(a){var b=0;if(a instanceof Object)for(var c in a)p(a,c)||b++;return b}function r(b,c,d){return 0==a.jsonPropertiesFilter.length||""==d||g(a.jsonPropertiesFilter,b,c,d)}function s(b){var c=[];if(b instanceof Object)for(var d in b)-1==d.toString().indexOf("__")&&0==d.toString().indexOf(a.attributePrefix)&&c.push(d);return c}function t(b){var c="";return null!=b.__cdata&&(c+=""),null!=b.__text&&(a.escapeMode?c+=f(b.__text):c+=b.__text),c}function u(b){var c="";return b instanceof Object?c+=t(b):null!=b&&(a.escapeMode?c+=f(b):c+=b),c}function v(a,b){return""===a?b:a+"."+b}function w(a,b,c,d){var e="";if(0==a.length)e+=m(a,b,c,!0);else for(var f=0;f0)for(var d in a)if(!p(a,d)&&(""==b||r(a,d,v(b,d)))){var e=a[d],f=s(e);if(null==e||void 0==e)c+=m(e,d,f,!0);else if(e instanceof Object)if(e instanceof Array)c+=w(e,d,f,b);else if(e instanceof Date)c+=m(e,d,f,!1),c+=e.toISOString(),c+=n(e,d);else{var g=q(e);g>0||null!=e.__text||null!=e.__cdata?(c+=m(e,d,f,!1),c+=x(e,v(b,d)),c+=n(e,d)):c+=m(e,d,f,!0)}else c+=m(e,d,f,!1),c+=u(e),c+=n(e,d)}return c+=u(a)}var y="1.2.0";a=a||{},b(),c();var z={ELEMENT_NODE:1,TEXT_NODE:3,CDATA_SECTION_NODE:4,COMMENT_NODE:8,DOCUMENT_NODE:9};this.parseXmlString=function(a){window.ActiveXObject||window;if(void 0===a)return null;var b;if(window.DOMParser){var c=new window.DOMParser;try{b=c.parseFromString(a,"text/xml"),b.getElementsByTagNameNS("*","parsererror").length>0&&(b=null)}catch(d){b=null}}else 0==a.indexOf("")+2)),b=new ActiveXObject("Microsoft.XMLDOM"),b.async="false",b.loadXML(a);return b},this.asArray=function(a){return void 0===a||null==a?[]:a instanceof Array?a:[a]},this.toXmlDateTime=function(a){return a instanceof Date?a.toISOString():"number"==typeof a?new Date(a).toISOString():null},this.asDateTime=function(a){return"string"==typeof a?i(a):a},this.xml2json=function(a){return l(a)},this.xml_str2json=function(a){var b=this.parseXmlString(a);return null!=b?this.xml2json(b):null},this.json2xml_str=function(a){return x(a,"")},this.json2xml=function(a){var b=this.json2xml_str(a);return this.parseXmlString(b)},this.getVersion=function(){return y}}Object.defineProperty(c,"__esModule",{value:!0}),c.default=d,b.exports=c.default},{}],4:[function(a,b,c){(function(b){"use strict";function d(a){return a&&a.__esModule?a:{default:a}}Object.defineProperty(c,"__esModule",{value:!0});var e=a(91),f=d(e),g=a(47),h=d(g),i=a(45),j=d(i),k=a(48),l="undefined"!=typeof window&&window||b,m=l.dashjs;m||(m=l.dashjs={}),m.MediaPlayer=f.default,m.FactoryMaker=h.default,m.Debug=j.default,m.Version=(0,k.getVersionString)(),c.default=m,c.MediaPlayer=f.default,c.FactoryMaker=h.default,c.Debug=j.default}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{45:45,47:47,48:48,91:91}],5:[function(a,b,c){/*! codem-isoboxer v0.3.5 https://github.com/madebyhiro/codem-isoboxer/blob/master/LICENSE.txt */ -var d={};d.parseBuffer=function(a){return new e(a).parse()},d.addBoxProcessor=function(a,b){"string"==typeof a&&"function"==typeof b&&(f.prototype._boxProcessors[a]=b)},d.createFile=function(){return new e},d.createBox=function(a,b,c){var d=f.create(a);return b&&b.append(d,c),d},d.createFullBox=function(a,b,c){var e=d.createBox(a,b,c);return e.version=0,e.flags=0,e},d.Utils={},d.Utils.dataViewToString=function(a,b){var c=b||"utf-8";if("undefined"!=typeof TextDecoder)return new TextDecoder(c).decode(a);var d=[],e=0;if("utf-8"===c)for(;e>6),b.push(128|63&d)):d<65536?(b.push(224|d>>12),b.push(128|63&d>>6),b.push(128|63&d)):(b.push(240|d>>18),b.push(128|63&d>>12),b.push(128|63&d>>6),b.push(128|63&d))}return b},d.Utils.appendBox=function(a,b,c){if(b._offset=a._cursor.offset,b._root=a._root?a._root:a,b._raw=a._raw,b._parent=a,-1!==c){if(void 0===c||null===c)return void a.boxes.push(b);var d,e=-1;if("number"==typeof c)e=c;else{if("string"==typeof c)d=c;else{if("object"!=typeof c||!c.type)return void a.boxes.push(b);d=c.type}for(var f=0;f>3,b},f.prototype._readUint=function(a){var b,c,d=null,e=this._cursor.offset-this._raw.byteOffset;switch(a){case 8:d=this._raw.getUint8(e);break;case 16:d=this._raw.getUint16(e);break;case 24:b=this._raw.getUint16(e),c=this._raw.getUint8(e+2),d=(b<<8)+c;break;case 32:d=this._raw.getUint32(e);break;case 64:b=this._raw.getUint32(e),c=this._raw.getUint32(e+4),d=b*Math.pow(2,32)+c}return this._cursor.offset+=a>>3,d},f.prototype._readString=function(a){for(var b="",c=0;c0?a:this._raw.byteLength-(this._cursor.offset-this._offset);if(b>0){var c=new Uint8Array(this._raw.buffer,this._cursor.offset,b);return this._cursor.offset+=b,c}return null},f.prototype._readUTF8String=function(){var a=this._raw.byteLength-(this._cursor.offset-this._offset),b=null;return a>0&&(b=new DataView(this._raw.buffer,this._cursor.offset,a),this._cursor.offset+=a),b?d.Utils.dataViewToString(b):b},f.prototype._parseBox=function(){if(this._parsing=!0,this._cursor.offset=this._offset,this._offset+8>this._raw.buffer.byteLength)return void(this._root._incomplete=!0);switch(this._procField("size","uint",32),this._procField("type","string",4),1===this.size&&this._procField("largesize","uint",64),"uuid"===this.type&&this._procFieldArray("usertype",16,"uint",8),this.size){case 0:this._raw=new DataView(this._raw.buffer,this._offset,this._raw.byteLength-this._cursor.offset+8);break;case 1:this._offset+this.size>this._raw.buffer.byteLength?(this._incomplete=!0,this._root._incomplete=!0):this._raw=new DataView(this._raw.buffer,this._offset,this.largesize);break;default:this._offset+this.size>this._raw.buffer.byteLength?(this._incomplete=!0,this._root._incomplete=!0):this._raw=new DataView(this._raw.buffer,this._offset,this.size)}this._incomplete||(this._boxProcessors[this.type]&&this._boxProcessors[this.type].call(this),-1!==this._boxContainers.indexOf(this.type)?this._parseContainerBox():this._data=this._readData())},f.prototype._parseFullBox=function(){this.version=this._readUint(8),this.flags=this._readUint(24)},f.prototype._parseContainerBox=function(){for(this.boxes=[];this._cursor.offset-this._raw.byteOffset>3}else this.size+=a>>3},f.prototype._writeUint=function(a,b){if(this._rawo){var c,d,e=this._cursor.offset-this._rawo.byteOffset;switch(a){case 8:this._rawo.setUint8(e,b);break;case 16:this._rawo.setUint16(e,b);break;case 24:c=(16776960&b)>>8,d=255&b,this._rawo.setUint16(e,c),this._rawo.setUint8(e+2,d);break;case 32:this._rawo.setUint32(e,b);break;case 64:c=Math.floor(b/Math.pow(2,32)),d=b-c*Math.pow(2,32),this._rawo.setUint32(e,c),this._rawo.setUint32(e+4,d)}this._cursor.offset+=a>>3}else this.size+=a>>3},f.prototype._writeString=function(a,b){for(var c=0;c>10&31),96+(this.language>>5&31),96+(31&this.language))),this._procField("pre_defined","uint",16)},f.prototype._boxProcessors.mehd=function(){this._procFullBox(),this._procField("fragment_duration","uint",1==this.version?64:32)},f.prototype._boxProcessors.mfhd=function(){this._procFullBox(),this._procField("sequence_number","uint",32)},f.prototype._boxProcessors.mfro=function(){this._procFullBox(),this._procField("mfra_size","uint",32)},f.prototype._boxProcessors.mp4a=f.prototype._boxProcessors.enca=function(){this._procFieldArray("reserved1",6,"uint",8),this._procField("data_reference_index","uint",16),this._procFieldArray("reserved2",2,"uint",32),this._procField("channelcount","uint",16),this._procField("samplesize","uint",16),this._procField("pre_defined","uint",16),this._procField("reserved3","uint",16),this._procField("samplerate","template",32),this._procField("esds","data",-1)},f.prototype._boxProcessors.mvhd=function(){this._procFullBox(),this._procField("creation_time","uint",1==this.version?64:32),this._procField("modification_time","uint",1==this.version?64:32),this._procField("timescale","uint",32),this._procField("duration","uint",1==this.version?64:32),this._procField("rate","template",32),this._procField("volume","template",16),this._procField("reserved1","uint",16),this._procFieldArray("reserved2",2,"uint",32),this._procFieldArray("matrix",9,"template",32),this._procFieldArray("pre_defined",6,"uint",32),this._procField("next_track_ID","uint",32)},f.prototype._boxProcessors.payl=function(){this._procField("cue_text","utf8")},f.prototype._boxProcessors.pssh=function(){this._procFullBox(),this._procFieldArray("SystemID",16,"uint",8),this._procField("DataSize","uint",32),this._procFieldArray("Data",this.DataSize,"uint",8)},f.prototype._boxProcessors.schm=function(){this._procFullBox(),this._procField("scheme_type","uint",32),this._procField("scheme_version","uint",32),1&this.flags&&this._procField("scheme_uri","string",-1)},f.prototype._boxProcessors.sdtp=function(){this._procFullBox();var a=-1;this._parsing&&(a=this._raw.byteLength-(this._cursor.offset-this._raw.byteOffset)),this._procFieldArray("sample_dependency_table",a,"uint",8)},f.prototype._boxProcessors.sidx=function(){this._procFullBox(),this._procField("reference_ID","uint",32),this._procField("timescale","uint",32),this._procField("earliest_presentation_time","uint",1==this.version?64:32),this._procField("first_offset","uint",1==this.version?64:32),this._procField("reserved","uint",16),this._procField("reference_count","uint",16),this._procEntries("references",this.reference_count,function(a){this._parsing||(a.reference=(1&a.reference_type)<<31,a.reference|=2147483647&a.referenced_size,a.sap=(1&a.starts_with_SAP)<<31,a.sap|=(3&a.SAP_type)<<28,a.sap|=268435455&a.SAP_delta_time),this._procEntryField(a,"reference","uint",32),this._procEntryField(a,"subsegment_duration","uint",32),this._procEntryField(a,"sap","uint",32),this._parsing&&(a.reference_type=a.reference>>31&1,a.referenced_size=2147483647&a.reference,a.starts_with_SAP=a.sap>>31&1,a.SAP_type=a.sap>>28&7,a.SAP_delta_time=268435455&a.sap)})},f.prototype._boxProcessors.smhd=function(){this._procFullBox(),this._procField("balance","uint",16),this._procField("reserved","uint",16)},f.prototype._boxProcessors.ssix=function(){this._procFullBox(),this._procField("subsegment_count","uint",32),this._procEntries("subsegments",this.subsegment_count,function(a){this._procEntryField(a,"ranges_count","uint",32),this._procSubEntries(a,"ranges",a.ranges_count,function(a){this._procEntryField(a,"level","uint",8),this._procEntryField(a,"range_size","uint",24)})})},f.prototype._boxProcessors.stsd=function(){this._procFullBox(),this._procField("entry_count","uint",32),this._procSubBoxes("entries",this.entry_count)},f.prototype._boxProcessors.subs=function(){this._procFullBox(),this._procField("entry_count","uint",32),this._procEntries("entries",this.entry_count,function(a){this._procEntryField(a,"sample_delta","uint",32),this._procEntryField(a,"subsample_count","uint",16),this._procSubEntries(a,"subsamples",a.subsample_count,function(a){this._procEntryField(a,"subsample_size","uint",1===this.version?32:16),this._procEntryField(a,"subsample_priority","uint",8),this._procEntryField(a,"discardable","uint",8),this._procEntryField(a,"codec_specific_parameters","uint",32)})})},f.prototype._boxProcessors.tenc=function(){this._procFullBox(),this._procField("default_IsEncrypted","uint",24),this._procField("default_IV_size","uint",8),this._procFieldArray("default_KID",16,"uint",8)},f.prototype._boxProcessors.tfdt=function(){this._procFullBox(),this._procField("baseMediaDecodeTime","uint",1==this.version?64:32)},f.prototype._boxProcessors.tfhd=function(){this._procFullBox(),this._procField("track_ID","uint",32),1&this.flags&&this._procField("base_data_offset","uint",64),2&this.flags&&this._procField("sample_description_offset","uint",32),8&this.flags&&this._procField("default_sample_duration","uint",32),16&this.flags&&this._procField("default_sample_size","uint",32),32&this.flags&&this._procField("default_sample_flags","uint",32)},f.prototype._boxProcessors.tfra=function(){this._procFullBox(),this._procField("track_ID","uint",32),this._parsing||(this.reserved=0,this.reserved|=(48&this.length_size_of_traf_num)<<4,this.reserved|=(12&this.length_size_of_trun_num)<<2,this.reserved|=3&this.length_size_of_sample_num),this._procField("reserved","uint",32),this._parsing&&(this.length_size_of_traf_num=(48&this.reserved)>>4,this.length_size_of_trun_num=(12&this.reserved)>>2,this.length_size_of_sample_num=3&this.reserved),this._procField("number_of_entry","uint",32),this._procEntries("entries",this.number_of_entry,function(a){this._procEntryField(a,"time","uint",1===this.version?64:32),this._procEntryField(a,"moof_offset","uint",1===this.version?64:32),this._procEntryField(a,"traf_number","uint",8*(this.length_size_of_traf_num+1)),this._procEntryField(a,"trun_number","uint",8*(this.length_size_of_trun_num+1)),this._procEntryField(a,"sample_number","uint",8*(this.length_size_of_sample_num+1))})},f.prototype._boxProcessors.tkhd=function(){this._procFullBox(),this._procField("creation_time","uint",1==this.version?64:32),this._procField("modification_time","uint",1==this.version?64:32),this._procField("track_ID","uint",32),this._procField("reserved1","uint",32),this._procField("duration","uint",1==this.version?64:32),this._procFieldArray("reserved2",2,"uint",32),this._procField("layer","uint",16),this._procField("alternate_group","uint",16),this._procField("volume","template",16),this._procField("reserved3","uint",16),this._procFieldArray("matrix",9,"template",32),this._procField("width","template",32),this._procField("height","template",32)},f.prototype._boxProcessors.trex=function(){this._procFullBox(),this._procField("track_ID","uint",32),this._procField("default_sample_description_index","uint",32),this._procField("default_sample_duration","uint",32),this._procField("default_sample_size","uint",32),this._procField("default_sample_flags","uint",32)},f.prototype._boxProcessors.trun=function(){this._procFullBox(),this._procField("sample_count","uint",32),1&this.flags&&this._procField("data_offset","int",32),4&this.flags&&this._procField("first_sample_flags","uint",32),this._procEntries("samples",this.sample_count,function(a){256&this.flags&&this._procEntryField(a,"sample_duration","uint",32),512&this.flags&&this._procEntryField(a,"sample_size","uint",32),1024&this.flags&&this._procEntryField(a,"sample_flags","uint",32),2048&this.flags&&this._procEntryField(a,"sample_composition_time_offset",1===this.version?"int":"uint",32)})},f.prototype._boxProcessors["url "]=f.prototype._boxProcessors["urn "]=function(){this._procFullBox(),"urn "===this.type&&this._procField("name","string",-1),this._procField("location","string",-1)},f.prototype._boxProcessors.vlab=function(){this._procField("source_label","utf8")},f.prototype._boxProcessors.vmhd=function(){this._procFullBox(),this._procField("graphicsmode","uint",16),this._procFieldArray("opcolor",3,"uint",16)},f.prototype._boxProcessors.vttC=function(){this._procField("config","utf8")},f.prototype._boxProcessors.vtte=function(){}},{}],6:[function(a,b,c){"use strict";var d=Array.isArray,e=Object.keys,f=Object.prototype.hasOwnProperty;b.exports=function a(b,c){if(b===c)return!0;var g,h,i,j=d(b),k=d(c);if(j&&k){if((h=b.length)!=c.length)return!1;for(g=0;g0)throw new Error("Invalid string. Length must be a multiple of 4");var k=a.length;i="="===a.charAt(k-2)?2:"="===a.charAt(k-1)?1:0,j=new e(3*a.length/4-i),g=i>0?a.length-4:a.length;var l=0;for(d=0,f=0;d>16),c((65280&h)>>8),c(255&h);return 2===i?(h=b(a.charAt(d))<<2|b(a.charAt(d+1))>>4,c(255&h)):1===i&&(h=b(a.charAt(d))<<10|b(a.charAt(d+1))<<4|b(a.charAt(d+2))>>2,c(h>>8&255),c(255&h)),j}function d(a){function b(a){return"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(a)}function c(a){return b(a>>18&63)+b(a>>12&63)+b(a>>6&63)+b(63&a)}var d,e,f,g=a.length%3,h="";for(d=0,f=a.length-g;d>2),h+=b(e<<4&63),h+="==";break;case 2:e=(a[a.length-2]<<8)+a[a.length-1],h+=b(e>>10),h+=b(e>>4&63),h+=b(e<<2&63),h+="="}return h}var e="undefined"!=typeof Uint8Array?Uint8Array:Array,f="+".charCodeAt(0),g="/".charCodeAt(0),h="0".charCodeAt(0),i="a".charCodeAt(0),j="A".charCodeAt(0),k="-".charCodeAt(0),l="_".charCodeAt(0);a.toByteArray=c,a.fromByteArray=d}(void 0===c?this.base64js={}:c)},{}],8:[function(a,b,c){},{}],9:[function(a,b,c){(function(b){/*! - * The buffer module from node.js, for the browser. - * - * @author Feross Aboukhadijeh - * @license MIT - */ -"use strict";function d(){function a(){}try{var b=new Uint8Array(1);return b.foo=function(){return 42},b.constructor=a,42===b.foo()&&b.constructor===a&&"function"==typeof b.subarray&&0===b.subarray(1,1).byteLength}catch(c){return!1}}function e(){return f.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function f(a){return this instanceof f?(f.TYPED_ARRAY_SUPPORT||(this.length=0,this.parent=void 0),"number"==typeof a?g(this,a):"string"==typeof a?h(this,a,arguments.length>1?arguments[1]:"utf8"):i(this,a)):arguments.length>1?new f(a,arguments[1]):new f(a)}function g(a,b){if(a=p(a,b<0?0:0|q(b)),!f.TYPED_ARRAY_SUPPORT)for(var c=0;c>>1&&(a.parent=Z),a}function q(a){if(a>=e())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+e().toString(16)+" bytes");return 0|a}function r(a,b){if(!(this instanceof r))return new r(a,b);var c=new f(a,b);return delete c.parent,c}function s(a,b){"string"!=typeof a&&(a=""+a);var c=a.length;if(0===c)return 0;for(var d=!1;;)switch(b){case"ascii":case"binary":case"raw":case"raws":return c;case"utf8":case"utf-8":return R(a).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*c;case"hex":return c>>>1;case"base64":return U(a).length;default:if(d)return R(a).length;b=(""+b).toLowerCase(),d=!0}}function t(a,b,c){var d=!1;if(b|=0,c=void 0===c||c===1/0?this.length:0|c,a||(a="utf8"),b<0&&(b=0),c>this.length&&(c=this.length),c<=b)return"";for(;;)switch(a){case"hex":return F(this,b,c);case"utf8":case"utf-8":return B(this,b,c);case"ascii":return D(this,b,c);case"binary":return E(this,b,c);case"base64":return A(this,b,c);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return G(this,b,c);default:if(d)throw new TypeError("Unknown encoding: "+a);a=(a+"").toLowerCase(),d=!0}}function u(a,b,c,d){c=Number(c)||0;var e=a.length-c;d?(d=Number(d))>e&&(d=e):d=e;var f=b.length;if(f%2!=0)throw new Error("Invalid hex string");d>f/2&&(d=f/2);for(var g=0;g239?4:f>223?3:f>191?2:1;if(e+h<=c){var i,j,k,l;switch(h){case 1:f<128&&(g=f);break;case 2:i=a[e+1],128==(192&i)&&(l=(31&f)<<6|63&i)>127&&(g=l);break;case 3:i=a[e+1],j=a[e+2],128==(192&i)&&128==(192&j)&&(l=(15&f)<<12|(63&i)<<6|63&j)>2047&&(l<55296||l>57343)&&(g=l);break;case 4:i=a[e+1],j=a[e+2],k=a[e+3],128==(192&i)&&128==(192&j)&&128==(192&k)&&(l=(15&f)<<18|(63&i)<<12|(63&j)<<6|63&k)>65535&&l<1114112&&(g=l)}}null===g?(g=65533,h=1):g>65535&&(g-=65536,d.push(g>>>10&1023|55296),g=56320|1023&g),d.push(g),e+=h}return C(d)}function C(a){var b=a.length;if(b<=$)return String.fromCharCode.apply(String,a);for(var c="",d=0;dd)&&(c=d);for(var e="",f=b;fc)throw new RangeError("Trying to access beyond buffer length")}function I(a,b,c,d,e,g){if(!f.isBuffer(a))throw new TypeError("buffer must be a Buffer instance");if(b>e||ba.length)throw new RangeError("index out of range")}function J(a,b,c,d){b<0&&(b=65535+b+1);for(var e=0,f=Math.min(a.length-c,2);e>>8*(d?e:1-e)}function K(a,b,c,d){b<0&&(b=4294967295+b+1);for(var e=0,f=Math.min(a.length-c,4);e>>8*(d?e:3-e)&255}function L(a,b,c,d,e,f){if(b>e||ba.length)throw new RangeError("index out of range");if(c<0)throw new RangeError("index out of range")}function M(a,b,c,d,e){return e||L(a,b,c,4,3.4028234663852886e38,-3.4028234663852886e38),X.write(a,b,c,d,23,4),c+4}function N(a,b,c,d,e){return e||L(a,b,c,8,1.7976931348623157e308,-1.7976931348623157e308),X.write(a,b,c,d,52,8),c+8}function O(a){if(a=P(a).replace(aa,""),a.length<2)return"";for(;a.length%4!=0;)a+="=";return a}function P(a){return a.trim?a.trim():a.replace(/^\s+|\s+$/g,"")}function Q(a){return a<16?"0"+a.toString(16):a.toString(16)}function R(a,b){b=b||1/0;for(var c,d=a.length,e=null,f=[],g=0;g55295&&c<57344){if(!e){if(c>56319){(b-=3)>-1&&f.push(239,191,189);continue}if(g+1===d){(b-=3)>-1&&f.push(239,191,189);continue}e=c;continue}if(c<56320){(b-=3)>-1&&f.push(239,191,189),e=c;continue}c=65536+(e-55296<<10|c-56320)}else e&&(b-=3)>-1&&f.push(239,191,189);if(e=null,c<128){if((b-=1)<0)break;f.push(c)}else if(c<2048){if((b-=2)<0)break;f.push(c>>6|192,63&c|128)}else if(c<65536){if((b-=3)<0)break;f.push(c>>12|224,c>>6&63|128,63&c|128)}else{if(!(c<1114112))throw new Error("Invalid code point");if((b-=4)<0)break;f.push(c>>18|240,c>>12&63|128,c>>6&63|128,63&c|128)}}return f}function S(a){for(var b=[],c=0;c>8,e=c%256,f.push(e),f.push(d);return f}function U(a){return W.toByteArray(O(a))}function V(a,b,c,d){for(var e=0;e=b.length||e>=a.length);e++)b[e+c]=a[e];return e}var W=a(7),X=a(13),Y=a(10);c.Buffer=f,c.SlowBuffer=r,c.INSPECT_MAX_BYTES=50,f.poolSize=8192;var Z={};f.TYPED_ARRAY_SUPPORT=void 0!==b.TYPED_ARRAY_SUPPORT?b.TYPED_ARRAY_SUPPORT:d(),f.TYPED_ARRAY_SUPPORT?(f.prototype.__proto__=Uint8Array.prototype,f.__proto__=Uint8Array):(f.prototype.length=void 0,f.prototype.parent=void 0),f.isBuffer=function(a){return!(null==a||!a._isBuffer)},f.compare=function(a,b){if(!f.isBuffer(a)||!f.isBuffer(b))throw new TypeError("Arguments must be Buffers");if(a===b)return 0;for(var c=a.length,d=b.length,e=0,g=Math.min(c,d);e0&&(a=this.toString("hex",0,b).match(/.{2}/g).join(" "),this.length>b&&(a+=" ... ")),""},f.prototype.compare=function(a){if(!f.isBuffer(a))throw new TypeError("Argument must be a Buffer");return this===a?0:f.compare(this,a)},f.prototype.indexOf=function(a,b){function c(a,b,c){for(var d=-1,e=0;c+e2147483647?b=2147483647:b<-2147483648&&(b=-2147483648),b>>=0,0===this.length)return-1;if(b>=this.length)return-1;if(b<0&&(b=Math.max(this.length+b,0)),"string"==typeof a)return 0===a.length?-1:String.prototype.indexOf.call(this,a,b);if(f.isBuffer(a))return c(this,a,b);if("number"==typeof a)return f.TYPED_ARRAY_SUPPORT&&"function"===Uint8Array.prototype.indexOf?Uint8Array.prototype.indexOf.call(this,a,b):c(this,[a],b);throw new TypeError("val must be string, number or Buffer")},f.prototype.get=function(a){return console.log(".get() is deprecated. Access using array indexes instead."),this.readUInt8(a)},f.prototype.set=function(a,b){return console.log(".set() is deprecated. Access using array indexes instead."),this.writeUInt8(a,b)},f.prototype.write=function(a,b,c,d){if(void 0===b)d="utf8",c=this.length,b=0;else if(void 0===c&&"string"==typeof b)d=b,c=this.length,b=0;else if(isFinite(b))b|=0,isFinite(c)?(c|=0,void 0===d&&(d="utf8")):(d=c,c=void 0);else{var e=d;d=b,b=0|c,c=e}var f=this.length-b;if((void 0===c||c>f)&&(c=f),a.length>0&&(c<0||b<0)||b>this.length)throw new RangeError("attempt to write outside buffer bounds");d||(d="utf8");for(var g=!1;;)switch(d){case"hex":return u(this,a,b,c);case"utf8":case"utf-8":return v(this,a,b,c);case"ascii":return w(this,a,b,c);case"binary":return x(this,a,b,c);case"base64":return y(this,a,b,c);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return z(this,a,b,c);default:if(g)throw new TypeError("Unknown encoding: "+d);d=(""+d).toLowerCase(),g=!0}},f.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var $=4096;f.prototype.slice=function(a,b){var c=this.length;a=~~a,b=void 0===b?c:~~b,a<0?(a+=c)<0&&(a=0):a>c&&(a=c),b<0?(b+=c)<0&&(b=0):b>c&&(b=c),b0&&(e*=256);)d+=this[a+--b]*e;return d},f.prototype.readUInt8=function(a,b){return b||H(a,1,this.length),this[a]},f.prototype.readUInt16LE=function(a,b){return b||H(a,2,this.length),this[a]|this[a+1]<<8},f.prototype.readUInt16BE=function(a,b){return b||H(a,2,this.length),this[a]<<8|this[a+1]},f.prototype.readUInt32LE=function(a,b){return b||H(a,4,this.length),(this[a]|this[a+1]<<8|this[a+2]<<16)+16777216*this[a+3]},f.prototype.readUInt32BE=function(a,b){return b||H(a,4,this.length),16777216*this[a]+(this[a+1]<<16|this[a+2]<<8|this[a+3])},f.prototype.readIntLE=function(a,b,c){a|=0,b|=0,c||H(a,b,this.length);for(var d=this[a],e=1,f=0;++f=e&&(d-=Math.pow(2,8*b)),d},f.prototype.readIntBE=function(a,b,c){a|=0,b|=0,c||H(a,b,this.length);for(var d=b,e=1,f=this[a+--d];d>0&&(e*=256);)f+=this[a+--d]*e;return e*=128,f>=e&&(f-=Math.pow(2,8*b)),f},f.prototype.readInt8=function(a,b){return b||H(a,1,this.length),128&this[a]?-1*(255-this[a]+1):this[a]},f.prototype.readInt16LE=function(a,b){b||H(a,2,this.length);var c=this[a]|this[a+1]<<8;return 32768&c?4294901760|c:c},f.prototype.readInt16BE=function(a,b){b||H(a,2,this.length);var c=this[a+1]|this[a]<<8;return 32768&c?4294901760|c:c},f.prototype.readInt32LE=function(a,b){return b||H(a,4,this.length),this[a]|this[a+1]<<8|this[a+2]<<16|this[a+3]<<24},f.prototype.readInt32BE=function(a,b){return b||H(a,4,this.length),this[a]<<24|this[a+1]<<16|this[a+2]<<8|this[a+3]},f.prototype.readFloatLE=function(a,b){return b||H(a,4,this.length),X.read(this,a,!0,23,4)},f.prototype.readFloatBE=function(a,b){return b||H(a,4,this.length),X.read(this,a,!1,23,4)},f.prototype.readDoubleLE=function(a,b){return b||H(a,8,this.length),X.read(this,a,!0,52,8)},f.prototype.readDoubleBE=function(a,b){return b||H(a,8,this.length),X.read(this,a,!1,52,8)},f.prototype.writeUIntLE=function(a,b,c,d){a=+a,b|=0,c|=0,d||I(this,a,b,c,Math.pow(2,8*c),0);var e=1,f=0;for(this[b]=255&a;++f=0&&(f*=256);)this[b+e]=a/f&255;return b+c},f.prototype.writeUInt8=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,1,255,0),f.TYPED_ARRAY_SUPPORT||(a=Math.floor(a)),this[b]=255&a,b+1},f.prototype.writeUInt16LE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,2,65535,0),f.TYPED_ARRAY_SUPPORT?(this[b]=255&a,this[b+1]=a>>>8):J(this,a,b,!0),b+2},f.prototype.writeUInt16BE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,2,65535,0),f.TYPED_ARRAY_SUPPORT?(this[b]=a>>>8,this[b+1]=255&a):J(this,a,b,!1),b+2},f.prototype.writeUInt32LE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,4,4294967295,0),f.TYPED_ARRAY_SUPPORT?(this[b+3]=a>>>24,this[b+2]=a>>>16,this[b+1]=a>>>8,this[b]=255&a):K(this,a,b,!0),b+4},f.prototype.writeUInt32BE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,4,4294967295,0),f.TYPED_ARRAY_SUPPORT?(this[b]=a>>>24,this[b+1]=a>>>16,this[b+2]=a>>>8,this[b+3]=255&a):K(this,a,b,!1),b+4},f.prototype.writeIntLE=function(a,b,c,d){if(a=+a,b|=0,!d){var e=Math.pow(2,8*c-1);I(this,a,b,c,e-1,-e)}var f=0,g=1,h=a<0?1:0;for(this[b]=255&a;++f>0)-h&255;return b+c},f.prototype.writeIntBE=function(a,b,c,d){if(a=+a,b|=0,!d){var e=Math.pow(2,8*c-1);I(this,a,b,c,e-1,-e)}var f=c-1,g=1,h=a<0?1:0;for(this[b+f]=255&a;--f>=0&&(g*=256);)this[b+f]=(a/g>>0)-h&255;return b+c},f.prototype.writeInt8=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,1,127,-128),f.TYPED_ARRAY_SUPPORT||(a=Math.floor(a)),a<0&&(a=255+a+1),this[b]=255&a,b+1},f.prototype.writeInt16LE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,2,32767,-32768),f.TYPED_ARRAY_SUPPORT?(this[b]=255&a,this[b+1]=a>>>8):J(this,a,b,!0),b+2},f.prototype.writeInt16BE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,2,32767,-32768),f.TYPED_ARRAY_SUPPORT?(this[b]=a>>>8,this[b+1]=255&a):J(this,a,b,!1),b+2},f.prototype.writeInt32LE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,4,2147483647,-2147483648),f.TYPED_ARRAY_SUPPORT?(this[b]=255&a,this[b+1]=a>>>8,this[b+2]=a>>>16,this[b+3]=a>>>24):K(this,a,b,!0),b+4},f.prototype.writeInt32BE=function(a,b,c){return a=+a,b|=0,c||I(this,a,b,4,2147483647,-2147483648),a<0&&(a=4294967295+a+1),f.TYPED_ARRAY_SUPPORT?(this[b]=a>>>24,this[b+1]=a>>>16,this[b+2]=a>>>8,this[b+3]=255&a):K(this,a,b,!1),b+4},f.prototype.writeFloatLE=function(a,b,c){return M(this,a,b,!0,c)},f.prototype.writeFloatBE=function(a,b,c){return M(this,a,b,!1,c)},f.prototype.writeDoubleLE=function(a,b,c){return N(this,a,b,!0,c)},f.prototype.writeDoubleBE=function(a,b,c){return N(this,a,b,!1,c)},f.prototype.copy=function(a,b,c,d){if(c||(c=0),d||0===d||(d=this.length),b>=a.length&&(b=a.length),b||(b=0),d>0&&d=this.length)throw new RangeError("sourceStart out of bounds");if(d<0)throw new RangeError("sourceEnd out of bounds");d>this.length&&(d=this.length),a.length-b=0;e--)a[e+b]=this[e+c];else if(g<1e3||!f.TYPED_ARRAY_SUPPORT)for(e=0;e=this.length)throw new RangeError("start out of bounds");if(c<0||c>this.length)throw new RangeError("end out of bounds");var d;if("number"==typeof a)for(d=b;d0&&this._events[a].length>c&&(this._events[a].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[a].length),"function"==typeof console.trace&&console.trace())}return this},d.prototype.on=d.prototype.addListener,d.prototype.once=function(a,b){function c(){this.removeListener(a,c),d||(d=!0,b.apply(this,arguments))}if(!e(b))throw TypeError("listener must be a function");var d=!1;return c.listener=b,this.on(a,c),this},d.prototype.removeListener=function(a,b){var c,d,f,h;if(!e(b))throw TypeError("listener must be a function");if(!this._events||!this._events[a])return this;if(c=this._events[a],f=c.length,d=-1,c===b||e(c.listener)&&c.listener===b)delete this._events[a],this._events.removeListener&&this.emit("removeListener",a,b);else if(g(c)){for(h=f;h-- >0;)if(c[h]===b||c[h].listener&&c[h].listener===b){d=h;break}if(d<0)return this;1===c.length?(c.length=0,delete this._events[a]):c.splice(d,1),this._events.removeListener&&this.emit("removeListener",a,b)}return this},d.prototype.removeAllListeners=function(a){var b,c;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[a]&&delete this._events[a],this;if(0===arguments.length){for(b in this._events)"removeListener"!==b&&this.removeAllListeners(b);return this.removeAllListeners("removeListener"),this._events={},this}if(c=this._events[a],e(c))this.removeListener(a,c);else for(;c.length;)this.removeListener(a,c[c.length-1]);return delete this._events[a],this},d.prototype.listeners=function(a){return this._events&&this._events[a]?e(this._events[a])?[this._events[a]]:this._events[a].slice():[]},d.listenerCount=function(a,b){return a._events&&a._events[b]?e(a._events[b])?1:a._events[b].length:0}},{}],13:[function(a,b,c){c.read=function(a,b,c,d,e){var f,g,h=8*e-d-1,i=(1<>1,k=-7,l=c?e-1:0,m=c?-1:1,n=a[b+l];for(l+=m,f=n&(1<<-k)-1,n>>=-k,k+=h;k>0;f=256*f+a[b+l],l+=m,k-=8);for(g=f&(1<<-k)-1,f>>=-k,k+=d;k>0;g=256*g+a[b+l],l+=m,k-=8);if(0===f)f=1-j;else{if(f===i)return g?NaN:1/0*(n?-1:1);g+=Math.pow(2,d),f-=j}return(n?-1:1)*g*Math.pow(2,f-d)},c.write=function(a,b,c,d,e,f){var g,h,i,j=8*f-e-1,k=(1<>1,m=23===e?Math.pow(2,-24)-Math.pow(2,-77):0,n=d?0:f-1,o=d?1:-1,p=b<0||0===b&&1/b<0?1:0;for(b=Math.abs(b),isNaN(b)||b===1/0?(h=isNaN(b)?1:0,g=k):(g=Math.floor(Math.log(b)/Math.LN2),b*(i=Math.pow(2,-g))<1&&(g--,i*=2),b+=g+l>=1?m/i:m*Math.pow(2,1-l),b*i>=2&&(g++,i/=2),g+l>=k?(h=0,g=k):g+l>=1?(h=(b*i-1)*Math.pow(2,e),g+=l):(h=b*Math.pow(2,l-1)*Math.pow(2,e),g=0));e>=8;a[c+n]=255&h,n+=o,h/=256,e-=8);for(g=g<0;a[c+n]=255&g,n+=o,g/=256,j-=8);a[c+n-o]|=128*p}},{}],14:[function(a,b,c){"function"==typeof Object.create?b.exports=function(a,b){a.super_=b,a.prototype=Object.create(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}})}:b.exports=function(a,b){a.super_=b;var c=function(){};c.prototype=b.prototype,a.prototype=new c,a.prototype.constructor=a}},{}],15:[function(a,b,c){function d(a){return!!a.constructor&&"function"==typeof a.constructor.isBuffer&&a.constructor.isBuffer(a)}function e(a){return"function"==typeof a.readFloatLE&&"function"==typeof a.slice&&d(a.slice(0,0))}/*! - * Determine if an object is a Buffer - * - * @author Feross Aboukhadijeh - * @license MIT - */ -b.exports=function(a){return null!=a&&(d(a)||e(a)||!!a._isBuffer)}},{}],16:[function(a,b,c){(function(a){"use strict";function c(b,c,d,e){if("function"!=typeof b)throw new TypeError('"callback" argument must be a function');var f,g,h=arguments.length;switch(h){case 0:case 1:return a.nextTick(b);case 2:return a.nextTick(function(){b.call(null,c)});case 3:return a.nextTick(function(){b.call(null,c,d)});case 4:return a.nextTick(function(){b.call(null,c,d,e)});default:for(f=new Array(h-1),g=0;g1)for(var c=1;c0?("string"==typeof b||g.objectMode||Object.getPrototypeOf(b)===L.prototype||(b=e(b)),d?g.endEmitted?a.emit("error",new Error("stream.unshift() after end event")):k(a,g,b,!0):g.ended?a.emit("error",new Error("stream.push() after EOF")):(g.reading=!1,g.decoder&&!c?(b=g.decoder.write(b),g.objectMode||0!==b.length?k(a,g,b,!1):s(a,g)):k(a,g,b,!1))):d||(g.reading=!1)}return m(g)}function k(a,b,c,d){b.flowing&&0===b.length&&!b.sync?(a.emit("data",c),a.read(0)):(b.length+=b.objectMode?1:c.length,d?b.buffer.unshift(c):b.buffer.push(c),b.needReadable&&q(a)),s(a,b)}function l(a,b){var c;return f(b)||"string"==typeof b||void 0===b||a.objectMode||(c=new TypeError("Invalid non-string/buffer chunk")),c}function m(a){return!a.ended&&(a.needReadable||a.length=U?a=U:(a--,a|=a>>>1,a|=a>>>2,a|=a>>>4,a|=a>>>8,a|=a>>>16,a++),a}function o(a,b){return a<=0||0===b.length&&b.ended?0:b.objectMode?1:a!==a?b.flowing&&b.length?b.buffer.head.data.length:b.length:(a>b.highWaterMark&&(b.highWaterMark=n(a)),a<=b.length?a:b.ended?b.length:(b.needReadable=!0,0))}function p(a,b){if(!b.ended){if(b.decoder){var c=b.decoder.end();c&&c.length&&(b.buffer.push(c),b.length+=b.objectMode?1:c.length)}b.ended=!0,q(a)}}function q(a){var b=a._readableState;b.needReadable=!1,b.emittedReadable||(P("emitReadable",b.flowing),b.emittedReadable=!0,b.sync?G.nextTick(r,a):r(a))}function r(a){P("emit readable"),a.emit("readable"),y(a)}function s(a,b){b.readingMore||(b.readingMore=!0,G.nextTick(t,a,b))}function t(a,b){for(var c=b.length;!b.reading&&!b.flowing&&!b.ended&&b.length=b.length?(c=b.decoder?b.buffer.join(""):1===b.buffer.length?b.buffer.head.data:b.buffer.concat(b.length),b.buffer.clear()):c=A(a,b.buffer,b.decoder),c}function A(a,b,c){var d;return af.length?f.length:a;if(g===f.length?e+=f:e+=f.slice(0,a),0===(a-=g)){g===f.length?(++d,c.next?b.head=c.next:b.head=b.tail=null):(b.head=c,c.data=f.slice(g));break}++d}return b.length-=d,e}function C(a,b){var c=L.allocUnsafe(a),d=b.head,e=1;for(d.data.copy(c),a-=d.data.length;d=d.next;){var f=d.data,g=a>f.length?f.length:a;if(f.copy(c,c.length-a,0,g),0===(a-=g)){g===f.length?(++e,d.next?b.head=d.next:b.head=b.tail=null):(b.head=d,d.data=f.slice(g));break}++e}return b.length-=e,c}function D(a){var b=a._readableState;if(b.length>0)throw new Error('"endReadable()" called on non-empty stream');b.endEmitted||(b.ended=!0,G.nextTick(E,b,a))}function E(a,b){a.endEmitted||0!==a.length||(a.endEmitted=!0,b.readable=!1,b.emit("end"))}function F(a,b){for(var c=0,d=a.length;c=b.highWaterMark||b.ended))return P("read: emitReadable",b.length,b.ended),0===b.length&&b.ended?D(this):q(this),null;if(0===(a=o(a,b))&&b.ended)return 0===b.length&&D(this),null;var d=b.needReadable;P("need readable",d),(0===b.length||b.length-a0?z(a,b):null,null===e?(b.needReadable=!0,a=0):b.length-=a,0===b.length&&(b.ended||(b.needReadable=!0),c!==a&&b.ended&&D(this)),null!==e&&this.emit("data",e),e},i.prototype._read=function(a){this.emit("error",new Error("_read() is not implemented"))},i.prototype.pipe=function(a,b){function d(a,b){P("onunpipe"),a===m&&b&&!1===b.hasUnpiped&&(b.hasUnpiped=!0,f())}function e(){P("onend"),a.end()}function f(){P("cleanup"),a.removeListener("close",j),a.removeListener("finish",k),a.removeListener("drain",q),a.removeListener("error",i),a.removeListener("unpipe",d),m.removeListener("end",e),m.removeListener("end",l),m.removeListener("data",h),r=!0,!n.awaitDrain||a._writableState&&!a._writableState.needDrain||q()}function h(b){P("ondata"),s=!1,!1!==a.write(b)||s||((1===n.pipesCount&&n.pipes===a||n.pipesCount>1&&-1!==F(n.pipes,a))&&!r&&(P("false write response, pause",m._readableState.awaitDrain),m._readableState.awaitDrain++,s=!0),m.pause())}function i(b){P("onerror",b),l(),a.removeListener("error",i),0===J(a,"error")&&a.emit("error",b)}function j(){a.removeListener("finish",k),l()}function k(){P("onfinish"),a.removeListener("close",j),l()}function l(){P("unpipe"),m.unpipe(a)}var m=this,n=this._readableState;switch(n.pipesCount){case 0:n.pipes=a;break;case 1:n.pipes=[n.pipes,a];break;default:n.pipes.push(a)}n.pipesCount+=1,P("pipe count=%d opts=%j",n.pipesCount,b);var o=(!b||!1!==b.end)&&a!==c.stdout&&a!==c.stderr,p=o?e:l;n.endEmitted?G.nextTick(p):m.once("end",p),a.on("unpipe",d);var q=u(m);a.on("drain",q);var r=!1,s=!1;return m.on("data",h),g(a,"error",i),a.once("close",j),a.once("finish",k),a.emit("pipe",m),n.flowing||(P("pipe resume"),m.resume()),a},i.prototype.unpipe=function(a){var b=this._readableState,c={hasUnpiped:!1};if(0===b.pipesCount)return this;if(1===b.pipesCount)return a&&a!==b.pipes?this:(a||(a=b.pipes),b.pipes=null,b.pipesCount=0,b.flowing=!1,a&&a.emit("unpipe",this,c),this);if(!a){var d=b.pipes,e=b.pipesCount;b.pipes=null,b.pipesCount=0,b.flowing=!1;for(var f=0;f-1?setImmediate:B.nextTick;j.WritableState=i;var E=a(11);E.inherits=a(14);var F={deprecate:a(36)},G=a(26),H=a(33).Buffer,I=d.Uint8Array||function(){},J=a(25);E.inherits(j,G),i.prototype.getBuffer=function(){for(var a=this.bufferedRequest,b=[];a;)b.push(a),a=a.next;return b},function(){try{Object.defineProperty(i.prototype,"buffer",{get:F.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(a){}}();var K;"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(K=Function.prototype[Symbol.hasInstance],Object.defineProperty(j,Symbol.hasInstance,{value:function(a){return!!K.call(this,a)||this===j&&(a&&a._writableState instanceof i)}})):K=function(a){return a instanceof this},j.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},j.prototype.write=function(a,b,c){var d=this._writableState,e=!1,i=!d.objectMode&&g(a);return i&&!H.isBuffer(a)&&(a=f(a)),"function"==typeof b&&(c=b,b=null),i?b="buffer":b||(b=d.defaultEncoding),"function"!=typeof c&&(c=h),d.ended?k(this,c):(i||l(this,d,a,c))&&(d.pendingcb++,e=n(this,d,i,a,b,c)),e},j.prototype.cork=function(){this._writableState.corked++},j.prototype.uncork=function(){var a=this._writableState;a.corked&&(a.corked--,a.writing||a.corked||a.finished||a.bufferProcessing||!a.bufferedRequest||u(this,a))},j.prototype.setDefaultEncoding=function(a){if("string"==typeof a&&(a=a.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((a+"").toLowerCase())>-1))throw new TypeError("Unknown encoding: "+a);return this._writableState.defaultEncoding=a,this},Object.defineProperty(j.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),j.prototype._write=function(a,b,c){c(new Error("_write() is not implemented"))},j.prototype._writev=null,j.prototype.end=function(a,b,c){var d=this._writableState;"function"==typeof a?(c=a,a=null,b=null):"function"==typeof b&&(c=b,b=null),null!==a&&void 0!==a&&this.write(a,b),d.corked&&(d.corked=1,this.uncork()),d.ending||d.finished||z(this,d,c)},Object.defineProperty(j.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(a){this._writableState&&(this._writableState.destroyed=a)}}),j.prototype.destroy=J.destroy,j.prototype._undestroy=J.undestroy,j.prototype._destroy=function(a,b){this.end(),b(a)}}).call(this,a(17),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{11:11,14:14,16:16,17:17,19:19,25:25,26:26,33:33,36:36}],24:[function(a,b,c){"use strict";function d(a,b){if(!(a instanceof b))throw new TypeError("Cannot call a class as a function")}function e(a,b,c){a.copy(b,c)}var f=a(33).Buffer,g=a(8);b.exports=function(){function a(){d(this,a),this.head=null,this.tail=null,this.length=0}return a.prototype.push=function(a){var b={data:a,next:null};this.length>0?this.tail.next=b:this.head=b,this.tail=b,++this.length},a.prototype.unshift=function(a){var b={data:a,next:this.head};0===this.length&&(this.tail=b),this.head=b,++this.length},a.prototype.shift=function(){if(0!==this.length){var a=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,a}},a.prototype.clear=function(){this.head=this.tail=null,this.length=0},a.prototype.join=function(a){if(0===this.length)return"";for(var b=this.head,c=""+b.data;b=b.next;)c+=a+b.data;return c},a.prototype.concat=function(a){if(0===this.length)return f.alloc(0);if(1===this.length)return this.head.data;for(var b=f.allocUnsafe(a>>>0),c=this.head,d=0;c;)e(c.data,b,d),d+=c.data.length,c=c.next;return b},a}(),g&&g.inspect&&g.inspect.custom&&(b.exports.prototype[g.inspect.custom]=function(){var a=g.inspect({length:this.length});return this.constructor.name+" "+a})},{33:33,8:8}],25:[function(a,b,c){"use strict";function d(a,b){var c=this,d=this._readableState&&this._readableState.destroyed,e=this._writableState&&this._writableState.destroyed;return d||e?(b?b(a):!a||this._writableState&&this._writableState.errorEmitted||g.nextTick(f,this,a),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(a||null,function(a){!b&&a?(g.nextTick(f,c,a),c._writableState&&(c._writableState.errorEmitted=!0)):b&&b(a)}),this)}function e(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}function f(a,b){a.emit("error",b)}var g=a(16);b.exports={destroy:d,undestroy:e}},{16:16}],26:[function(a,b,c){b.exports=a(12).EventEmitter},{12:12}],27:[function(a,b,c){arguments[4][10][0].apply(c,arguments)},{10:10}],28:[function(a,b,c){"use strict";function d(a){if(!a)return"utf8";for(var b;;)switch(a){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return a;default:if(b)return;a=(""+a).toLowerCase(),b=!0}}function e(a){var b=d(a);if("string"!=typeof b&&(s.isEncoding===t||!t(a)))throw new Error("Unknown encoding: "+a);return b||a}function f(a){this.encoding=e(a);var b;switch(this.encoding){case"utf16le":this.text=m,this.end=n,b=4;break;case"utf8":this.fillLast=j,b=4;break;case"base64":this.text=o,this.end=p,b=3;break;default:return this.write=q,void(this.end=r)}this.lastNeed=0,this.lastTotal=0,this.lastChar=s.allocUnsafe(b)}function g(a){return a<=127?0:a>>5==6?2:a>>4==14?3:a>>3==30?4:a>>6==2?-1:-2}function h(a,b,c){var d=b.length-1;if(d=0?(e>0&&(a.lastNeed=e-1),e):--d=0?(e>0&&(a.lastNeed=e-2),e):--d=0?(e>0&&(2===e?e=0:a.lastNeed=e-3),e):0)}function i(a,b,c){if(128!=(192&b[0]))return a.lastNeed=0,"�";if(a.lastNeed>1&&b.length>1){if(128!=(192&b[1]))return a.lastNeed=1,"�";if(a.lastNeed>2&&b.length>2&&128!=(192&b[2]))return a.lastNeed=2,"�"}}function j(a){var b=this.lastTotal-this.lastNeed,c=i(this,a,b);return void 0!==c?c:this.lastNeed<=a.length?(a.copy(this.lastChar,b,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(a.copy(this.lastChar,b,0,a.length),void(this.lastNeed-=a.length))}function k(a,b){var c=h(this,a,b);if(!this.lastNeed)return a.toString("utf8",b);this.lastTotal=c;var d=a.length-(c-this.lastNeed);return a.copy(this.lastChar,0,d),a.toString("utf8",b,d)}function l(a){var b=a&&a.length?this.write(a):"";return this.lastNeed?b+"�":b}function m(a,b){if((a.length-b)%2==0){var c=a.toString("utf16le",b);if(c){var d=c.charCodeAt(c.length-1);if(d>=55296&&d<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=a[a.length-2],this.lastChar[1]=a[a.length-1],c.slice(0,-1)}return c}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=a[a.length-1],a.toString("utf16le",b,a.length-1)}function n(a){var b=a&&a.length?this.write(a):"";if(this.lastNeed){var c=this.lastTotal-this.lastNeed;return b+this.lastChar.toString("utf16le",0,c)}return b}function o(a,b){var c=(a.length-b)%3;return 0===c?a.toString("base64",b):(this.lastNeed=3-c,this.lastTotal=3,1===c?this.lastChar[0]=a[a.length-1]:(this.lastChar[0]=a[a.length-2],this.lastChar[1]=a[a.length-1]),a.toString("base64",b,a.length-c))}function p(a){var b=a&&a.length?this.write(a):"";return this.lastNeed?b+this.lastChar.toString("base64",0,3-this.lastNeed):b}function q(a){return a.toString(this.encoding)}function r(a){return a&&a.length?this.write(a):""}var s=a(33).Buffer,t=s.isEncoding||function(a){switch((a=""+a)&&a.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};c.StringDecoder=f,f.prototype.write=function(a){if(0===a.length)return"";var b,c;if(this.lastNeed){if(void 0===(b=this.fillLast(a)))return"";c=this.lastNeed,this.lastNeed=0}else c=0;return c=this.charLength-this.charReceived?this.charLength-this.charReceived:a.length;if(a.copy(this.charBuffer,this.charReceived,0,c),this.charReceived+=c,this.charReceived=55296&&d<=56319)){if(this.charReceived=this.charLength=0,0===a.length)return b;break}this.charLength+=this.surrogateSize,b=""}this.detectIncompleteChar(a);var e=a.length;this.charLength&&(a.copy(this.charBuffer,0,a.length-this.charReceived,e),e-=this.charReceived),b+=a.toString(this.encoding,0,e);var e=b.length-1,d=b.charCodeAt(e);if(d>=55296&&d<=56319){var f=this.surrogateSize;return this.charLength+=f,this.charReceived+=f,this.charBuffer.copy(this.charBuffer,f,0,f),a.copy(this.charBuffer,0,0,f),b.substring(0,e)}return b},j.prototype.detectIncompleteChar=function(a){for(var b=a.length>=3?3:a.length;b>0;b--){var c=a[a.length-b];if(1==b&&c>>5==6){this.charLength=2;break}if(b<=2&&c>>4==14){this.charLength=3;break}if(b<=3&&c>>3==30){this.charLength=4;break}}this.charReceived=b},j.prototype.end=function(a){var b="";if(a&&a.length&&(b=this.write(a)),this.charReceived){var c=this.charReceived,d=this.charBuffer,e=this.encoding;b+=d.slice(0,c).toString(e)}return b}},{9:9}],36:[function(a,b,c){(function(a){function c(a,b){function c(){if(!e){if(d("throwDeprecation"))throw new Error(b);d("traceDeprecation")?console.trace(b):console.warn(b),e=!0}return a.apply(this,arguments)}if(d("noDeprecation"))return a;var e=!1;return c}function d(b){try{if(!a.localStorage)return!1}catch(d){return!1}var c=a.localStorage[b];return null!=c&&"true"===String(c).toLowerCase()}b.exports=c}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],37:[function(a,b,c){!function(a,b,c,d,e){function f(a){this.node=a}function g(){this.events=[],this.head=null,this.body=null}function h(){this.styling=null,this.layout=null}function i(){this.styles={}}function j(){this.id=null,this.styleAttrs=null,this.styleRefs=null}function k(){this.regions={}}function l(a){this.kind=a,this.begin=null,this.end=null,this.styleAttrs=null,this.regionID=null,this.sets=null,this.timeContainer=null}function m(){l.call(this,"body")}function n(){l.call(this,"div")}function o(){l.call(this,"p")}function p(){l.call(this,"span"),this.space=null}function q(){l.call(this,"span"),this.space=null,this.text=null}function r(){l.call(this,"br")}function s(){this.id=null,this.begin=null,this.end=null,this.styleAttrs=null,this.sets=null}function t(){this.begin=null,this.end=null,this.qname=null,this.value=null}function u(a){return a&&"xml:id"in a.attributes?a.attributes["xml:id"].value||null:null}function v(a){return a&&"region"in a.attributes?a.attributes.region.value:""}function w(a,b){var c=a&&"timeContainer"in a.attributes?a.attributes.timeContainer.value:null;return c&&"par"!==c?"seq"===c?"seq":(K(b,"Illegal value of timeContainer (assuming 'par')"),"par"):"par"}function x(a){return a&&"style"in a.attributes?a.attributes.style.value.split(" "):[]}function y(a,b){var c={};if(null!==a)for(var e in a.attributes){var f=a.attributes[e].uri+" "+a.attributes[e].local,g=d.byQName[f];if(void 0!==g){var h=g.parse(a.attributes[e].value);null!==h?(c[f]=h,g===d.byName.zIndex&&J(b,"zIndex attribute present but not used by IMSC1 since regions do not overlap")):K(b,"Cannot parse styling attribute "+f+" --\x3e "+a.attributes[e].value)}}return c}function z(a,b,c){for(var d in a.attributes)if(a.attributes[d].uri===b&&a.attributes[d].local===c)return a.attributes[d].value;return null}function A(a,b){var d=z(a,c.ns_ittp,"aspectRatio"),e=null;if(null!==d){var f=/(\d+) (\d+)/,g=f.exec(d);if(null!==g){var h=parseInt(g[1]),i=parseInt(g[2]);0!==h&&0!==i?e=h/i:K(b,"Illegal aspectRatio values (ignoring)")}else K(b,"Malformed aspectRatio attribute (ignoring)")}return e}function B(a,b){var d=z(a,c.ns_ttp,"cellResolution"),e=15,f=32;if(null!==d){var g=/(\d+) (\d+)/,h=g.exec(d);null!==h?(f=parseInt(h[1]),e=parseInt(h[2])):J(b,"Malformed cellResolution value (using initial value instead)")}return{w:f,h:e}}function C(a,b){var d,e=z(a,c.ns_ttp,"frameRate"),f=30;if(null!==e){d=/(\d+)/.exec(e),null!==d?f=parseInt(d[1]):J(b,"Malformed frame rate attribute (using initial value instead)")}var g=z(a,c.ns_ttp,"frameRateMultiplier"),h=1;if(null!==g){d=/(\d+) (\d+)/.exec(g),null!==d?h=parseInt(d[1])/parseInt(d[2]):J(b,"Malformed frame rate multiplier attribute (using initial value instead)")}var i=h*f,j=1,k=z(a,c.ns_ttp,"tickRate");if(null===k)null!==e&&(j=i);else{d=/(\d+)/.exec(k),null!==d?j=parseInt(d[1]):J(b,"Malformed tick rate attribute (using initial value instead)")}return{effectiveFrameRate:i,tickRate:j}}function D(a,b){var d=z(a,c.ns_tts,"extent");if(null===d)return null;var f=d.split(" ");if(2!==f.length)return J(b,"Malformed extent (ignoring)"),null;var g=e.parseLength(f[0]),h=e.parseLength(f[1]);return h&&g?{h:h,w:g}:(J(b,"Malformed extent values (ignoring)"),null)}function E(a,b,c){var d,e=/^(\d{2,}):(\d\d):(\d\d(?:\.\d+)?)$/,f=/^(\d{2,}):(\d\d):(\d\d)\:(\d{2,})$/,g=/^(\d+(?:\.\d+)?)f$/,h=/^(\d+(?:\.\d+)?)t$/,i=/^(\d+(?:\.\d+)?)ms$/,j=/^(\d+(?:\.\d+)?)s$/,k=/^(\d+(?:\.\d+)?)h$/,l=/^(\d+(?:\.\d+)?)m$/,m=null;return null!==(d=g.exec(c))?null!==b&&(m=parseFloat(d[1])/b):null!==(d=h.exec(c))?null!==a&&(m=parseFloat(d[1])/a):null!==(d=i.exec(c))?m=parseFloat(d[1])/1e3:null!==(d=j.exec(c))?m=parseFloat(d[1]):null!==(d=k.exec(c))?m=3600*parseFloat(d[1]):null!==(d=l.exec(c))?m=60*parseFloat(d[1]):null!==(d=e.exec(c))?m=3600*parseInt(d[1])+60*parseInt(d[2])+parseFloat(d[3]):null!==(d=f.exec(c))&&null!==b&&(m=3600*parseInt(d[1])+60*parseInt(d[2])+parseInt(d[3])+(null===d[4]?0:parseInt(d[4])/b)),m}function F(a,b,c,d){var e=b&&"seq"===b.timeContainer,f=0;c&&"begin"in c.attributes&&null===(f=E(a.tickRate,a.effectiveFrameRate,c.attributes.begin.value))&&(J(d,"Malformed begin value "+c.attributes.begin.value+" (using 0)"),f=0);var g=e?0:null;c&&"dur"in c.attributes&&null===(g=E(a.tickRate,a.effectiveFrameRate,c.attributes.dur.value))&&J(d,"Malformed dur value "+c.attributes.dur.value+" (ignoring)");var h=null;c&&"end"in c.attributes&&null===(h=E(a.tickRate,a.effectiveFrameRate,c.attributes.end.value))&&J(d,"Malformed end value (ignoring)");var i=0;if(b&&(i=e&&"contents"in b&&b.contents.length>0?b.contents[b.contents.length-1].end:b.begin||0),f+=i,null!==g)h=f+g;else{var j=b&&"end"in b?b.end:Number.POSITIVE_INFINITY;h=null!==h?h+i:j}return{begin:f,end:h}}function G(a,b,c){for(;b.styleRefs.length>0;){var d=b.styleRefs.pop();d in a.styles?(G(a,a.styles[d],c),I(a.styles[d].styleAttrs,b.styleAttrs)):K(c,"Non-existant style id referenced")}}function H(a,b,c,d){for(var e=b.length-1;e>=0;e--){var f=b[e];f in a.styles?I(a.styles[f].styleAttrs,c):K(d,"Non-existant style id referenced")}}function I(a,b){for(var c in a)c in b||(b[c]=a[c])}function J(a,b){if(a&&a.warn&&a.warn(b))throw b}function K(a,b){if(a&&a.error&&a.error(b))throw b}function L(a,b){throw a&&a.fatal&&a.fatal(b),b}function M(a,b){for(var c,d=0,e=a.length-1;d<=e;){c=Math.floor((d+e)/2);var f=a[c];if(fb))return{found:!0,index:c};e=c-1}}return{found:!1,index:d}}a.fromXML=function(a,d,e){var l=b.parser(!0,{xmlns:!0}),u=[],v=[],w=[],x=0,y=null;l.onclosetag=function(a){if(u[0]instanceof i)for(var b in u[0].styles)G(u[0],u[0].styles[b],d);else if(u[0]instanceof o||u[0]instanceof p){if(u[0].contents.length>1){var g,h=[u[0].contents[0]];for(g=1;g0&&e&&"onCloseTag"in e&&e.onCloseTag());w.shift(),v.shift(),u.shift()},l.ontext=function(a){if(void 0===u[0]);else if(u[0]instanceof p||u[0]instanceof o){var b=new q;b.initFromText(y,u[0],a,w[0],d),u[0].contents.push(b)}else u[0]instanceof f&&x>0&&e&&"onText"in e&&e.onText(a)},l.onopentag=function(a){var b=a.attributes["xml:space"];b?w.unshift(b.value):0===w.length?w.unshift("default"):w.unshift(w[0]);var l=a.attributes["xml:lang"];if(l?v.unshift(l.value):0===v.length?v.unshift(""):v.unshift(v[0]),a.uri===c.ns_tt)if("tt"===a.local)null!==y&&L("Two elements at ("+this.line+","+this.column+")"),y=new g,y.initFromNode(a,d),u.unshift(y);else if("head"===a.local)u[0]instanceof g||L("Parent of element is not at ("+this.line+","+this.column+")"),null!==y.head&&L("Second element at ("+this.line+","+this.column+")"),y.head=new h,u.unshift(y.head);else if("styling"===a.local)u[0]instanceof h||L("Parent of element is not at ("+this.line+","+this.column+")"),null!==y.head.styling&&L("Second element at ("+this.line+","+this.column+")"),y.head.styling=new i,u.unshift(y.head.styling);else if("style"===a.local){var q;u[0]instanceof i?(q=new j,q.initFromNode(a,d),q.id?y.head.styling.styles[q.id]=q:K("