From 8ec992a8a31742a82de38a0aa5eeb509362da9b4 Mon Sep 17 00:00:00 2001 From: matthewmcgarvey Date: Wed, 9 Feb 2022 00:50:32 -0600 Subject: [PATCH 01/21] Add custom migration implementation --- src/invidious.cr | 3 ++ src/invidious/migration.cr | 38 +++++++++++++++ .../migrations/0000_create_channels_table.cr | 30 ++++++++++++ .../migrations/0001_create_videos_table.cr | 28 +++++++++++ .../0002_create_channel_videos_table.cr | 35 ++++++++++++++ .../migrations/0003_create_users_table.cr | 34 ++++++++++++++ .../0004_create_session_ids_table.cr | 28 +++++++++++ .../migrations/0005_create_nonces_table.cr | 27 +++++++++++ .../0006_create_annotations_table.cr | 20 ++++++++ .../migrations/0007_create_playlists_table.cr | 47 +++++++++++++++++++ .../0008_create_playlist_videos_table.cr | 27 +++++++++++ src/invidious/migrator.cr | 41 ++++++++++++++++ 12 files changed, 358 insertions(+) create mode 100644 src/invidious/migration.cr create mode 100644 src/invidious/migrations/0000_create_channels_table.cr create mode 100644 src/invidious/migrations/0001_create_videos_table.cr create mode 100644 src/invidious/migrations/0002_create_channel_videos_table.cr create mode 100644 src/invidious/migrations/0003_create_users_table.cr create mode 100644 src/invidious/migrations/0004_create_session_ids_table.cr create mode 100644 src/invidious/migrations/0005_create_nonces_table.cr create mode 100644 src/invidious/migrations/0006_create_annotations_table.cr create mode 100644 src/invidious/migrations/0007_create_playlists_table.cr create mode 100644 src/invidious/migrations/0008_create_playlist_videos_table.cr create mode 100644 src/invidious/migrator.cr diff --git a/src/invidious.cr b/src/invidious.cr index 1ff70905..6ec5f3a5 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -34,6 +34,7 @@ require "./invidious/channels/*" require "./invidious/user/*" require "./invidious/routes/**" require "./invidious/jobs/**" +require "./invidious/migrations/*" CONFIG = Config.load HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32) @@ -111,6 +112,8 @@ end OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mode: "a") LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level) +# Run migrations +Invidious::Migrator.new(PG_DB).migrate # Check table integrity Invidious::Database.check_integrity(CONFIG) diff --git a/src/invidious/migration.cr b/src/invidious/migration.cr new file mode 100644 index 00000000..a4eec1c5 --- /dev/null +++ b/src/invidious/migration.cr @@ -0,0 +1,38 @@ +abstract class Invidious::Migration + macro inherited + Invidious::Migrator.migrations << self + end + + @@version : Int64? + + def self.version(version : Int32 | Int64) + @@version = version.to_i64 + end + + getter? completed = false + + def initialize(@db : DB::Database) + end + + abstract def up(conn : DB::Connection) + + def migrate + # migrator already ignores completed migrations + # but this is an extra check to make sure a migration doesn't run twice + return if completed? + + @db.transaction do |txn| + up(txn.connection) + track(txn.connection) + @completed = true + end + end + + def version : Int64 + @@version.not_nil! + end + + private def track(conn : DB::Connection) + conn.exec("INSERT INTO #{Invidious::Migrator::MIGRATIONS_TABLE}(version) VALUES ($1)", version) + end +end diff --git a/src/invidious/migrations/0000_create_channels_table.cr b/src/invidious/migrations/0000_create_channels_table.cr new file mode 100644 index 00000000..1f8f18e2 --- /dev/null +++ b/src/invidious/migrations/0000_create_channels_table.cr @@ -0,0 +1,30 @@ +module Invidious::Migrations + class CreateChannelsTable < Migration + version 0 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.channels + ( + id text NOT NULL, + author text, + updated timestamp with time zone, + deleted boolean, + subscribed timestamp with time zone, + CONSTRAINT channels_id_key UNIQUE (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.channels TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS channels_id_idx + ON public.channels + USING btree + (id COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/migrations/0001_create_videos_table.cr b/src/invidious/migrations/0001_create_videos_table.cr new file mode 100644 index 00000000..cdc9993f --- /dev/null +++ b/src/invidious/migrations/0001_create_videos_table.cr @@ -0,0 +1,28 @@ +module Invidious::Migrations + class CreateVideosTable < Migration + version 1 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE UNLOGGED TABLE IF NOT EXISTS public.videos + ( + id text NOT NULL, + info text, + updated timestamp with time zone, + CONSTRAINT videos_pkey PRIMARY KEY (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.videos TO current_user; + SQL + + conn.exec <<-SQL + CREATE UNIQUE INDEX IF NOT EXISTS id_idx + ON public.videos + USING btree + (id COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/migrations/0002_create_channel_videos_table.cr b/src/invidious/migrations/0002_create_channel_videos_table.cr new file mode 100644 index 00000000..737abad4 --- /dev/null +++ b/src/invidious/migrations/0002_create_channel_videos_table.cr @@ -0,0 +1,35 @@ +module Invidious::Migrations + class CreateChannelVideosTable < Migration + version 2 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.channel_videos + ( + id text NOT NULL, + title text, + published timestamp with time zone, + updated timestamp with time zone, + ucid text, + author text, + length_seconds integer, + live_now boolean, + premiere_timestamp timestamp with time zone, + views bigint, + CONSTRAINT channel_videos_id_key UNIQUE (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.channel_videos TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS channel_videos_ucid_idx + ON public.channel_videos + USING btree + (ucid COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/migrations/0003_create_users_table.cr b/src/invidious/migrations/0003_create_users_table.cr new file mode 100644 index 00000000..d91cca8d --- /dev/null +++ b/src/invidious/migrations/0003_create_users_table.cr @@ -0,0 +1,34 @@ +module Invidious::Migrations + class CreateUsersTable < Migration + version 3 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.users + ( + updated timestamp with time zone, + notifications text[], + subscriptions text[], + email text NOT NULL, + preferences text, + password text, + token text, + watched text[], + feed_needs_update boolean, + CONSTRAINT users_email_key UNIQUE (email) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.users TO current_user; + SQL + + conn.exec <<-SQL + CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx + ON public.users + USING btree + (lower(email) COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/migrations/0004_create_session_ids_table.cr b/src/invidious/migrations/0004_create_session_ids_table.cr new file mode 100644 index 00000000..9ef00f78 --- /dev/null +++ b/src/invidious/migrations/0004_create_session_ids_table.cr @@ -0,0 +1,28 @@ +module Invidious::Migrations + class CreateSessionIdsTable < Migration + version 4 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.session_ids + ( + id text NOT NULL, + email text, + issued timestamp with time zone, + CONSTRAINT session_ids_pkey PRIMARY KEY (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.session_ids TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS session_ids_id_idx + ON public.session_ids + USING btree + (id COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/migrations/0005_create_nonces_table.cr b/src/invidious/migrations/0005_create_nonces_table.cr new file mode 100644 index 00000000..4b1220e6 --- /dev/null +++ b/src/invidious/migrations/0005_create_nonces_table.cr @@ -0,0 +1,27 @@ +module Invidious::Migrations + class CreateNoncesTable < Migration + version 5 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.nonces + ( + nonce text, + expire timestamp with time zone, + CONSTRAINT nonces_id_key UNIQUE (nonce) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.nonces TO current_user; + SQL + + conn.exec <<-SQL + CREATE INDEX IF NOT EXISTS nonces_nonce_idx + ON public.nonces + USING btree + (nonce COLLATE pg_catalog."default"); + SQL + end + end +end diff --git a/src/invidious/migrations/0006_create_annotations_table.cr b/src/invidious/migrations/0006_create_annotations_table.cr new file mode 100644 index 00000000..86f21dd9 --- /dev/null +++ b/src/invidious/migrations/0006_create_annotations_table.cr @@ -0,0 +1,20 @@ +module Invidious::Migrations + class CreateAnnotationsTable < Migration + version 6 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.annotations + ( + id text NOT NULL, + annotations xml, + CONSTRAINT annotations_id_key UNIQUE (id) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.annotations TO current_user; + SQL + end + end +end diff --git a/src/invidious/migrations/0007_create_playlists_table.cr b/src/invidious/migrations/0007_create_playlists_table.cr new file mode 100644 index 00000000..81217365 --- /dev/null +++ b/src/invidious/migrations/0007_create_playlists_table.cr @@ -0,0 +1,47 @@ +module Invidious::Migrations + class CreatePlaylistsTable < Migration + version 7 + + def up(conn : DB::Connection) + conn.exec <<-SQL + DO + $$ + BEGIN + IF NOT EXISTS (SELECT * + FROM pg_type typ + INNER JOIN pg_namespace nsp ON nsp.oid = typ.typnamespace + WHERE nsp.nspname = 'public' + AND typ.typname = 'privacy') THEN + CREATE TYPE public.privacy AS ENUM + ( + 'Public', + 'Unlisted', + 'Private' + ); + END IF; + END; + $$ + LANGUAGE plpgsql; + SQL + + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.playlists + ( + title text, + id text primary key, + author text, + description text, + video_count integer, + created timestamptz, + updated timestamptz, + privacy privacy, + index int8[] + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON public.playlists TO current_user; + SQL + end + end +end diff --git a/src/invidious/migrations/0008_create_playlist_videos_table.cr b/src/invidious/migrations/0008_create_playlist_videos_table.cr new file mode 100644 index 00000000..80fa6b5f --- /dev/null +++ b/src/invidious/migrations/0008_create_playlist_videos_table.cr @@ -0,0 +1,27 @@ +module Invidious::Migrations + class CreatePlaylistVideosTable < Migration + version 8 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.playlist_videos + ( + title text, + id text, + author text, + ucid text, + length_seconds integer, + published timestamptz, + plid text references playlists(id), + index int8, + live_now boolean, + PRIMARY KEY (index,plid) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.playlist_videos TO current_user; + SQL + end + end +end diff --git a/src/invidious/migrator.cr b/src/invidious/migrator.cr new file mode 100644 index 00000000..dc6880b9 --- /dev/null +++ b/src/invidious/migrator.cr @@ -0,0 +1,41 @@ +class Invidious::Migrator + MIGRATIONS_TABLE = "invidious_migrations" + + class_getter migrations = [] of Invidious::Migration.class + + def initialize(@db : DB::Database) + end + + def migrate + run_migrations = load_run_migrations + migrations = load_migrations.sort_by(&.version) + migrations_to_run = migrations.reject { |migration| run_migrations.includes?(migration.version) } + if migrations.empty? + puts "No migrations to run." + return + end + + migrations_to_run.each do |migration| + puts "Running migration: #{migration.class.name}" + migration.migrate + end + end + + private def load_migrations : Array(Invidious::Migration) + self.class.migrations.map(&.new(@db)) + end + + private def load_run_migrations : Array(Int64) + create_migrations_table + @db.query_all("SELECT version FROM #{MIGRATIONS_TABLE}", as: Int64) + end + + private def create_migrations_table + @db.exec <<-SQL + CREATE TABLE IF NOT EXISTS #{MIGRATIONS_TABLE} ( + id bigserial PRIMARY KEY, + version bigint NOT NULL + ) + SQL + end +end From cf13c11236de6c4e373b110730530c8e1f305900 Mon Sep 17 00:00:00 2001 From: matthewmcgarvey Date: Thu, 10 Feb 2022 22:16:40 -0600 Subject: [PATCH 02/21] Migrations tweaks --- src/invidious.cr | 4 +- src/invidious/{ => database}/migration.cr | 6 +-- .../migrations/0001_create_channels_table.cr} | 4 +- .../migrations/0002_create_videos_table.cr} | 4 +- .../0003_create_channel_videos_table.cr} | 4 +- .../migrations/0004_create_users_table.cr} | 4 +- .../0005_create_session_ids_table.cr} | 4 +- .../migrations/0006_create_nonces_table.cr} | 4 +- .../0007_create_annotations_table.cr} | 4 +- .../migrations/0008_create_playlists_table.cr | 50 +++++++++++++++++++ .../0009_create_playlist_videos_table.cr | 27 ++++++++++ .../migrations/0010_make_videos_unlogged.cr | 11 ++++ src/invidious/database/migrator.cr | 42 ++++++++++++++++ .../migrations/0007_create_playlists_table.cr | 47 ----------------- .../0008_create_playlist_videos_table.cr | 27 ---------- src/invidious/migrator.cr | 41 --------------- 16 files changed, 149 insertions(+), 134 deletions(-) rename src/invidious/{ => database}/migration.cr (78%) rename src/invidious/{migrations/0000_create_channels_table.cr => database/migrations/0001_create_channels_table.cr} (92%) rename src/invidious/{migrations/0001_create_videos_table.cr => database/migrations/0002_create_videos_table.cr} (91%) rename src/invidious/{migrations/0002_create_channel_videos_table.cr => database/migrations/0003_create_channel_videos_table.cr} (94%) rename src/invidious/{migrations/0003_create_users_table.cr => database/migrations/0004_create_users_table.cr} (93%) rename src/invidious/{migrations/0004_create_session_ids_table.cr => database/migrations/0005_create_session_ids_table.cr} (92%) rename src/invidious/{migrations/0005_create_nonces_table.cr => database/migrations/0006_create_nonces_table.cr} (91%) rename src/invidious/{migrations/0006_create_annotations_table.cr => database/migrations/0007_create_annotations_table.cr} (88%) create mode 100644 src/invidious/database/migrations/0008_create_playlists_table.cr create mode 100644 src/invidious/database/migrations/0009_create_playlist_videos_table.cr create mode 100644 src/invidious/database/migrations/0010_make_videos_unlogged.cr create mode 100644 src/invidious/database/migrator.cr delete mode 100644 src/invidious/migrations/0007_create_playlists_table.cr delete mode 100644 src/invidious/migrations/0008_create_playlist_videos_table.cr delete mode 100644 src/invidious/migrator.cr diff --git a/src/invidious.cr b/src/invidious.cr index 6ec5f3a5..e8ad03ef 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -27,6 +27,7 @@ require "compress/zip" require "protodec/utils" require "./invidious/database/*" +require "./invidious/database/migrations/*" require "./invidious/helpers/*" require "./invidious/yt_backend/*" require "./invidious/*" @@ -34,7 +35,6 @@ require "./invidious/channels/*" require "./invidious/user/*" require "./invidious/routes/**" require "./invidious/jobs/**" -require "./invidious/migrations/*" CONFIG = Config.load HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32) @@ -113,7 +113,7 @@ OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mo LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level) # Run migrations -Invidious::Migrator.new(PG_DB).migrate +Invidious::Database::Migrator.new(PG_DB).migrate # Check table integrity Invidious::Database.check_integrity(CONFIG) diff --git a/src/invidious/migration.cr b/src/invidious/database/migration.cr similarity index 78% rename from src/invidious/migration.cr rename to src/invidious/database/migration.cr index a4eec1c5..921d8f38 100644 --- a/src/invidious/migration.cr +++ b/src/invidious/database/migration.cr @@ -1,6 +1,6 @@ -abstract class Invidious::Migration +abstract class Invidious::Database::Migration macro inherited - Invidious::Migrator.migrations << self + Migrator.migrations << self end @@version : Int64? @@ -33,6 +33,6 @@ abstract class Invidious::Migration end private def track(conn : DB::Connection) - conn.exec("INSERT INTO #{Invidious::Migrator::MIGRATIONS_TABLE}(version) VALUES ($1)", version) + conn.exec("INSERT INTO #{Migrator::MIGRATIONS_TABLE} (version) VALUES ($1)", version) end end diff --git a/src/invidious/migrations/0000_create_channels_table.cr b/src/invidious/database/migrations/0001_create_channels_table.cr similarity index 92% rename from src/invidious/migrations/0000_create_channels_table.cr rename to src/invidious/database/migrations/0001_create_channels_table.cr index 1f8f18e2..a1362bcf 100644 --- a/src/invidious/migrations/0000_create_channels_table.cr +++ b/src/invidious/database/migrations/0001_create_channels_table.cr @@ -1,6 +1,6 @@ -module Invidious::Migrations +module Invidious::Database::Migrations class CreateChannelsTable < Migration - version 0 + version 1 def up(conn : DB::Connection) conn.exec <<-SQL diff --git a/src/invidious/migrations/0001_create_videos_table.cr b/src/invidious/database/migrations/0002_create_videos_table.cr similarity index 91% rename from src/invidious/migrations/0001_create_videos_table.cr rename to src/invidious/database/migrations/0002_create_videos_table.cr index cdc9993f..c2ac84f8 100644 --- a/src/invidious/migrations/0001_create_videos_table.cr +++ b/src/invidious/database/migrations/0002_create_videos_table.cr @@ -1,6 +1,6 @@ -module Invidious::Migrations +module Invidious::Database::Migrations class CreateVideosTable < Migration - version 1 + version 2 def up(conn : DB::Connection) conn.exec <<-SQL diff --git a/src/invidious/migrations/0002_create_channel_videos_table.cr b/src/invidious/database/migrations/0003_create_channel_videos_table.cr similarity index 94% rename from src/invidious/migrations/0002_create_channel_videos_table.cr rename to src/invidious/database/migrations/0003_create_channel_videos_table.cr index 737abad4..c9b62e4c 100644 --- a/src/invidious/migrations/0002_create_channel_videos_table.cr +++ b/src/invidious/database/migrations/0003_create_channel_videos_table.cr @@ -1,6 +1,6 @@ -module Invidious::Migrations +module Invidious::Database::Migrations class CreateChannelVideosTable < Migration - version 2 + version 3 def up(conn : DB::Connection) conn.exec <<-SQL diff --git a/src/invidious/migrations/0003_create_users_table.cr b/src/invidious/database/migrations/0004_create_users_table.cr similarity index 93% rename from src/invidious/migrations/0003_create_users_table.cr rename to src/invidious/database/migrations/0004_create_users_table.cr index d91cca8d..a13ba15f 100644 --- a/src/invidious/migrations/0003_create_users_table.cr +++ b/src/invidious/database/migrations/0004_create_users_table.cr @@ -1,6 +1,6 @@ -module Invidious::Migrations +module Invidious::Database::Migrations class CreateUsersTable < Migration - version 3 + version 4 def up(conn : DB::Connection) conn.exec <<-SQL diff --git a/src/invidious/migrations/0004_create_session_ids_table.cr b/src/invidious/database/migrations/0005_create_session_ids_table.cr similarity index 92% rename from src/invidious/migrations/0004_create_session_ids_table.cr rename to src/invidious/database/migrations/0005_create_session_ids_table.cr index 9ef00f78..13c2228d 100644 --- a/src/invidious/migrations/0004_create_session_ids_table.cr +++ b/src/invidious/database/migrations/0005_create_session_ids_table.cr @@ -1,6 +1,6 @@ -module Invidious::Migrations +module Invidious::Database::Migrations class CreateSessionIdsTable < Migration - version 4 + version 5 def up(conn : DB::Connection) conn.exec <<-SQL diff --git a/src/invidious/migrations/0005_create_nonces_table.cr b/src/invidious/database/migrations/0006_create_nonces_table.cr similarity index 91% rename from src/invidious/migrations/0005_create_nonces_table.cr rename to src/invidious/database/migrations/0006_create_nonces_table.cr index 4b1220e6..cf1229e1 100644 --- a/src/invidious/migrations/0005_create_nonces_table.cr +++ b/src/invidious/database/migrations/0006_create_nonces_table.cr @@ -1,6 +1,6 @@ -module Invidious::Migrations +module Invidious::Database::Migrations class CreateNoncesTable < Migration - version 5 + version 6 def up(conn : DB::Connection) conn.exec <<-SQL diff --git a/src/invidious/migrations/0006_create_annotations_table.cr b/src/invidious/database/migrations/0007_create_annotations_table.cr similarity index 88% rename from src/invidious/migrations/0006_create_annotations_table.cr rename to src/invidious/database/migrations/0007_create_annotations_table.cr index 86f21dd9..dcecbc3b 100644 --- a/src/invidious/migrations/0006_create_annotations_table.cr +++ b/src/invidious/database/migrations/0007_create_annotations_table.cr @@ -1,6 +1,6 @@ -module Invidious::Migrations +module Invidious::Database::Migrations class CreateAnnotationsTable < Migration - version 6 + version 7 def up(conn : DB::Connection) conn.exec <<-SQL diff --git a/src/invidious/database/migrations/0008_create_playlists_table.cr b/src/invidious/database/migrations/0008_create_playlists_table.cr new file mode 100644 index 00000000..6aa16e1a --- /dev/null +++ b/src/invidious/database/migrations/0008_create_playlists_table.cr @@ -0,0 +1,50 @@ +module Invidious::Database::Migrations + class CreatePlaylistsTable < Migration + version 8 + + def up(conn : DB::Connection) + if !privacy_type_exists?(conn) + conn.exec <<-SQL + CREATE TYPE public.privacy AS ENUM + ( + 'Public', + 'Unlisted', + 'Private' + ); + SQL + end + + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.playlists + ( + title text, + id text primary key, + author text, + description text, + video_count integer, + created timestamptz, + updated timestamptz, + privacy privacy, + index int8[] + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON public.playlists TO current_user; + SQL + end + + private def privacy_type_exists?(conn : DB::Connection) : Bool + request = <<-SQL + SELECT 1 AS one + FROM pg_type + INNER JOIN pg_namespace ON pg_namespace.oid = pg_type.typnamespace + WHERE pg_namespace.nspname = 'public' + AND pg_type.typname = 'privacy' + LIMIT 1; + SQL + + !conn.query_one?(request, as: Int32).nil? + end + end +end diff --git a/src/invidious/database/migrations/0009_create_playlist_videos_table.cr b/src/invidious/database/migrations/0009_create_playlist_videos_table.cr new file mode 100644 index 00000000..84938b9b --- /dev/null +++ b/src/invidious/database/migrations/0009_create_playlist_videos_table.cr @@ -0,0 +1,27 @@ +module Invidious::Database::Migrations + class CreatePlaylistVideosTable < Migration + version 9 + + def up(conn : DB::Connection) + conn.exec <<-SQL + CREATE TABLE IF NOT EXISTS public.playlist_videos + ( + title text, + id text, + author text, + ucid text, + length_seconds integer, + published timestamptz, + plid text references playlists(id), + index int8, + live_now boolean, + PRIMARY KEY (index,plid) + ); + SQL + + conn.exec <<-SQL + GRANT ALL ON TABLE public.playlist_videos TO current_user; + SQL + end + end +end diff --git a/src/invidious/database/migrations/0010_make_videos_unlogged.cr b/src/invidious/database/migrations/0010_make_videos_unlogged.cr new file mode 100644 index 00000000..f5d19683 --- /dev/null +++ b/src/invidious/database/migrations/0010_make_videos_unlogged.cr @@ -0,0 +1,11 @@ +module Invidious::Database::Migrations + class MakeVideosUnlogged < Migration + version 10 + + def up(conn : DB::Connection) + conn.exec <<-SQL + ALTER TABLE public.videos SET UNLOGGED; + SQL + end + end +end diff --git a/src/invidious/database/migrator.cr b/src/invidious/database/migrator.cr new file mode 100644 index 00000000..2cd869c9 --- /dev/null +++ b/src/invidious/database/migrator.cr @@ -0,0 +1,42 @@ +class Invidious::Database::Migrator + MIGRATIONS_TABLE = "public.invidious_migrations" + + class_getter migrations = [] of Invidious::Database::Migration.class + + def initialize(@db : DB::Database) + end + + def migrate + versions = load_versions + + ran_migration = false + load_migrations.sort_by(&.version) + .each do |migration| + next if versions.includes?(migration.version) + + puts "Running migration: #{migration.class.name}" + migration.migrate + ran_migration = true + end + + puts "No migrations to run." unless ran_migration + end + + private def load_migrations : Array(Invidious::Database::Migration) + self.class.migrations.map(&.new(@db)) + end + + private def load_versions : Array(Int64) + create_migrations_table + @db.query_all("SELECT version FROM #{MIGRATIONS_TABLE}", as: Int64) + end + + private def create_migrations_table + @db.exec <<-SQL + CREATE TABLE IF NOT EXISTS #{MIGRATIONS_TABLE} ( + id bigserial PRIMARY KEY, + version bigint NOT NULL + ) + SQL + end +end diff --git a/src/invidious/migrations/0007_create_playlists_table.cr b/src/invidious/migrations/0007_create_playlists_table.cr deleted file mode 100644 index 81217365..00000000 --- a/src/invidious/migrations/0007_create_playlists_table.cr +++ /dev/null @@ -1,47 +0,0 @@ -module Invidious::Migrations - class CreatePlaylistsTable < Migration - version 7 - - def up(conn : DB::Connection) - conn.exec <<-SQL - DO - $$ - BEGIN - IF NOT EXISTS (SELECT * - FROM pg_type typ - INNER JOIN pg_namespace nsp ON nsp.oid = typ.typnamespace - WHERE nsp.nspname = 'public' - AND typ.typname = 'privacy') THEN - CREATE TYPE public.privacy AS ENUM - ( - 'Public', - 'Unlisted', - 'Private' - ); - END IF; - END; - $$ - LANGUAGE plpgsql; - SQL - - conn.exec <<-SQL - CREATE TABLE IF NOT EXISTS public.playlists - ( - title text, - id text primary key, - author text, - description text, - video_count integer, - created timestamptz, - updated timestamptz, - privacy privacy, - index int8[] - ); - SQL - - conn.exec <<-SQL - GRANT ALL ON public.playlists TO current_user; - SQL - end - end -end diff --git a/src/invidious/migrations/0008_create_playlist_videos_table.cr b/src/invidious/migrations/0008_create_playlist_videos_table.cr deleted file mode 100644 index 80fa6b5f..00000000 --- a/src/invidious/migrations/0008_create_playlist_videos_table.cr +++ /dev/null @@ -1,27 +0,0 @@ -module Invidious::Migrations - class CreatePlaylistVideosTable < Migration - version 8 - - def up(conn : DB::Connection) - conn.exec <<-SQL - CREATE TABLE IF NOT EXISTS public.playlist_videos - ( - title text, - id text, - author text, - ucid text, - length_seconds integer, - published timestamptz, - plid text references playlists(id), - index int8, - live_now boolean, - PRIMARY KEY (index,plid) - ); - SQL - - conn.exec <<-SQL - GRANT ALL ON TABLE public.playlist_videos TO current_user; - SQL - end - end -end diff --git a/src/invidious/migrator.cr b/src/invidious/migrator.cr deleted file mode 100644 index dc6880b9..00000000 --- a/src/invidious/migrator.cr +++ /dev/null @@ -1,41 +0,0 @@ -class Invidious::Migrator - MIGRATIONS_TABLE = "invidious_migrations" - - class_getter migrations = [] of Invidious::Migration.class - - def initialize(@db : DB::Database) - end - - def migrate - run_migrations = load_run_migrations - migrations = load_migrations.sort_by(&.version) - migrations_to_run = migrations.reject { |migration| run_migrations.includes?(migration.version) } - if migrations.empty? - puts "No migrations to run." - return - end - - migrations_to_run.each do |migration| - puts "Running migration: #{migration.class.name}" - migration.migrate - end - end - - private def load_migrations : Array(Invidious::Migration) - self.class.migrations.map(&.new(@db)) - end - - private def load_run_migrations : Array(Int64) - create_migrations_table - @db.query_all("SELECT version FROM #{MIGRATIONS_TABLE}", as: Int64) - end - - private def create_migrations_table - @db.exec <<-SQL - CREATE TABLE IF NOT EXISTS #{MIGRATIONS_TABLE} ( - id bigserial PRIMARY KEY, - version bigint NOT NULL - ) - SQL - end -end From 59654289cb7c024d3cf7c9d00d38cf4453bc48df Mon Sep 17 00:00:00 2001 From: matthewmcgarvey Date: Fri, 11 Feb 2022 22:43:16 -0600 Subject: [PATCH 03/21] Run migrations through CLI instead of when app starts --- src/invidious.cr | 9 ++++++++- src/invidious/database/migrator.cr | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/invidious.cr b/src/invidious.cr index e8ad03ef..25ee7c78 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -102,6 +102,10 @@ Kemal.config.extra_options do |parser| puts SOFTWARE.to_pretty_json exit end + parser.on("--migrate", "Run any migrations") do + Invidious::Database::Migrator.new(PG_DB).migrate + exit + end end Kemal::CLI.new ARGV @@ -113,7 +117,10 @@ OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mo LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level) # Run migrations -Invidious::Database::Migrator.new(PG_DB).migrate +if Invidious::Database::Migrator.new(PG_DB).pending_migrations? + puts "There are pending migrations. Run `invidious --migrate` to apply the migrations." + exit 46 +end # Check table integrity Invidious::Database.check_integrity(CONFIG) diff --git a/src/invidious/database/migrator.cr b/src/invidious/database/migrator.cr index 2cd869c9..660c3203 100644 --- a/src/invidious/database/migrator.cr +++ b/src/invidious/database/migrator.cr @@ -22,6 +22,13 @@ class Invidious::Database::Migrator puts "No migrations to run." unless ran_migration end + def pending_migrations? : Bool + versions = load_versions + + load_migrations.sort_by(&.version) + .any? { |migration| !versions.includes?(migration.version) } + end + private def load_migrations : Array(Invidious::Database::Migration) self.class.migrations.map(&.new(@db)) end From bf054dfda5ac6d1d3c4ab40b44a3bbb45ca132a3 Mon Sep 17 00:00:00 2001 From: matthewmcgarvey Date: Sat, 12 Feb 2022 09:20:43 -0600 Subject: [PATCH 04/21] Do not check for pending migrations on app start This is so that we don't break deploys with this PR. Instead we only ship the 'invidious --migrate' cli command and let people test that. Maybe even ship a new migration that wouldn't break apps that don't run the migrations. Then we roll out the functionality that requires migrations. --- src/invidious.cr | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index 25ee7c78..04b18a65 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -116,11 +116,6 @@ end OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mode: "a") LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level) -# Run migrations -if Invidious::Database::Migrator.new(PG_DB).pending_migrations? - puts "There are pending migrations. Run `invidious --migrate` to apply the migrations." - exit 46 -end # Check table integrity Invidious::Database.check_integrity(CONFIG) From bdfe317e20c9cc5d9e972e51d995faf59b86197d Mon Sep 17 00:00:00 2001 From: James Blair Date: Fri, 4 Mar 2022 04:09:13 +1300 Subject: [PATCH 05/21] Fix deprecated helm chart dependency (#2944) --- kubernetes/Chart.lock | 8 ++++---- kubernetes/Chart.yaml | 6 +++--- kubernetes/values.yaml | 21 +++++++++++++-------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/kubernetes/Chart.lock b/kubernetes/Chart.lock index 1799798b..37fcdbbd 100644 --- a/kubernetes/Chart.lock +++ b/kubernetes/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql - repository: https://kubernetes-charts.storage.googleapis.com/ - version: 8.3.0 -digest: sha256:1feec3c396cbf27573dc201831ccd3376a4a6b58b2e7618ce30a89b8f5d707fd -generated: "2020-02-07T13:39:38.624846+01:00" + repository: https://charts.bitnami.com/bitnami/ + version: 11.1.3 +digest: sha256:79061645472b6fb342d45e8e5b3aacd018ef5067193e46a060bccdc99fe7f6e1 +generated: "2022-03-02T05:57:20.081432389+13:00" diff --git a/kubernetes/Chart.yaml b/kubernetes/Chart.yaml index 9e4b793e..ca44f4b7 100644 --- a/kubernetes/Chart.yaml +++ b/kubernetes/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: invidious description: Invidious is an alternative front-end to YouTube -version: 1.1.0 +version: 1.1.1 appVersion: 0.20.1 keywords: - youtube @@ -17,6 +17,6 @@ maintainers: email: mail@leonklingele.de dependencies: - name: postgresql - version: ~8.3.0 - repository: "https://kubernetes-charts.storage.googleapis.com/" + version: ~11.1.3 + repository: "https://charts.bitnami.com/bitnami/" engine: gotpl diff --git a/kubernetes/values.yaml b/kubernetes/values.yaml index f241970c..2dc4db2c 100644 --- a/kubernetes/values.yaml +++ b/kubernetes/values.yaml @@ -14,7 +14,7 @@ autoscaling: targetCPUUtilizationPercentage: 50 service: - type: clusterIP + type: ClusterIP port: 3000 #loadBalancerIP: @@ -32,14 +32,19 @@ securityContext: runAsGroup: 1000 fsGroup: 1000 -# See https://github.com/helm/charts/tree/master/stable/postgresql +# See https://github.com/bitnami/charts/tree/master/bitnami/postgresql postgresql: - postgresqlUsername: kemal - postgresqlPassword: kemal - postgresqlDatabase: invidious - initdbUsername: kemal - initdbPassword: kemal - initdbScriptsConfigMap: invidious-postgresql-init + image: + registry: quay.io + auth: + username: kemal + password: kemal + database: invidious + primary: + initdb: + username: kemal + password: kemal + scriptsConfigMap: invidious-postgresql-init # Adapted from ../config/config.yml config: From f7b557eed1c8e55002608ee9117cf9523b24bafd Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sun, 6 Mar 2022 01:12:57 +0100 Subject: [PATCH 06/21] API: fix suggestions not workin Closes #2914 Thanks to @TiA4f8R for the help --- src/invidious/routes/api/v1/search.cr | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr index 0b0853b1..5666460d 100644 --- a/src/invidious/routes/api/v1/search.cr +++ b/src/invidious/routes/api/v1/search.cr @@ -43,20 +43,20 @@ module Invidious::Routes::API::V1::Search end def self.search_suggestions(env) - locale = env.get("preferences").as(Preferences).locale - region = env.params.query["region"]? + preferences = env.get("preferences").as(Preferences) + region = env.params.query["region"]? || preferences.region env.response.content_type = "application/json" - query = env.params.query["q"]? - query ||= "" + query = env.params.query["q"]? || "" begin - headers = HTTP::Headers{":authority" => "suggestqueries.google.com"} - response = YT_POOL.client &.get("/complete/search?hl=en&gl=#{region}&client=youtube&ds=yt&q=#{URI.encode_www_form(query)}&callback=suggestCallback", headers).body + client = HTTP::Client.new("suggestqueries-clients6.youtube.com") + url = "/complete/search?client=youtube&hl=en&gl=#{region}&q=#{URI.encode_www_form(query)}&xssi=t&gs_ri=youtube&ds=yt" - body = response[35..-2] - body = JSON.parse(body).as_a + response = client.get(url).body + + body = JSON.parse(response[5..-1]).as_a suggestions = body[1].as_a[0..-2] JSON.build do |json| From 7e351b21bc64aed051056e09b8ee3b6c40254321 Mon Sep 17 00:00:00 2001 From: Mr Keuz Date: Wed, 9 Mar 2022 17:24:32 +0300 Subject: [PATCH 07/21] Fix broken links (#2958) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7fd27004..e79faa7e 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@  •  Instances list  •  - FAQ + FAQ  •  Documentation  •  @@ -88,7 +88,7 @@ **Technical features** - Embedded video support -- [Developer API](https://docs.invidious.io/API.md) +- [Developer API](https://docs.invidious.io/API/) - Does not use official YouTube APIs - No Contributor License Agreement (CLA) @@ -101,7 +101,7 @@ **Hosting invidious:** -- [Follow the installation instructions](https://docs.invidious.io/Installation.md) +- [Follow the installation instructions](https://docs.invidious.io/Installation/) ## Documentation @@ -119,7 +119,7 @@ embedded youtube videos on other websites with invidious. The documentation contains a list of browser extensions that we recommended to use along with Invidious. -You can read more here: https://docs.invidious.io/Extensions.md +You can read more here: https://docs.invidious.io/Extensions/ ## Contribute From 0585131f787e460b96a1d733b51fae9998a5c13c Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:03 +0100 Subject: [PATCH 08/21] Update Finnish translation Co-authored-by: Markus Mikkonen --- locales/fi.json | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/locales/fi.json b/locales/fi.json index 7f97e869..84090c24 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -21,15 +21,15 @@ "No": "Ei", "Import and Export Data": "Tuo ja vie tietoja", "Import": "Tuo", - "Import Invidious data": "Tuo Invidious-tietoja", - "Import YouTube subscriptions": "Tuo YouTube-tilaukset", + "Import Invidious data": "Tuo Invidiousin JSON-tietoja", + "Import YouTube subscriptions": "Tuo YouTube/OPML-tilaukset", "Import FreeTube subscriptions (.db)": "Tuo FreeTube-tilaukset (.db)", "Import NewPipe subscriptions (.json)": "Tuo NewPipe-tilaukset (.json)", "Import NewPipe data (.zip)": "Tuo NewPipe-tietoja (.zip)", "Export": "Vie", "Export subscriptions as OPML": "Vie tilaukset OPML-muodossa", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Vie tilaukset OPML-muodossa (NewPipe & FreeTube)", - "Export data as JSON": "Vie data JSON-muodossa", + "Export data as JSON": "Vie Invidious-data JSON-muodossa", "Delete account?": "Poista tili?", "History": "Historia", "An alternative front-end to YouTube": "Vaihtoehtoinen front-end YouTubelle", @@ -66,7 +66,7 @@ "preferences_related_videos_label": "Näytä aiheeseen liittyviä videoita: ", "preferences_annotations_label": "Näytä huomautukset oletuksena: ", "preferences_extend_desc_label": "Laajenna automaattisesti videon kuvausta: ", - "preferences_vr_mode_label": "Interaktiiviset 360-asteiset videot: ", + "preferences_vr_mode_label": "Interaktiiviset 360-asteiset videot (vaatii WebGL:n): ", "preferences_category_visual": "Visuaaliset asetukset", "preferences_player_style_label": "Soittimen tyyli: ", "Dark mode: ": "Tumma tila: ", @@ -437,5 +437,29 @@ "long": "Pitkä (> 20 minuuttia)", "footer_documentation": "Dokumentaatio", "footer_original_source_code": "Alkuperäinen lähdekoodi", - "footer_modfied_source_code": "Muokattu lähdekoodi" + "footer_modfied_source_code": "Muokattu lähdekoodi", + "Japanese (auto-generated)": "Japani (automaattisesti luotu)", + "German (auto-generated)": "Saksa (automaattisesti luotu)", + "Portuguese (auto-generated)": "Portugali (automaattisesti luotu)", + "Russian (auto-generated)": "Venäjä (automaattisesti luotu)", + "preferences_watch_history_label": "Ota katseluhistoria käyttöön: ", + "English (United Kingdom)": "Englanti (Iso-Britannia)", + "English (United States)": "Englanti (Yhdysvallat)", + "Cantonese (Hong Kong)": "Kantoninkiina (Hong Kong)", + "Chinese": "Kiina", + "Chinese (China)": "Kiina (Kiina)", + "Chinese (Hong Kong)": "Kiina (Hong Kong)", + "Chinese (Taiwan)": "Kiina (Taiwan)", + "Dutch (auto-generated)": "Hollanti (automaattisesti luotu)", + "French (auto-generated)": "Ranska (automaattisesti luotu)", + "Indonesian (auto-generated)": "Indonesia (automaattisesti luotu)", + "Interlingue": "Interlingue", + "Italian (auto-generated)": "Italia (automaattisesti luotu)", + "Korean (auto-generated)": "Korea (automaattisesti luotu)", + "Portuguese (Brazil)": "Portugali (Brasilia)", + "Spanish (auto-generated)": "Espanja (automaattisesti luotu)", + "Spanish (Mexico)": "Espanja (Meksiko)", + "Spanish (Spain)": "Espanja (Espanja)", + "Turkish (auto-generated)": "Turkki (automaattisesti luotu)", + "Vietnamese (auto-generated)": "Vietnam (automaattisesti luotu)" } From 37b3248202310a9f5b40f30a0e25ff7bd41f0056 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:03 +0100 Subject: [PATCH 09/21] Update English (United States) translation Co-authored-by: Samantaz Fox --- locales/en-US.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en-US.json b/locales/en-US.json index 1335d384..a78d8062 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -459,7 +459,7 @@ "crash_page_before_reporting": "Before reporting a bug, make sure that you have:", "crash_page_refresh": "tried to refresh the page", "crash_page_switch_instance": "tried to use another instance", - "crash_page_read_the_faq": "read the Frenquently Asked Questions (FAQ)", + "crash_page_read_the_faq": "read the Frequently Asked Questions (FAQ)", "crash_page_search_issue": "searched for existing issues on Github", "crash_page_report_issue": "If none of the above helped, please open a new issue on GitHub (preferably in English) and include the following text in your message (do NOT translate that text):" } From 9991c4507d4bce70fb2e9996de00a29133a61d03 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:03 +0100 Subject: [PATCH 10/21] Update Japanese translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: GnuPGを使うべきだ --- locales/ja.json | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/locales/ja.json b/locales/ja.json index e3014152..9708c0ea 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -26,15 +26,15 @@ "No": "いいえ", "Import and Export Data": "データのインポートとエクスポート", "Import": "インポート", - "Import Invidious data": "Invidious データをインポート", - "Import YouTube subscriptions": "YouTube 登録チャンネルをインポート", + "Import Invidious data": "Invidious JSONデータをインポート", + "Import YouTube subscriptions": "YouTube/OPML 登録チャンネルをインポート", "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 でエクスポート", + "Export data as JSON": "Invidious のデータを JSON でエクスポート", "Delete account?": "アカウントを削除しますか?", "History": "履歴", "An alternative front-end to YouTube": "YouTube 向けの代用フロントエンド", @@ -71,7 +71,7 @@ "preferences_related_videos_label": "関連動画を表示: ", "preferences_annotations_label": "デフォルトでアノテーションを表示: ", "preferences_extend_desc_label": "動画の説明文を自動的に拡張: ", - "preferences_vr_mode_label": "対話的な360°動画: ", + "preferences_vr_mode_label": "対話的な360°動画 (WebGL が必要): ", "preferences_category_visual": "外観設定", "preferences_player_style_label": "プレイヤースタイル: ", "Dark mode: ": "ダークモード: ", @@ -411,5 +411,28 @@ "videoinfo_started_streaming_x_ago": "`x`分前に配信を開始", "videoinfo_watch_on_youTube": "YouTube上で見る", "user_created_playlists": "`x`が作成したプレイリスト", - "Video unavailable": "ビデオは利用できません" + "Video unavailable": "ビデオは利用できません", + "Chinese": "中国語", + "Chinese (Taiwan)": "中国語 (台湾)", + "Korean (auto-generated)": "韓国語 (自動生成)", + "Portuguese (auto-generated)": "ポルトガル語 (自動生成)", + "Turkish (auto-generated)": "トルコ語 (自動生成)", + "English (United Kingdom)": "英語 (イギリス)", + "Cantonese (Hong Kong)": "広東語 (香港)", + "Chinese (China)": "中国語 (中国)", + "Chinese (Hong Kong)": "中国語 (香港)", + "Dutch (auto-generated)": "オランダ語 (自動生成)", + "French (auto-generated)": "フランス語 (自動生成)", + "German (auto-generated)": "ドイツ語 (自動生成)", + "Indonesian (auto-generated)": "インドネシア語 (自動生成)", + "Italian (auto-generated)": "イタリア語 (自動生成)", + "Japanese (auto-generated)": "日本語 (自動生成)", + "Interlingue": "インターリング", + "Portuguese (Brazil)": "ポルトガル語 (ブラジル)", + "Russian (auto-generated)": "ロシア語 (自動生成)", + "Spanish (auto-generated)": "スペイン語 (自動生成)", + "Spanish (Mexico)": "スペイン語 (メキシコ)", + "Spanish (Spain)": "スペイン語 (スペイン)", + "Vietnamese (auto-generated)": "ベトナム語 (自動生成)", + "360": "360°" } From e582d25654a06dc97b4a753901aad411905a34af Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:04 +0100 Subject: [PATCH 11/21] Update Arabic translation Co-authored-by: Rex_sa --- locales/ar.json | 56 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/locales/ar.json b/locales/ar.json index 306566d7..10ef200b 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -21,15 +21,15 @@ "No": "لا", "Import and Export Data": "اِستيراد البيانات وتصديرها", "Import": "استيراد", - "Import Invidious data": "استيراد بيانات انفيدياس", - "Import YouTube subscriptions": "استيراد اشتراكات يوتيوب", + "Import Invidious data": "استيراد بيانات JSON Invidious", + "Import YouTube subscriptions": "استيراد اشتراكات YouTube/OPML", "Import FreeTube subscriptions (.db)": "استيراد اشتراكات فريتيوب (.db)", "Import NewPipe subscriptions (.json)": "استيراد اشتراكات نيو بايب (.json)", "Import NewPipe data (.zip)": "استيراد بيانات نيو بايب (.zip)", "Export": "تصدير", "Export subscriptions as OPML": "تصدير الاشتراكات كـOPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "تصدير الاشتراكات كـOPML (لِنيو بايب و فريتيوب)", - "Export data as JSON": "تصدير البيانات بتنسيق JSON", + "Export data as JSON": "تصدير بيانات Invidious كـ JSON", "Delete account?": "حذف الحساب؟", "History": "السِّجل", "An alternative front-end to YouTube": "واجهة أمامية بديلة لموقع يوتيوب", @@ -66,7 +66,7 @@ "preferences_related_videos_label": "اعرض الفيديوهات ذات الصلة: ", "preferences_annotations_label": "اعرض الملاحظات في الفيديو تلقائيا: ", "preferences_extend_desc_label": "توسيع وصف الفيديو تلقائيا: ", - "preferences_vr_mode_label": "مقاطع فيديو تفاعلية ب درجة 360: ", + "preferences_vr_mode_label": "مقاطع فيديو تفاعلية بزاوية 360 درجة (تتطلب WebGL): ", "preferences_category_visual": "التفضيلات المرئية", "preferences_player_style_label": "شكل مشغل الفيديوهات: ", "Dark mode: ": "الوضع الليلي: ", @@ -108,9 +108,9 @@ "preferences_show_nick_label": "إظهار اللقب في الأعلى: ", "Top enabled: ": "تفعيل 'الأفضل' ؟ ", "CAPTCHA enabled: ": "تفعيل الكابتشا: ", - "Login enabled: ": "تفعيل الولوج: ", + "Login enabled: ": "تمكين تسجيل الدخول: ", "Registration enabled: ": "تفعيل التسجيل: ", - "Report statistics: ": "الإبلاغ عن الإحصائيات: ", + "Report statistics: ": "تقرير الإحصائيات: ", "Save preferences": "حفظ الإعدادات", "Subscription manager": "مدير الاشتراكات", "Token manager": "إداره الرمز", @@ -175,7 +175,7 @@ "User ID is a required field": "مكان اسم المستخدم مطلوب", "Password is a required field": "مكان كلمة السر مطلوب", "Wrong username or password": "اسم المستخدم او كلمة السر غير صحيح", - "Please sign in using 'Log in with Google'": "الرجاء تسجيل الدخول 'تسجيل الدخول بواسطة جوجل'", + "Please sign in using 'Log in with Google'": "الرجاء تسجيل الدخول باستخدام \"تسجيل الدخول باستخدام Google\"", "Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة", "Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا", "Please log in": "الرجاء تسجيل الدخول", @@ -187,7 +187,7 @@ "Could not fetch comments": "لم يتمكن من إحضار التعليقات", "`x` ago": "`x` منذ", "Load more": "عرض المزيد", - "Could not create mix.": "لم يستطع عمل خلط.", + "Could not create mix.": "تعذر إنشاء مزيج.", "Empty playlist": "قائمة التشغيل فارغة", "Not a playlist.": "قائمة التشغيل غير صالحة.", "Playlist does not exist.": "قائمة التشغيل غير موجودة.", @@ -195,7 +195,7 @@ "Hidden field \"challenge\" is a required field": "مكان مخفي \"تحدي\" مكان مطلوب", "Hidden field \"token\" is a required field": "مكان مخفي \"رمز\" مكان مطلوب", "Erroneous challenge": "تحدي غير صالح", - "Erroneous token": "روز غير صالح", + "Erroneous token": "رمز مميز خاطئ", "No such user": "مستخدم غير صالح", "Token is expired, please try again": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى", "English": "إنجليزي", @@ -337,7 +337,7 @@ "duration": "المدة الزمنية", "features": "الميزات", "sort": "فرز", - "hour": "ساعة", + "hour": "آخر ساعة", "today": "اليوم", "week": "هذا الأسبوع", "month": "هذا الشهر", @@ -363,7 +363,7 @@ "short": "قصير (< 4 دقائق)", "long": "طويل (> 20 دقيقة)", "footer_source_code": "شفرة المصدر", - "footer_original_source_code": "شفرة المصدر الأصلية", + "footer_original_source_code": "كود المصدر الأصلي", "footer_modfied_source_code": "شفرة المصدر المعدلة", "adminprefs_modified_source_code_url_label": "URL إلى مستودع التعليمات البرمجية المصدرية المعدلة", "footer_documentation": "التوثيق", @@ -398,7 +398,7 @@ "360": "360°", "download_subtitles": "ترجمات - 'x' (.vtt)", "invidious": "الخيالي", - "preferences_save_player_pos_label": "احفظ وقت الفيديو الحالي: ", + "preferences_save_player_pos_label": "حفظ موضع التشغيل: ", "crash_page_you_found_a_bug": "يبدو أنك قد وجدت خطأً برمجيًّا في Invidious!", "generic_videos_count_0": "لا فيديوهات", "generic_videos_count_1": "فيديو واحد", @@ -429,5 +429,35 @@ "generic_playlists_count_2": "قائمتا تشغيل", "generic_playlists_count_3": "{{count}} قوائم تشغيل", "generic_playlists_count_4": "{{count}} قائمة تشغيل", - "generic_playlists_count_5": "{{count}} قائمة تشغيل" + "generic_playlists_count_5": "{{count}} قائمة تشغيل", + "English (United States)": "الإنجليزية (الولايات المتحدة)", + "Indonesian (auto-generated)": "إندونيسي (مُنشأ تلقائيًا)", + "Interlingue": "إنترلينغوي", + "Italian (auto-generated)": "الإيطالية (مُنشأة تلقائيًا)", + "Spanish (auto-generated)": "الأسبانية (تم إنشاؤه تلقائيًا)", + "crash_page_before_reporting": "قبل الإبلاغ عن خطأ، تأكد من وجود:", + "French (auto-generated)": "الفرنسية (مُنشأة تلقائيًا)", + "Portuguese (auto-generated)": "البرتغالية (تم إنشاؤه تلقائيًا)", + "Turkish (auto-generated)": "التركية (تم إنشاؤها تلقائيًا)", + "crash_page_refresh": "حاول تحديث الصفحة ", + "crash_page_switch_instance": "حاول استخدام مثيل آخر ", + "Korean (auto-generated)": "كوري (تم إنشاؤه تلقائيًا)", + "Spanish (Mexico)": "الإسبانية (المكسيك)", + "Vietnamese (auto-generated)": "فيتنامي (تم إنشاؤه تلقائيًا)", + "crash_page_report_issue": "إذا لم يساعد أي مما سبق، يرجى فتح مشكلة جديدة على GitHub (ويفضل أن يكون باللغة الإنجليزية) وتضمين النص التالي في رسالتك (لا تترجم هذا النص):", + "crash_page_read_the_faq": "قراءة الأسئلة المتكررة (الأسئلة الشائعة) ", + "preferences_watch_history_label": "تمكين سجل المشاهدة: ", + "English (United Kingdom)": "الإنجليزية (المملكة المتحدة)", + "Cantonese (Hong Kong)": "الكانتونية (هونغ كونغ)", + "Chinese": "الصينية", + "Chinese (China)": "الصينية (الصين)", + "Chinese (Hong Kong)": "الصينية (هونج كونج)", + "Chinese (Taiwan)": "الصينية (تايوان)", + "Dutch (auto-generated)": "هولندي (تم إنشاؤه تلقائيًا)", + "German (auto-generated)": "ألماني (تم إنشاؤه تلقائيًا)", + "Japanese (auto-generated)": "اليابانية (مُنشأة تلقائيًا)", + "Portuguese (Brazil)": "البرتغالية (البرازيل)", + "Russian (auto-generated)": "الروسية (منشأة تلقائيا)", + "Spanish (Spain)": "الإسبانية (إسبانيا)", + "crash_page_search_issue": "بحثت عن المشكلات الموجودة على Github " } From 7101af764aefa0de85ed802351aa41d81152a3a8 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:04 +0100 Subject: [PATCH 12/21] Update German translation Co-authored-by: Samantaz Fox --- locales/de.json | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/locales/de.json b/locales/de.json index 8381016b..665810a4 100644 --- a/locales/de.json +++ b/locales/de.json @@ -141,7 +141,7 @@ "Show less": "Weniger anzeigen", "Watch on YouTube": "Video auf YouTube ansehen", "Switch Invidious Instance": "Invidious Instanz wechseln", - "Broken? Try another Invidious Instance": "Funktioniert nicht? Probiere eine andere Invidious Instanz aus", + "Broken? Try another Invidious Instance": "Kaputt? Versuche eine andere Invidious Instanz", "Hide annotations": "Anmerkungen ausblenden", "Show annotations": "Anmerkungen anzeigen", "Genre: ": "Genre: ", @@ -346,7 +346,7 @@ "channel": "Kanal", "playlist": "Wiedergabeliste", "movie": "Film", - "show": "Anzeigen", + "show": "anzeigen", "hd": "HD", "subtitles": "Untertitel / CC", "creative_commons": "Creative Commons", @@ -388,7 +388,7 @@ "Video unavailable": "Video nicht verfügbar", "user_created_playlists": "`x` Wiedergabelisten erstellt", "user_saved_playlists": "`x` Wiedergabelisten gespeichert", - "preferences_save_player_pos_label": "Aktuelle Position speichern: ", + "preferences_save_player_pos_label": "Aktuelle Position im Video speichern: ", "360": "360°", "preferences_quality_dash_option_best": "Höchste", "preferences_quality_dash_option_worst": "Niedrigste", @@ -398,5 +398,42 @@ "none": "keine", "videoinfo_started_streaming_x_ago": "Stream begann vor `x`", "videoinfo_watch_on_youTube": "Auf YouTube ansehen", - "preferences_quality_dash_label": "Bevorzugte DASH-Videoqualität: " + "preferences_quality_dash_label": "Bevorzugte DASH-Videoqualität: ", + "generic_subscribers_count": "{{count}} Abonnent", + "generic_subscribers_count_plural": "{{count}} Abonnenten", + "generic_videos_count": "{{count}} Video", + "generic_videos_count_plural": "{{count}} Videos", + "subscriptions_unseen_notifs_count": "{{count}} ungesehene Benachrichtung", + "subscriptions_unseen_notifs_count_plural": "{{count}} ungesehene Benachrichtungen", + "crash_page_refresh": "Versucht haben, die Seite neu zu laden", + "comments_view_x_replies": "{{count}} Antwort anzeigen", + "comments_view_x_replies_plural": "{{count}} Antworten anzeigen", + "generic_count_years": "{{count}} Jahr", + "generic_count_years_plural": "{{count}} Jahre", + "generic_count_weeks": "{{count}} Woche", + "generic_count_weeks_plural": "{{count}} Wochen", + "generic_count_days": "{{count}} Tag", + "generic_count_days_plural": "{{count}} Tage", + "crash_page_before_reporting": "Bevor Sie einen Bug melden, stellen Sie sicher, dass Sie:", + "crash_page_switch_instance": "Eine andere Instanz versucht haben", + "generic_count_hours": "{{count}} Stunde", + "generic_count_hours_plural": "{{count}} Stunden", + "generic_count_minutes": "{{count}} Minute", + "generic_count_minutes_plural": "{{count}} Minuten", + "crash_page_read_the_faq": "Das FAQ gelesen haben", + "crash_page_search_issue": "Nach bereits gemeldeten Bugs auf Github gesucht haben", + "crash_page_report_issue": "Wenn all dies nicht geholfen hat, öffnen Sie bitte ein neues Problem (issue) auf Github (vorzugsweise auf Englisch) und fügen Sie den folgenden Text in Ihre Nachricht ein (bitte übersetzen Sie diesen Text NICHT):", + "generic_views_count": "{{count}} Aufruf", + "generic_views_count_plural": "{{count}} Aufrufe", + "generic_count_seconds": "{{count}} Sekunde", + "generic_count_seconds_plural": "{{count}} Sekunden", + "generic_subscriptions_count": "{{count}} Abo", + "generic_subscriptions_count_plural": "{{count}} Abos", + "tokens_count": "{{count}} Token", + "tokens_count_plural": "{{count}} Tokens", + "comments_points_count": "{{count}} Punkt", + "comments_points_count_plural": "{{count}} Punkte", + "crash_page_you_found_a_bug": "Anscheinend haben Sie einen Fehler in Invidious gefunden!", + "generic_count_months": "{{count}} Monat", + "generic_count_months_plural": "{{count}} Monate" } From 272c85c0627855600facdd1b4ecb282c5b8b30b9 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:04 +0100 Subject: [PATCH 13/21] Update Czech translation Co-authored-by: Fjuro --- locales/cs.json | 277 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 235 insertions(+), 42 deletions(-) diff --git a/locales/cs.json b/locales/cs.json index 7dc24cbc..10dd685e 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -1,6 +1,6 @@ { "LIVE": "ŽIVĚ", - "Shared `x` ago": "Sdíleno před `x`", + "Shared `x` ago": "Zveřejněno před `x`", "Unsubscribe": "Odhlásit odběr", "Subscribe": "Odebírat", "View channel on YouTube": "Otevřít kanál na YouTube", @@ -19,17 +19,17 @@ "Authorize token for `x`?": "Autorizovat token pro `x`?", "Yes": "Ano", "No": "Ne", - "Import and Export Data": "Import a Export údajů", - "Import": "Inport", - "Import Invidious data": "Importovat údaje Invidious", - "Import YouTube subscriptions": "Importovat odběry z YouTube", + "Import and Export Data": "Import a export dat", + "Import": "Importovat", + "Import Invidious data": "Importovat JSON údaje Invidious", + "Import YouTube subscriptions": "Importovat odběry z YouTube/OPML", "Import FreeTube subscriptions (.db)": "Importovat odběry z FreeTube (.db)", "Import NewPipe subscriptions (.json)": "Importovat odběry z NewPipe (.json)", "Import NewPipe data (.zip)": "Importovat údeje z NewPipe (.zip)", "Export": "Exportovat", "Export subscriptions as OPML": "Exportovat odběry jako OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportovat údaje jako OPML (na NewPipe a FreeTube)", - "Export data as JSON": "Exportovat data jako JSON", + "Export data as JSON": "Exportovat data Invidious jako JSON", "Delete account?": "Smazat účet?", "History": "Historie", "An alternative front-end to YouTube": "Alternativní front-end pro YouTube", @@ -38,7 +38,7 @@ "Log in": "Přihlásit se", "Log in/register": "Přihlásit se/vytvořit účet", "Log in with Google": "Přihlásit se s Googlem", - "User ID": "Uživatelské IČ", + "User ID": "ID uživatele", "Password": "Heslo", "Time (h:mm:ss):": "Čas (h:mm:ss):", "Text CAPTCHA": "Textové CAPTCHA", @@ -51,16 +51,16 @@ "preferences_category_player": "Nastavení přehravače", "preferences_video_loop_label": "Vždy opakovat: ", "preferences_autoplay_label": "Automatické přehrávání: ", - "preferences_continue_label": "Přehrát další ve výchozím stavu: ", + "preferences_continue_label": "Automaticky přehrát další: ", "preferences_continue_autoplay_label": "Automaticky přehrát další video: ", "preferences_listen_label": "Poslouchat ve výchozím nastavení: ", "preferences_local_label": "Video přes proxy: ", - "preferences_speed_label": "Základní Rychlost: ", + "preferences_speed_label": "Výchozí rychlost: ", "preferences_quality_label": "Preferovaná kvalita videa: ", "preferences_volume_label": "Hlasitost přehrávače: ", "preferences_comments_label": "Předpřipravené komentáře: ", "youtube": "YouTube", - "reddit": "reddit", + "reddit": "Reddit", "preferences_captions_label": "Standartní Titulky: ", "Fallback captions: ": "Záložní titulky: ", "preferences_related_videos_label": "Zobrazit podobné videa: ", @@ -93,23 +93,23 @@ "`x` is live": "`x` je živě", "preferences_category_data": "Nastavení dat", "Clear watch history": "Smazat historii", - "Import/export data": "importovat/exportovat data", + "Import/export data": "Importovat/exportovat data", "Change password": "Změnit heslo", "Manage subscriptions": "Spravovat odebírané kanály", - "Manage tokens": "Spravovat klíče", - "Watch history": "Historie Sledování", - "Delete account": "Smazat Účet", + "Manage tokens": "Spravovat tokeny", + "Watch history": "Historie sledování", + "Delete account": "Smazat účet", "preferences_category_admin": "Administrátorská nastavení", "preferences_default_home_label": "Základní domovská stránka: ", "preferences_feed_menu_label": "Menu doporučených: ", - "CAPTCHA enabled: ": "CAPTCHA povolen: ", + "CAPTCHA enabled: ": "CAPTCHA povolena: ", "Login enabled: ": "Přihlášení povoleno: ", "Registration enabled: ": "Registrace povolena ", "Report statistics: ": "Oznámit statistiky: ", "Save preferences": "Uložit nastavení", - "Subscription manager": "Správa Odběrů", - "Token manager": "Správa klíčů", - "Token": "Klíč", + "Subscription manager": "Správa odběrů", + "Token manager": "Správa tokenů", + "Token": "Token", "Import/export": "Importovat/exportovat", "unsubscribe": "odhlásit odběr", "revoke": "vrátit zpět", @@ -118,10 +118,10 @@ "Log out": "Odhlásit se", "Source available here.": "Zdrojový kód dostupný zde.", "View JavaScript license information.": "Zobrazit informace o licenci JavaScript .", - "View privacy policy.": "Zobrazit Zásady ochrany osobních údajů.", + "View privacy policy.": "Zobrazit zásady ochrany osobních údajů.", "Trending": "Trendy", "Public": "Veřejné", - "Unlisted": "Nevypsáno", + "Unlisted": "Neveřejné", "Private": "Soukromé", "View all playlists": "Zobrazit všechny playlisty", "Updated `x` ago": "Aktualizováno před `x`", @@ -133,12 +133,12 @@ "Show more": "Zobrazit více", "Show less": "Zobrazit méně", "Watch on YouTube": "Sledovat na YouTube", - "Hide annotations": "Skrýt vysvětlivky", - "Show annotations": "Zobrazit vysvětlivky", + "Hide annotations": "Skrýt poznámky", + "Show annotations": "Zobrazit poznámky", "Genre: ": "Žánr: ", "License: ": "Licence: ", "Family friendly? ": "Vhodné pro děti? ", - "Engagement: ": "Závaznost: ", + "Engagement: ": "Zapojení: ", "English": "Angličtina", "English (auto-generated)": "Angličtina (automaticky generováno)", "Afrikaans": "Afrikánština", @@ -262,27 +262,220 @@ "Video mode": "Videový režim", "Videos": "Videa", "Community": "Komunita", - "rating": "hodnocení", - "date": "datum", - "views": "zhlédnutí", - "duration": "délka", - "hour": "hodina", - "today": "dnes", - "week": "týden", - "month": "měsíc", - "year": "rok", - "video": "video", - "channel": "kanál", - "playlist": "playlist", - "movie": "film", - "show": "zobrazit", + "rating": "Hodnocení", + "date": "Datum zveřejnění", + "views": "Počet zhlédnutí", + "duration": "Délka", + "hour": "Před hodinou", + "today": "Dnes", + "week": "Tento týden", + "month": "Tento měsíc", + "year": "Tento rok", + "video": "Video", + "channel": "Kanál", + "playlist": "Playlist", + "movie": "Film", + "show": "Show", "hd": "HD", - "subtitles": "titulky", + "subtitles": "Titulky", "creative_commons": "Creative Commons", "3d": "3D", - "live": "živě", - "4k": "4k", - "location": "umístění", + "live": "Živě", + "4k": "4K", + "location": "Umístění", "hdr": "HDR", - "filter": "filtr" + "filter": "Filtr", + "generic_count_days_0": "{{count}} den", + "generic_count_days_1": "{{count}} dny", + "generic_count_days_2": "{{count}} dní", + "generic_count_hours_0": "{{count}} hodina", + "generic_count_hours_1": "{{count}} hodiny", + "generic_count_hours_2": "{{count}} hodin", + "crash_page_refresh": "zkusili obnovit stránku", + "crash_page_switch_instance": "zkusili použít jinou instanci", + "preferences_vr_mode_label": "Interaktivní 360-stupňová videa (vyžaduje WebGL): ", + "English (United Kingdom)": "Angličtina (Spojené království)", + "Chinese (China)": "Čínština (Čína)", + "Chinese (Hong Kong)": "Čínština (Hong Kong)", + "Chinese (Taiwan)": "Čínština (Taiwan)", + "Portuguese (auto-generated)": "Portugalština (automaticky generováno)", + "Spanish (auto-generated)": "Španělština (automaticky generováno)", + "Spanish (Mexico)": "Španělština (Mexiko)", + "Spanish (Spain)": "Španělština (Španělsko)", + "generic_count_years_0": "{{count}} rok", + "generic_count_years_1": "{{count}} roky", + "generic_count_years_2": "{{count}} let", + "Fallback comments: ": "Záložní komentáře: ", + "Search": "Hledat", + "Top": "Nejlepší", + "Playlists": "Playlisty", + "videoinfo_started_streaming_x_ago": "Stream spuštěn před `x`", + "videoinfo_watch_on_youTube": "Sledovat na YouTube", + "videoinfo_youTube_embed_link": "Vložení", + "crash_page_read_the_faq": "si přečetli často kladené otázky (FAQ)", + "crash_page_before_reporting": "Před nahlášením chyby se ujistěte, že jste:", + "preferences_quality_option_hd720": "HD720", + "preferences_quality_option_dash": "DASH (adaptivní kvalita)", + "generic_views_count_0": "{{count}} zhlédnutí", + "generic_views_count_1": "{{count}} zhlédnutí", + "generic_views_count_2": "{{count}} zhlédnutí", + "generic_subscriptions_count_0": "{{count}} odběr", + "generic_subscriptions_count_1": "{{count}} odběry", + "generic_subscriptions_count_2": "{{count}} odběrů", + "preferences_quality_dash_option_4320p": "4320p", + "generic_videos_count_0": "{{count}} video", + "generic_videos_count_1": "{{count}} videa", + "generic_videos_count_2": "{{count}} videí", + "preferences_quality_option_small": "Nízká", + "preferences_quality_dash_option_2160p": "2160p", + "preferences_quality_dash_option_1080p": "1080p", + "preferences_quality_dash_option_720p": "720p", + "preferences_quality_dash_option_360p": "360p", + "preferences_quality_dash_option_144p": "144p", + "preferences_quality_option_medium": "Střední", + "preferences_quality_dash_option_1440p": "1440p", + "invidious": "Invidious", + "View more comments on Reddit": "Zobrazit více komentářů na Redditu", + "Invalid TFA code": "Nesprávný TFA kód", + "generic_playlists_count_0": "{{count}} playlist", + "generic_playlists_count_1": "{{count}} playlisty", + "generic_playlists_count_2": "{{count}} playlistů", + "generic_subscribers_count_0": "{{count}} odběratel", + "generic_subscribers_count_1": "{{count}} odběratelé", + "generic_subscribers_count_2": "{{count}} odběratelů", + "preferences_watch_history_label": "Povolit historii sledování: ", + "preferences_quality_dash_option_240p": "240p", + "preferences_region_label": "Země obsahu: ", + "subscriptions_unseen_notifs_count_0": "{{count}} nezobrazené oznámení", + "subscriptions_unseen_notifs_count_1": "{{count}} nezobrazená oznámení", + "subscriptions_unseen_notifs_count_2": "{{count}} nezobrazených oznámení", + "Show replies": "Zobrazit odpovědi", + "Quota exceeded, try again in a few hours": "Kvóta překročena, zkuste to znovu za pár hodin", + "Password cannot be longer than 55 characters": "Heslo nesmí být delší než 55 znaků", + "comments_view_x_replies_0": "Zobrazit {{count}} odpověď", + "comments_view_x_replies_1": "Zobrazit {{count}} odpovědi", + "comments_view_x_replies_2": "Zobrazit {{count}} odpovědí", + "comments_points_count_0": "{{count}} bod", + "comments_points_count_1": "{{count}} body", + "comments_points_count_2": "{{count}} bodů", + "German (auto-generated)": "Němčina (automaticky generováno)", + "Indonesian (auto-generated)": "Indonéština (automaticky generováno)", + "Interlingue": "Interlingue", + "Italian (auto-generated)": "Italština (automaticky generováno)", + "Japanese (auto-generated)": "Japonština (automaticky generováno)", + "Korean (auto-generated)": "Korejština (automaticky generováno)", + "Russian (auto-generated)": "Ruština (automaticky generováno)", + "generic_count_months_0": "{{count}} měsíc", + "generic_count_months_1": "{{count}} měsíce", + "generic_count_months_2": "{{count}} měsíců", + "generic_count_weeks_0": "{{count}} týden", + "generic_count_weeks_1": "{{count}} týdny", + "generic_count_weeks_2": "{{count}} týdnů", + "generic_count_minutes_0": "{{count}} minuta", + "generic_count_minutes_1": "{{count}} minuty", + "generic_count_minutes_2": "{{count}} minut", + "short": "Krátké (< 4 minuty)", + "long": "Dlouhé (> 20 minut)", + "footer_documentation": "Dokumentace", + "next_steps_error_message_refresh": "Obnovit stránku", + "Chinese": "Čínština", + "360": "360°", + "Dutch (auto-generated)": "Nizozemština (automaticky generováno)", + "Erroneous token": "Chybný token", + "tokens_count_0": "{{count}} token", + "tokens_count_1": "{{count}} tokeny", + "tokens_count_2": "{{count}} tokenů", + "Portuguese (Brazil)": "Portugalština (Brazílie)", + "content_type": "Typ", + "sort": "Řazení", + "Token is expired, please try again": "Token vypršel, zkuste to prosím znovu", + "English (United States)": "Angličtina (Spojené státy)", + "Cantonese (Hong Kong)": "Kantonština (Hong Kong)", + "French (auto-generated)": "Francouzština (automaticky generováno)", + "Turkish (auto-generated)": "Turečtina (automaticky generováno)", + "Vietnamese (auto-generated)": "Vietnamština (automaticky generováno)", + "Current version: ": "Aktuální verze: ", + "next_steps_error_message": "Měli byste zkusit: ", + "footer_donate_page": "Přispět", + "download_subtitles": "Titulky - `x` (.vtt)", + "%A %B %-d, %Y": "%A %B %-d, %Y", + "YouTube comment permalink": "Permanentní odkaz YouTube komentáře", + "permalink": "permalink", + "purchased": "Zakoupeno", + "footer_original_source_code": "Původní zdrojový kód", + "adminprefs_modified_source_code_url_label": "URL repozitáře s upraveným zdrojovým kódem", + "Video unavailable": "Video není dostupné", + "next_steps_error_message_go_to_youtube": "Jít na YouTube", + "footer_modfied_source_code": "Upravený zdrojový kód", + "none": "žádné", + "videoinfo_invidious_embed_link": "Odkaz na vložení", + "user_saved_playlists": "`x` uložených playlistů", + "crash_page_you_found_a_bug": "Vypadá to, že jste našli chybu v Invidious!", + "user_created_playlists": "`x` vytvořených playlistů", + "crash_page_search_issue": "vyhledali existující problémy na GitHubu", + "crash_page_report_issue": "Pokud nepomohlo nic z výše uvedeného, otevřete prosím nový problém na GitHubu (pokud možno v angličtině) a zahrňte do zprávy následující text (NEpřekládejte jej):", + "preferences_quality_dash_label": "Preferovaná kvalita videí DASH: ", + "preferences_quality_dash_option_auto": "Automatická", + "preferences_quality_dash_option_best": "Nejlepší", + "preferences_quality_dash_option_worst": "Nejhorší", + "preferences_quality_dash_option_480p": "480p", + "Top enabled: ": "Povoleny nejlepší: ", + "generic_count_seconds_0": "{{count}} sekunda", + "generic_count_seconds_1": "{{count}} sekundy", + "generic_count_seconds_2": "{{count}} sekund", + "preferences_save_player_pos_label": "Uložit pozici přehrávání: ", + "Incorrect password": "Nesprávné heslo", + "View as playlist": "Zobrazit jako playlist", + "View Reddit comments": "Zobrazit komentáře z Redditu", + "No such user": "Uživatel nenalezen", + "Playlist privacy": "Soukromí playlistu", + "Wrong answer": "Špatná odpověď", + "Could not pull trending pages.": "Nepodařilo se získat trendy stránky.", + "Erroneous CAPTCHA": "Chybná CAPTCHA", + "Password is a required field": "Heslo je vyžadované pole", + "preferences_automatic_instance_redirect_label": "Automatické přesměrování instance (fallback na redirect.invidious.io): ", + "Broken? Try another Invidious Instance": "Je něco rozbité? Zkuste jinou instanci Invidious", + "Switch Invidious Instance": "Přepnout instanci Invidious", + "Empty playlist": "Prázdný playlist", + "footer_source_code": "Zdrojový kód", + "relevance": "Relevantnost", + "View YouTube comments": "Zobrazit YouTube komentáře", + "Blacklisted regions: ": "Oblasti na černé listině: ", + "Wrong username or password": "Nesprávné uživatelské jméno nebo heslo", + "Please sign in using 'Log in with Google'": "Přihlaste se prosím pomocí Googlu", + "Password cannot be empty": "Heslo nemůže být prázné", + "preferences_category_misc": "Různá nastavení", + "preferences_show_nick_label": "Zobrazit přezdívku na vrchu: ", + "Whitelisted regions: ": "Oblasti na bílé listině: ", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Zdravíme! Zdá se, že máte vypnutý JavaScript. Klikněte sem pro zobrazení komentářů - nezapomeňte, že se mohou načítat trochu déle.", + "User ID is a required field": "ID uživatele je vyžadované pole", + "Please log in": "Přihlaste se prosím", + "Invidious Private Feed for `x`": "Soukromý kanál Invidious pro `x`", + "Deleted or invalid channel": "Smazaný nebo neplatný kanál", + "This channel does not exist.": "Tento kanál neexistuje.", + "Hidden field \"token\" is a required field": "Skryté pole \"token\" je vyžadované", + "features": "Funkce", + "Wilson score: ": "Skóre Wilson: ", + "Shared `x`": "Sdíleno `x`", + "Premieres in `x`": "Premiéra za `x`", + "View `x` comments": { + "([^.,0-9]|^)1([^.,0-9]|$)": "Zobrazit `x` komentář", + "": "Zobrazit `x` komentářů" + }, + "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "Nepodařilo se přihlásit, ujistěte se, že je povoleno dvoufázové ověřování (autentifikátor nebo SMS).", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "Přihlášení selhalo. Toto se může stát, když není na vašem účtu povolené dvoufázové ověřování.", + "Could not get channel info.": "Nepodařilo se získat informace o kanálu.", + "Could not fetch comments": "Nepodařilo se získat komentáře", + "Could not create mix.": "Nepodařilo se vytvořit mix.", + "Hidden field \"challenge\" is a required field": "Skryté pole \"challenge\" je vyžadované", + "Released under the AGPLv3 on Github.": "Vydáno pod licencí AGPLv3 na GitHubu.", + "Hide replies": "Skrýt odpovědi", + "channel:`x`": "kanál: `x`", + "Load more": "Načíst další", + "Not a playlist.": "Není playlist.", + "Playlist does not exist.": "Playlist neexistuje.", + "Erroneous challenge": "Chybná výzva", + "Premieres `x`": "Premiéra `x`", + "CAPTCHA is a required field": "CAPTCHA je vyžadované pole", + "`x` ago": "Před `x`" } From 5b19d33387be0658f61d31a94dc99940fc0b6680 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:04 +0100 Subject: [PATCH 14/21] Update Russian translation Co-authored-by: Samantaz Fox --- locales/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index 88f81395..c223bcf8 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -476,5 +476,6 @@ "360": "360°", "Video unavailable": "Видео недоступно", "preferences_save_player_pos_label": "Запоминать позицию: ", - "preferences_region_label": "Страна: " + "preferences_region_label": "Страна: ", + "preferences_watch_history_label": "Включить историю просмотров " } From e3222d99ac00271abac74e95e4f1ba7a7036b6c5 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:04 +0100 Subject: [PATCH 15/21] Update Swedish translation Co-authored-by: Samantaz Fox --- locales/sv-SE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/sv-SE.json b/locales/sv-SE.json index 98c24cc3..ab0d0773 100644 --- a/locales/sv-SE.json +++ b/locales/sv-SE.json @@ -327,8 +327,8 @@ "Videos": "Videor", "Playlists": "Spellistor", "Community": "Gemenskap", - "relevance": "relevans", - "rating": "rankning", + "relevance": "Relevans", + "rating": "Rankning", "date": "datum", "views": "visningar", "content_type": "Typ", From 1be4af733bc1d112294ab5c27247a8937bf8faf7 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:04 +0100 Subject: [PATCH 16/21] Update Croatian translation Co-authored-by: Milo Ivir --- locales/hr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/hr.json b/locales/hr.json index 1de3fa79..688368d2 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -88,7 +88,7 @@ "channel name": "ime kanala", "channel name - reverse": "ime kanala – obrnuto", "Only show latest video from channel: ": "Prikaži samo najnovija videa kanala: ", - "Only show latest unwatched video from channel: ": "Prikaži samo najnovija nepogledana videa kanala: ", + "Only show latest unwatched video from channel: ": "Prikaži samo najnovija nepogledana videa od kanala: ", "preferences_unseen_only_label": "Prikaži samo nepogledane: ", "preferences_notifications_only_label": "Prikaži samo obavijesti (ako ih ima): ", "Enable web notifications": "Aktiviraj web-obavijesti", @@ -476,5 +476,6 @@ "Chinese (Hong Kong)": "Kineski (Hong Kong)", "Korean (auto-generated)": "Korejski (automatski generiran)", "Portuguese (auto-generated)": "Portugalski (automatski generiran)", - "Spanish (auto-generated)": "Španjolski (automatski generiran)" + "Spanish (auto-generated)": "Španjolski (automatski generiran)", + "preferences_watch_history_label": "Aktiviraj povijest gledanja: " } From 391690d570185a4816e18af15ed86461e2cfca29 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:05 +0100 Subject: [PATCH 17/21] Update Lithuanian translation Co-authored-by: Samantaz Fox --- locales/lt.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/lt.json b/locales/lt.json index 5b27eae4..a5cee472 100644 --- a/locales/lt.json +++ b/locales/lt.json @@ -369,5 +369,8 @@ "footer_modfied_source_code": "Pakeistas pirminis kodas", "footer_donate_page": "Paaukoti", "preferences_region_label": "Turinio šalis: ", - "preferences_quality_dash_label": "Pageidaujama DASH vaizdo kokybė: " + "preferences_quality_dash_label": "Pageidaujama DASH vaizdo kokybė: ", + "preferences_quality_dash_option_best": "Geriausia", + "preferences_quality_dash_option_worst": "Blogiausia", + "preferences_quality_dash_option_auto": "Automatinis" } From e414476c6e0264194b9261f25f54289320481fe3 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:05 +0100 Subject: [PATCH 18/21] Update Spanish translation Co-authored-by: Samantaz Fox --- locales/es.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/es.json b/locales/es.json index fbdb13ac..689cb310 100644 --- a/locales/es.json +++ b/locales/es.json @@ -196,7 +196,7 @@ "Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio", "Erroneous challenge": "Desafío no válido", "Erroneous token": "Símbolo no válido", - "No such user": "Usuario no válido", + "No such user": "Usuario no existe", "Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo", "English": "Inglés", "English (auto-generated)": "Inglés (generados automáticamente)", @@ -358,7 +358,7 @@ "filter": "filtro", "Current version: ": "Versión actual: ", "next_steps_error_message": "Después de lo cual deberías intentar: ", - "next_steps_error_message_refresh": "Recargar", + "next_steps_error_message_refresh": "Recargar la página", "next_steps_error_message_go_to_youtube": "Ir a YouTube", "short": "Corto (< 4 minutos)", "long": "Largo (> 20 minutos)", From 49a7c16de58040a86ca4adfe9a581acaf775a99c Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:05 +0100 Subject: [PATCH 19/21] Update Greek translation Co-authored-by: THANOS SIOURDAKIS --- locales/el.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/el.json b/locales/el.json index 36fc695b..24e42153 100644 --- a/locales/el.json +++ b/locales/el.json @@ -448,5 +448,6 @@ "none": "κανένα", "videoinfo_youTube_embed_link": "Ενσωμάτωση", "videoinfo_invidious_embed_link": "Σύνδεσμος Ενσωμάτωσης", - "show": "Μπάρα προόδου διαβάσματος" + "show": "Μπάρα προόδου διαβάσματος", + "preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: " } From ad89be7523d1d11103074620cd09a0d97fc2361d Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 9 Mar 2022 23:54:05 +0100 Subject: [PATCH 20/21] Update Italian translation Co-authored-by: Samantaz Fox --- locales/it.json | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/locales/it.json b/locales/it.json index c80f4d96..411148c9 100644 --- a/locales/it.json +++ b/locales/it.json @@ -390,7 +390,41 @@ "preferences_quality_dash_option_best": "Migliore", "preferences_quality_dash_option_worst": "Peggiore", "invidious": "Invidious", - "preferences_quality_dash_label": "Qualità video DASH preferita ", + "preferences_quality_dash_label": "Qualità video DASH preferita: ", "preferences_quality_option_hd720": "HD720", - "preferences_quality_dash_option_auto": "Automatica" + "preferences_quality_dash_option_auto": "Automatica", + "videoinfo_watch_on_youTube": "Guarda su YouTube", + "preferences_extend_desc_label": "Espandi automaticamente la descrizione del video: ", + "preferences_vr_mode_label": "Video interattivi a 360 gradi: ", + "Show less": "Mostra di meno", + "Switch Invidious Instance": "Cambia istanza Invidious", + "next_steps_error_message_go_to_youtube": "Andare su YouTube", + "footer_documentation": "Documentazione", + "footer_original_source_code": "Codice sorgente originale", + "footer_modfied_source_code": "Codice sorgente modificato", + "none": "nessuno", + "videoinfo_started_streaming_x_ago": "Ha iniziato a trasmettere `x` fa", + "download_subtitles": "Sottotitoli - `x` (.vtt)", + "user_saved_playlists": "playlist salvate da `x`", + "preferences_automatic_instance_redirect_label": "Reindirizzamento automatico dell'istanza (ripiego su redirect.invidious.io): ", + "Video unavailable": "Video non disponibile", + "preferences_show_nick_label": "Mostra nickname in alto: ", + "short": "Corto (< 4 minuti)", + "videoinfo_youTube_embed_link": "Incorpora", + "videoinfo_invidious_embed_link": "Incorpora collegamento", + "user_created_playlists": "playlist create da `x`", + "preferences_save_player_pos_label": "Memorizza il minutaggio raggiunto dal video: ", + "purchased": "Acquistato", + "preferences_quality_option_dash": "DASH (qualità adattiva)", + "preferences_region_label": "Nazione del contenuto: ", + "preferences_category_misc": "Preferenze varie", + "show": "Serie", + "long": "Lungo (> 20 minuti)", + "next_steps_error_message": "Dopodiché dovresti provare a: ", + "next_steps_error_message_refresh": "Aggiornare", + "footer_donate_page": "Dona", + "footer_source_code": "Codice sorgente", + "adminprefs_modified_source_code_url_label": "Link per il repository del codice sorgente modificato", + "Show more": "Mostra di più", + "Broken? Try another Invidious Instance": "Non funzionante? Prova un’altra istanza Invidious" } From 6d3b907307346f8774d012b9ee24f203d5d6d48d Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Fri, 11 Mar 2022 20:51:12 +0100 Subject: [PATCH 21/21] Update --help to mention that --migrate is still in beta --- src/invidious.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious.cr b/src/invidious.cr index abc459b7..a470c6b6 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -103,7 +103,7 @@ Kemal.config.extra_options do |parser| puts SOFTWARE.to_pretty_json exit end - parser.on("--migrate", "Run any migrations") do + parser.on("--migrate", "Run any migrations (beta, use at your own risk!!") do Invidious::Database::Migrator.new(PG_DB).migrate exit end